C++面向对象2——再学C/C++:类与结构体

发布于:2025-06-28 ⋅ 阅读:(20) ⋅ 点赞:(0)

C语言结构体函数使用函数指针

#include <stdio.h>

struct car{
    char* name;
    int age;

    void (*print)(struct car* c);
}; 

void printA(struct car* c) {
    printf("CarAAAA name: %s, Age: %d\n", c->name, c->age);
}
void printB(struct car* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
}

int main()
{
    struct car car1;
    car1.name = "BMW";
    car1.age = 5;
    car1.print = printA;
    car1.print(&car1);

    struct car car2 = {"Audi", 3, printB};
    car2.print(&car2);

    struct car* car3 = (struct car*)malloc(sizeof(struct car));
    car3->name = "Tesla";
    car3->age = 2;
    car3->print = printB;
    car3->print(car3);

    printf("你好!\n");
    return 0;
}

这段C语言代码演示了结构体(struct car)的使用、函数指针作为结构体成员,以及不同方式初始化结构体变量的过程。以下是逐部分的详细解释:

1. 结构体 struct car 的定义

struct car{
    char* name;     // 指向汽车名称的字符串指针
    int age;        // 汽车使用年限(或车龄)
    void (*print)(struct car* c);  // 函数指针:指向一个打印汽车信息的函数
};

这是一个自定义的结构体类型,包含3个成员:

  • namechar* 类型,存储汽车名称的字符串地址(如 “BMW”)。
  • ageint 类型,存储汽车的使用年限。
  • print:函数指针,指向一个**返回值为 void,参数为 struct car*(指向自身的指针)**的函数。它的作用是:当调用 car.print() 时,执行具体的打印逻辑。

2. 打印函数 printAprintB

void printA(struct car* c) {
    printf("CarAAAA name: %s, Age: %d\n", c->name, c->age);
}
void printB(struct car* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
}

这两个函数是 struct carprint 函数指针的具体实现,功能是打印汽车的名称(c->name)和车龄(c->age),但输出的前缀不同(CarAAAACarBBBB)。
参数 struct car* c 是指向具体 car 结构体实例的指针,通过 c->namec->age 可以访问该实例的成员变量。

3. main 函数中的结构体实例化与使用

① 实例 car1:手动赋值
struct car car1;
car1.name = "BMW";      // 直接赋值名称
car1.age = 5;           // 赋值车龄
car1.print = printA;    // 函数指针指向 printA
car1.print(&car1);      // 调用 printA 打印 car1 的信息
  • 先定义 car1struct car 类型的变量。
  • 逐个赋值成员变量:name 指向字符串 "BMW"age 设为5,print 函数指针指向 printA
  • 调用 car1.print(&car1):通过函数指针调用 printA,传入 car1 的地址(&car1),printA 内部通过该指针访问 car1 的成员并打印。
    输出结果:CarAAAA name: BMW, Age: 5
② 实例 car2:初始化列表赋值
struct car car2 = {"Audi", 3, printB};  // 按顺序初始化成员
car2.print(&car2);  // 调用 printB 打印 car2 的信息
  • 使用结构体初始化列表直接赋值,顺序必须与结构体成员定义的顺序一致:
    第一个值对应 name"Audi"),第二个对应 age(3),第三个对应 print(函数指针 printB)。
  • 调用 car2.print(&car2):通过 printB 打印 car2 的信息。
    输出结果:CarBBBB name: Audi, Age: 3
③ 实例 car3:动态内存分配
struct car* car3 = (struct car*)malloc(sizeof(struct car));  // 动态分配内存
car3->name = "Tesla";  // 赋值名称(注意:`->` 是指针访问成员的语法)
car3->age = 2;         // 赋值车龄
car3->print = printB;  // 函数指针指向 printB
car3->print(car3);     // 调用 printB 打印 car3 的信息
  • 使用 malloc 动态分配 struct car 大小的内存,并将地址赋值给指针 car3
  • 通过 -> 操作符(指针访问成员的语法)为 car3 的成员赋值。
  • 调用 car3->print(car3):传入 car3 自身的指针(无需取地址,因为 car3 已经是指针),通过 printB 打印信息。
    输出结果:CarBBBB name: Tesla, Age: 2
④ 最后输出中文
printf("你好!\n");  // 输出中文 "你好!"

直接调用标准库函数 printf 输出字符串。注意:能否正确显示中文取决于编译器和终端的编码设置(如 UTF-8),现代环境通常可以正常显示。

潜在问题(需注意)

  • 内存泄漏car3 使用 malloc 分配了内存,但代码中未调用 free(car3) 释放内存。长期运行可能导致内存泄漏。
  • 字符串指针的风险namechar* 类型,直接指向字符串字面量(如 "BMW"),这些字符串存储在只读内存中,不可修改。如果尝试修改(如 car1.name[0] = 'b')会导致未定义行为。

完整输出结果

CarAAAA name: BMW, Age: 5
CarBBBB name: Audi, Age: 3
CarBBBB name: Tesla, Age: 2
你好!

C++结构体与类函数使用函数指针

#include <stdio.h>
#include <iostream>


struct car{
    char* name;
    int age;

    void (*print)(struct car* c);
};

class motor{
public://如果不写public,则默认是priate
    std::string name;
    int age;

    //函数指针
    void (*print)(motor* m);

    //成员函数声明
    void printClaim();
    //成员函数定义
    void printDefin() {
        printf("Motor::Define name: %s, Age: %d\n", name.c_str(), age);
    }
};


void printA(struct car* c) {
    printf("CarAAAA name: %s, Age: %d\n", c->name, c->age);
}
void printB(struct car* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
}
void printC(motor* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
}

void motor::printClaim() {
    printf("MotorClaim name: %s, Age: %d\n", name.c_str(), age);
}

int main()
{
    struct car car1;
    car1.name = "BMW";
    car1.age = 5;
    car1.print = printA;
    car1.print(&car1);

    struct car car2 = {"Audi", 3, printB};
    car2.print(&car2);

    struct car* car3 = new car;
    car3->age = 2;
    car3->print = printB;
    car3->print(car3);

    //上述表明c++也支持c语法,注意malloc变成了new
    motor m1;
    m1.name = "Tesla";
    m1.age = 1;
    m1.print = printC;
    m1.print(&m1);
    m1.printClaim();
    m1.printDefin();

    //使用new创建对象
    motor* m2 = new motor;
    m2->name = "BYD";
    m2->age = 2;    
    m2->print = printC;
    m2->print(m2);
    m2->printClaim();
    m2->printDefin();

    printf("你好!\n");
    return 0;
}

这段代码展示了C++中结构体与类的混合使用,以及函数指针和成员函数的区别。以下是关键分析:

1. C++兼容C语言特性

// C风格结构体(默认public)
struct car{
    char* name;
    int age;
    void (*print)(struct car* c);
};
  • C++完全兼容C语言的结构体语法。
  • 与C不同的是,C++的结构体可以包含成员函数(但此代码未使用)。

2. C++类与结构体的区别

class motor{
public:
    std::string name;  // C++字符串类
    int age;
    void (*print)(motor* m);  // 函数指针(非成员函数)
    
    void printClaim();  // 成员函数声明
    void printDefin() { /* ... */ }  // 成员函数定义
};
  • 类(class) 默认成员是 private,而结构体(struct) 默认是 public
  • 使用 std::string(需包含 <string> 头文件)代替C风格的 char*,更安全且自动管理内存。

3. 函数指针与成员函数的区别

函数指针(全局/静态函数)
void printC(motor* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);  // 错误:%s 应改为 %ls
}
  • 函数指针 void (*print)(motor* m) 只能指向全局函数或静态成员函数,无法直接指向类的非静态成员函数(因为非静态成员函数隐含 this 指针)。
  • 问题printf 中使用 %s 打印 std::string 是错误的,应使用 c->name.c_str() 转换为 const char*
成员函数
void motor::printClaim() {
    printf("MotorClaim name: %s, Age: %d\n", name.c_str(), age);
}
  • 成员函数属于类的实例,可直接访问类的成员变量(无需通过指针)。
  • 通过对象或指针调用:m1.printClaim()m2->printClaim()

4. 对象创建与内存管理

C风格内存分配
struct car* car3 = new car;  // C++中可用new替代C的malloc
  • new 会调用构造函数(即使类/结构体没有显式定义构造函数)。
  • 问题:未初始化 car3->name,直接调用 print 会导致未定义行为。
C++对象创建
motor* m2 = new motor;  // 动态分配
motor m1;               // 栈上分配
  • new 返回指针,需手动 delete 释放内存。
  • 栈上分配的对象在作用域结束时自动销毁。

5. 代码中的错误与警告

① 未包含必要的头文件
#include <iostream>  // 包含了iostream但未使用
// 缺少必要的:
#include <cstdio>    // 若使用printf
#include <string>    // 若使用std::string
printfstd::string 不兼容
// 错误:
printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
// 正确:
printf("CarBBBB name: %s, Age: %d\n", c->name.c_str(), c->age);
③ 内存泄漏
struct car* car3 = new car;  // 未delete
motor* m2 = new motor;       // 未delete
  • 使用 new 分配的内存必须用 delete 释放,否则会导致内存泄漏。

6. 修正后的完整代码

#include <cstdio>    // printf
#include <string>    // std::string

struct car{
    char* name;
    int age;
    void (*print)(struct car* c);
};

class motor{
public:
    std::string name;
    int age;
    void (*print)(motor* m);
    void printClaim();
    void printDefin() {
        printf("Motor::Define name: %s, Age: %d\n", name.c_str(), age);
    }
};

void printA(struct car* c) {
    printf("CarAAAA name: %s, Age: %d\n", c->name, c->age);
}
void printB(struct car* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
}
void printC(motor* c) {
    printf("Motor name: %s, Age: %d\n", c->name.c_str(), c->age);  // 修正:使用c_str()
}

void motor::printClaim() {
    printf("MotorClaim name: %s, Age: %d\n", name.c_str(), age);
}

int main()
{
    struct car car1;
    car1.name = "BMW";
    car1.age = 5;
    car1.print = printA;
    car1.print(&car1);

    struct car car2 = {"Audi", 3, printB};
    car2.print(&car2);

    struct car* car3 = new car;
    car3->name = "Tesla";  // 补充:初始化name
    car3->age = 2;
    car3->print = printB;
    car3->print(car3);
    delete car3;  // 释放内存

    motor m1;
    m1.name = "Tesla";
    m1.age = 1;
    m1.print = printC;
    m1.print(&m1);
    m1.printClaim();
    m1.printDefin();

    motor* m2 = new motor;
    m2->name = "BYD";
    m2->age = 2;    
    m2->print = printC;
    m2->print(m2);
    m2->printClaim();
    m2->printDefin();
    delete m2;  // 释放内存

    printf("你好!\n");
    return 0;
}

7. 关键总结

  • 结构体 vs 类:C++中结构体与类的主要区别是默认访问权限(public/private)。
  • 函数指针:只能指向全局函数或静态成员函数,无法直接指向非静态成员函数。
  • 内存管理:使用 new 分配的内存必须用 delete 释放,避免内存泄漏。
  • 字符串处理std::stringchar* 更安全,使用 c_str() 转换为C风格字符串。

类套类

#include <stdio.h>
#include <iostream>

class wheel{
public:
    std::string name;
    int age;

    //成员函数定义
    void printDefin() {
        printf("Wheel::Define name: %s, Age: %d\n", name.c_str(), age);
    }
};  


class motor{
public://如果不写public,则默认是priate
    std::string name;
    int age;
    wheel wheel1;
    wheel *wheel2;

    //函数指针
    void (*print)(motor* m);

    //成员函数声明
    void printClaim();
    //成员函数定义
    void printDefin() {
        printf("Motor::Define name: %s, Age: %d\n", name.c_str(), age);
    }
};
void printC(motor* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);
}

void motor::printClaim() {
    printf("MotorClaim name: %s, Age: %d\n", name.c_str(), age);
}

int main()
{
    //使用new创建对象
    motor* m2 = new motor;
    m2->name = "BYD";
    m2->age = 2;    
    m2->print = printC;
    m2->print(m2);
    m2->printClaim();
    m2->printDefin();
    m2->wheel1.name = "MIKIN";
    m2->wheel1.age = 3;
    m2->wheel1.printDefin();
    m2->wheel2 = new wheel;
    m2->wheel2->name = "HONDA";
    m2->wheel2->age = 4;
    m2->wheel2->printDefin();

    return 0;
}

这段代码展示了C++中类的组合关系(composition)和内存管理,但存在几个关键问题需要注意:

1. 类的组合关系

class motor{
public:
    wheel wheel1;    // 组合:直接包含wheel对象
    wheel *wheel2;   // 组合:通过指针包含wheel对象
};
  • wheel1motor 对象创建时,wheel1 会自动构造;motor 销毁时,wheel1 会自动销毁。
  • wheel2:需要手动 newdelete,否则会导致内存泄漏。

2. 内存泄漏问题

m2->wheel2 = new wheel;  // 分配内存
// ... 使用 wheel2 ...
// 缺少对应的 delete m2->wheel2;
delete m2;  // 仅释放了 m2,未释放 wheel2
  • 问题wheel2 是动态分配的内存,但程序结束前未调用 delete m2->wheel2,导致内存泄漏。

3. 字符串打印问题

void printC(motor* c) {
    printf("CarBBBB name: %s, Age: %d\n", c->name, c->age);  // 错误
}
  • 错误%s 要求参数为 const char*,但 c->namestd::string
  • 修正:使用 c->name.c_str()std::string 转换为 const char*

4. 修正后的完整代码

#include <cstdio>    // printf
#include <string>    // std::string

class wheel{
public:
    std::string name;
    int age;
    void printDefin() {
        printf("Wheel::Define name: %s, Age: %d\n", name.c_str(), age);
    }
};  

class motor{
public:
    std::string name;
    int age;
    wheel wheel1;
    wheel *wheel2;

    void (*print)(motor* m);
    void printClaim();
    void printDefin() {
        printf("Motor::Define name: %s, Age: %d\n", name.c_str(), age);
    }
};

void printC(motor* c) {
    printf("Motor name: %s, Age: %d\n", c->name.c_str(), age);  // 修正:使用 c_str()
}

void motor::printClaim() {
    printf("MotorClaim name: %s, Age: %d\n", name.c_str(), age);
}

int main()
{
    motor* m2 = new motor;
    m2->name = "BYD";
    m2->age = 2;    
    m2->print = printC;
    m2->print(m2);
    m2->printClaim();
    m2->printDefin();
    
    m2->wheel1.name = "MIKIN";
    m2->wheel1.age = 3;
    m2->wheel1.printDefin();
    
    m2->wheel2 = new wheel;
    m2->wheel2->name = "HONDA";
    m2->wheel2->age = 4;
    m2->wheel2->printDefin();

    delete m2->wheel2;  // 释放 wheel2 的内存
    delete m2;          // 释放 m2 的内存

    return 0;
}

5. 改进建议

① 使用智能指针管理动态内存
#include <memory>  // 包含智能指针头文件

class motor{
public:
    std::unique_ptr<wheel> wheel2;  // 使用 unique_ptr 自动管理内存
};

// 在 main 函数中:
m2->wheel2 = std::make_unique<wheel>();  // 无需手动 delete
  • 优势std::unique_ptr 会在对象销毁时自动释放内存,避免内存泄漏。
② 构造函数初始化成员
class motor{
public:
    motor() : wheel2(new wheel()) {}  // 构造函数初始化 wheel2
    // 或使用 C++11 的成员初始化器
    // std::unique_ptr<wheel> wheel2 = std::make_unique<wheel>();
};
  • 优势:确保指针成员被正确初始化,减少使用时的错误。

6. 关键知识点

  • 组合关系:类可以包含其他类的对象或指针,实现更复杂的数据结构。
  • 内存管理:手动 new 的对象必须手动 delete,建议优先使用智能指针(如 std::unique_ptr)。
  • 字符串处理printf 打印 std::string 时需用 c_str() 转换为 const char*

网站公告

今日签到

点亮在社区的每一天
去签到