构造与析构基础概念
核心定义
- 构造函数:对象被创建时自动调用的特殊成员函数,唯一作用是初始化对象的成员属性,确保对象创建后处于合法可用状态。
- 析构函数:对象被销毁前自动调用的特殊成员函数,用于释放对象占用的动态内存、文件句柄等资源,避免资源泄漏。
- 编译器行为:若用户未自定义构造 / 析构函数,编译器会自动生成空实现的默认版本;并非强制要求用户提供,仅在需要自定义初始化 / 资源清理时才需手动定义。
生活类比
- 构造函数:像新买手机的 “出厂激活 + 基础设置”,让手机从 “零件状态” 变成 “可使用状态”。
- 析构函数:像旧手机报废前的 “数据删除 + 账号注销”,避免资源被滥用或泄露。
必要性
- 未初始化的对象成员可能是随机值(野值),导致程序逻辑混乱、未定义行为;
- 未清理的动态内存会造成内存泄漏,长期运行的程序会逐渐耗尽系统资源。
构造函数详解
基本语法与核心性质
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(); // 接收返回值,可能触发拷贝构造(编译器优化后可能省略)
深浅拷贝问题
浅拷贝(默认拷贝构造的行为)

编译器自动生成的拷贝构造函数,会执行逐成员的直接复制—— 如果成员是指针,仅复制指针的 “地址”,而非指针指向的内存内容。
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销毁时,析构函数会两次释放同一块内存,导致程序崩溃!
深拷贝(自定义拷贝构造解决浅拷贝问题)

手动为新对象分配独立的内存空间,并复制源对象指针指向的 “内容”,使新对象和源对象拥有独立的资源。
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; }
};