解码构造与析构

构造与析构基础概念

核心定义

  • 构造函数:对象被创建时自动调用的特殊成员函数,唯一作用是初始化对象的成员属性,确保对象创建后处于合法可用状态。
  • 析构函数:对象被销毁前自动调用的特殊成员函数,用于释放对象占用的动态内存、文件句柄等资源,避免资源泄漏。
  • 编译器行为:若用户未自定义构造 / 析构函数,编译器会自动生成空实现的默认版本;并非强制要求用户提供,仅在需要自定义初始化 / 资源清理时才需手动定义。

生活类比

  • 构造函数:像新买手机的 “出厂激活 + 基础设置”,让手机从 “零件状态” 变成 “可使用状态”。
  • 析构函数:像旧手机报废前的 “数据删除 + 账号注销”,避免资源被滥用或泄露。

必要性

  • 未初始化的对象成员可能是随机值(野值),导致程序逻辑混乱、未定义行为;
  • 未清理的动态内存会造成内存泄漏,长期运行的程序会逐渐耗尽系统资源。

构造函数详解

基本语法与核心性质

class ClassName {
public:
    // 无参构造函数:没有参数
    ClassName(); 
    // 有参构造函数:带参数,支持重载
    ClassName(int param1, string param2); 
    // 拷贝构造函数:特殊的有参构造
    ClassName(const ClassName& obj); 
};
  • 性质
    • 函数名必须与类名完全一致,无返回值(连void都不能写);
    • 支持函数重载(参数个数 / 类型 / 顺序不同即可);
    • 对象创建时自动调用且仅调用一次
    • 若用户定义了任何构造函数(如带参构造),编译器不再生成默认无参构造函数

分类与调用方式

按参数分类

  • 无参构造:创建对象时不传参数ClassName obj;(注意:ClassName obj();是函数声明,不是对象创建!)
  • 有参构造:创建对象时传递参数初始化ClassName obj(10, "test");
  • 拷贝构造:用已存在的对象初始化新对象ClassName obj2 = obj1;

调用方式

  • 括号法:最常用,直观ClassName obj(10);(有参构造)、ClassName obj2(obj);(拷贝构造)
  • 显式法:直接调用构造函数生成临时对象ClassName obj = ClassName(10);(有参构造)、ClassName obj2 = ClassName(obj1);(拷贝构造)
  • 隐式转换法:仅适用于单参数构造函数ClassName obj = 10;(等价于ClassName obj(10);,编译器自动转换)

拷贝构造函数(重点)

核心作用

用一个已存在的对象,“复制” 出一个新对象,实现对象成员的完整初始化;本质是构造函数的重载形式,必须接收类对象的引用作为参数。

基本语法与参数要求

class ClassName {
public:
    // 拷贝构造函数
    /**
     * 拷贝构造函数:用已有对象初始化新对象
     * @param obj 被拷贝的源对象,const保证不修改源对象,引用避免值传递的无限递归
     */
    ClassName(const ClassName& obj) {
        // 将obj的成员变量赋值给当前对象
        this->m_a = obj.m_a;
        this->m_b = obj.m_b;
    }
private:
    int m_a;
    string m_b;
};
  • 参数必须是const ClassName&的原因
    • const:防止函数内部意外修改源对象的属性,保证源对象安全;
    • 引用(&):若用值传递,参数传递时会触发拷贝构造函数的递归调用(值传递需要复制对象,复制又要调用拷贝构造,无限循环)。

调用时机

场景 1:用已存在对象初始化新对象

Cube c1(1,2,3); // 普通构造
Cube c2 = c1;   // 调用拷贝构造
Cube c3(c1);    // 等价于c2,调用拷贝构造

场景 2:值传递方式传递对象参数

/**
 * 测试函数:值传递接收对象
 * @param c 形参是Cube对象,值传递时会复制实参c1,触发拷贝构造
 */
void testFunc(Cube c) {} 

Cube c1(1,2,3);
testFunc(c1); // 实参c1传递给形参c,调用拷贝构造

场景 3:以值方式返回局部对象

/**
 * 创建Cube对象并返回
 * @return Cube类型值,返回时会复制局部对象c,触发拷贝构造(编译器可能优化)
 */
Cube createCube() {
    Cube c(1,2,3); // 局部对象
    return c;      // 返回值时创建临时对象,调用拷贝构造
}

Cube c2 = createCube(); // 接收返回值,可能触发拷贝构造(编译器优化后可能省略)

深浅拷贝问题

浅拷贝(默认拷贝构造的行为)

image

编译器自动生成的拷贝构造函数,会执行逐成员的直接复制—— 如果成员是指针,仅复制指针的 “地址”,而非指针指向的内存内容。

class ShallowCube {
public:
    int* data; // 指针成员,指向堆内存
    /**
     * 普通构造函数:分配堆内存
     * @param d 初始化数据
     */
    ShallowCube(int d) {
        data = new int(d); // 动态分配内存,存储d的值
    }
    /**
     * 析构函数:释放堆内存
     */
    ~ShallowCube() {
        delete data; // 释放data指向的内存
    }
};

ShallowCube a(5); 
ShallowCube b = a; // 浅拷贝:b.data和a.data指向同一块堆内存
// 问题:a和b销毁时,析构函数会两次释放同一块内存,导致程序崩溃!

深拷贝(自定义拷贝构造解决浅拷贝问题)

image

手动为新对象分配独立的内存空间,并复制源对象指针指向的 “内容”,使新对象和源对象拥有独立的资源

class DeepCube {
public:
    int* data;
    /**
     * 普通构造函数:分配堆内存
     * @param d 初始化数据
     */
    DeepCube(int d) {
        data = new int(d);
    }
    /**
     * 深拷贝构造函数:分配独立内存并复制内容
     * @param obj 源对象
     */
    DeepCube(const DeepCube& obj) {
        data = new int(*obj.data); // 新分配内存,复制obj.data指向的值
    }
    /**
     * 析构函数:释放堆内存
     */
    ~DeepCube() {
        delete data;
    }
};

DeepCube a(5);
DeepCube b = a; // 深拷贝:b.data指向新内存,与a.data独立
// 销毁时各自释放自己的内存,无冲突

编译器生成拷贝构造的规则

  • 默认生成条件:若用户未自定义拷贝构造函数,编译器自动生成浅拷贝版本

  • 停止生成条件

    • 若用户自定义了拷贝构造函数,编译器不再生成默认无参构造函数
    • 若用户自定义了任意构造函数(如带参构造),编译器仍会生成默认拷贝构造(浅拷贝);
  • 示例验证

    class Cube1 {
    public:
        Cube1(int a) {} // 用户定义带参构造
    };
    Cube1 c1(10);
    Cube1 c2 = c1; // 合法:编译器生成默认拷贝构造(浅拷贝)
    
    class Cube2 {
    public:
        Cube2(const Cube2& obj) {} // 用户定义拷贝构造
    };
    Cube2 c3; // 错误:编译器不生成默认无参构造,必须自定义无参构造
    

注意事项与最佳实践

  • 必须自定义拷贝构造的场景:类成员包含指针、动态分配内存(如new)、文件句柄、网络连接等 “独占资源” 时,必须实现深拷贝;

  • 禁用拷贝构造的场景:类管理的资源不允许复制(如单例类),可将拷贝构造声明为delete

    class NonCopyable {
    public:
        NonCopyable(const NonCopyable&) = delete; // 禁用拷贝构造
    };
    
  • 性能优化:传递大对象时,用const 类名&(常量引用)代替值传递,避免拷贝构造的开销:

    void func(const Cube& c); // 引用传递,不触发拷贝构造
    

析构函数

基本语法与核心性质

class ClassName {
public:
    /**
     * 析构函数:对象销毁时自动调用
     * 无参数、无返回值、不可重载
     */
    ~ClassName(); 
};
  • 性质
    • 函数名以~开头,与类名一致,无参数、无返回值;
    • 不可重载(一个类只能有一个析构函数);
    • 对象销毁时自动调用,负责清理资源(如delete动态内存、关闭文件)。

调用时机

  • 局部对象:离开作用域时(如函数执行完毕、代码块结束);
  • 动态对象:调用delete时(new创建的对象,需手动delete触发析构);
  • 全局对象:程序运行结束时;
  • 静态对象:所在作用域结束时(如函数内的static对象,函数执行完不销毁,程序结束时销毁);
  • 临时对象:表达式执行完毕时(如ClassName(10)生成的临时对象)。

初始化列表

语法与核心用途

class ClassName {
private:
    int m_var1;
    const int m_var2; // 常量成员
    string& m_var3;   // 引用成员
public:
    /**
     * 构造函数:用初始化列表初始化成员
     * @param v1 初始化m_var1
     * @param v2 初始化m_var2(常量必须初始化)
     * @param v3 初始化m_var3(引用必须初始化)
     */
    ClassName(int v1, int v2, string& v3) 
        : m_var1(v1), m_var2(v2), m_var3(v3) {}
};
  • 核心用途
    • 初始化常量成员const成员只能初始化,不能赋值);
    • 初始化引用成员(引用必须在声明时绑定对象,不能后期赋值);
    • 提升效率:直接初始化成员,避免 “先默认构造成员再赋值” 的额外开销;
    • 初始化基类成员(继承中常用)。

关键注意点

  • 成员初始化的顺序由声明顺序决定,与初始化列表的顺序无关:

    class Test {
    private:
        int a;
        int b;
    public:
        Test() : b(10), a(b) {} // 实际先初始化a(声明在前),a的值是随机的(b未初始化)
    };
    
  • 对于自定义类型成员(如string),初始化列表直接调用其构造函数,比函数体内赋值更高效。

类对象作为类成员(成员对象)

构造与析构顺序

  • 构造顺序:先调用成员对象的构造函数(按成员声明顺序),再调用外部类的构造函数;
  • 析构顺序:与构造顺序相反 —— 先调用外部类的析构函数,再调用成员对象的析构函数。

示例验证

class A {
public:
    A() { cout << "A的构造函数" << endl; }
    ~A() { cout << "A的析构函数" << endl; }
};

class B {
private:
    A a; // 成员对象(类A的对象)
public:
    B() { cout << "B的构造函数" << endl; }
    ~B() { cout << "B的析构函数" << endl; }
};

int main() {
    B b; // 创建B对象
    return 0;
}

输出结果:A 的构造函数B 的构造函数B 的析构函数A 的析构函数

说明

成员对象的构造函数参数,可通过外部类的初始化列表传递:

class A {
public:
    A(int x) { cout << "A的带参构造:" << x << endl; }
};

class B {
private:
    A a; // 成员对象
public:
    // 通过初始化列表给A的构造函数传参
    B() : a(10) { cout << "B的构造函数" << endl; }
};
posted @ 2025-12-03 22:09  YouEmbedded  阅读(11)  评论(0)    收藏  举报