当然可以!以下是《类和对象(3)——构造函数、析构函数和拷贝构造函数》的详尽讲解,适合 C++ 初学者夯实面向对象基础,也是掌握内存管理与对象生命周期的关键一课。


🎯 类和对象(3) —— 构造函数、析构函数和拷贝构造函数详解

📘《Hello C++ World!》系列第5篇


📚 目录

  1. 构造函数:对象诞生的仪式
  2. 析构函数:对象终结的告别
  3. 拷贝构造函数:复制的本质
  4. 构造函数重载与默认构造
  5. 编译器自动生成行为
  6. 深拷贝 vs 浅拷贝
  7. const 对象与引用参数的构造问题
  8. 实战演示:动态数组类构建与析构追踪
  9. 总结与建议

1️⃣ 构造函数(Constructor)

构造函数是在 对象创建时自动调用的特殊函数,用于初始化对象成员变量。

✨ 语法特点:

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

    // 构造函数
    Person(std::string n, int a) {
        name = n;
        age = a;
    }
};

✨ 特性:

  • 无需显式调用,Person p("Tom", 18); 自动触发
  • 没有返回值类型
  • 支持重载(多个构造函数)

2️⃣ 析构函数(Destructor)

析构函数在 对象生命周期结束时自动调用,用于释放资源、打印日志等清理工作。

✨ 语法特点:

~Person() {
    std::cout << name << " 被析构了\n";
}
  • 名称前带 ~
  • 无参数、无返回值
  • 一个类只能有一个析构函数

3️⃣ 拷贝构造函数(Copy Constructor)

当你使用一个对象去初始化另一个对象时,会触发拷贝构造函数:

Person p1("Alice", 20);
Person p2 = p1;  // 拷贝构造调用

✨ 定义形式:

Person(const Person& other) {
    name = other.name;
    age = other.age;
    std::cout << "调用拷贝构造\n";
}

✨ 典型调用场景:

  • 以值传参
  • 函数返回值(返回对象)
  • 显式或隐式拷贝初始化

4️⃣ 构造函数重载与默认构造函数

✅ 重载构造函数:

Person() { name = "Unknown"; age = 0; }
Person(std::string n) { name = n; age = 0; }
Person(std::string n, int a) { name = n; age = a; }

✅ 默认构造函数:

无参构造函数被称为默认构造:

Person(); // 默认构造

如果你手动定义了任何带参构造函数,编译器不会自动生成默认构造函数,需要显式声明:

Person() = default;

5️⃣ 编译器的自动生成规则

如未手动定义,编译器会提供:

函数类型是否自动生成
默认构造函数✅(无其他构造时)
拷贝构造函数
拷贝赋值运算符
析构函数

但一旦手写其中之一,其他的可能不会再自动生成,必须显式声明。


6️⃣ 深拷贝 vs 浅拷贝

✅ 浅拷贝(默认行为):

只是复制指针,不复制数据所指向的内容。

class MyArray {
    int* data;
public:
    MyArray() { data = new int[10]; }
    ~MyArray() { delete[] data; }
};

默认拷贝构造函数只复制 data 指针,会导致 双重释放 (double free)

✅ 深拷贝(手写拷贝构造):

MyArray(const MyArray& other) {
    data = new int[10];
    std::copy(other.data, other.data + 10, data);
}

7️⃣ const 对象与引用参数的构造限制

void show(const Person& p); // 推荐:防止拷贝,支持常量

void show(Person p); // 会触发拷贝构造
const Person p; // 需要默认构造函数 + 支持 const 限定的成员访问

8️⃣ 实战演示:对象构造与析构过程

class Tracker {
    std::string id;
public:
    Tracker(std::string s) : id(s) {
        std::cout << "构造: " << id << "\n";
    }
    Tracker(const Tracker& other) : id(other.id + "-copy") {
        std::cout << "拷贝构造: " << id << "\n";
    }
    ~Tracker() {
        std::cout << "析构: " << id << "\n";
    }
};

int main() {
    Tracker t1("A");
    Tracker t2 = t1;     // 拷贝构造
    return 0;            // 析构顺序:t2 -> t1
}

✅ 总结与建议

特性构造函数析构函数拷贝构造
是否可重载
调用时机创建对象释放对象拷贝对象
是否自动生成
是否需自定义⚠️ 资源管理类建议手写⚠️ 删除资源的类必须写⚠️ 存在指针成员时必须写

🎯 建议:

  • 对于有指针资源的类:必须写 三大函数(构造、拷贝构造、析构)
  • 养成 Rule of Three(甚至 Rule of Five)思想
  • 拷贝构造必须使用 const 引用
  • 析构函数必须是 virtual(若用于继承)

📚 参考资料


如需我补充该内容为图文笔记、教学 PPT、或配套项目例程(如模拟 String 类、资源管理类),欢迎告诉我!是否需要接下来继续讲解赋值运算符重载与移动构造函数?