一、拷贝构造函数
1.定义
是特殊的构造函数,用一个已存在的对象来初始化另一个新对象。只拷贝数据成员,函数成员是共有的。
class ClassName {
public:
// 拷贝构造函数
ClassName(const ClassName& other) {
// 成员变量的拷贝操作
}
};
注意点:
(1)参数类型为const 类名&
①const的使用是为了防止修改被拷贝的对象
②&(引用)的使用是防止无限递归调用,如果把一个真实的类对象作为参数传递到拷贝构造函数,会无穷递归。
(2)当没有自定义拷贝构造函数时,编译器会自动生成一个默认的版本。默认拷贝构造函数会采用浅拷贝的方式,即逐个复制对象的成员变量。如果需要管理动态资源,需要手动处理深拷贝。
2.构造函数和拷贝构造函数的区别
①构造函数的参数列表可以是任意参数,而拷贝构造函数的参数是同类对象的引用。
②构造函数在创建对象时调用,拷贝构造函数在用已有对象初始化新对象时调用。
3.拷贝构造函数的使用场景
(1)用于拿一个对象初始化另一个对象
(2)函数的形参是类的对象
当对象作为值参数传递给函数时,会创建实参的副本,此时调用拷贝构造函数。
(3)函数的返回值是类对象
当函数返回对象时,会创建返回值的临时副本(亡值对象),此时调用拷贝构造函数。
亡值对象只存在函数调用中,函数结束亡值销毁。
注意:
①如果函数结束,但对象生存周期还未到,则可以使用引用或者指针返回。
②引用返回的场景:函数结束但变量仍然可以存在。
4.拷贝构造函数和赋值语句的重载
(1)拷贝构造函数的重载
用于创建一个新的对象,并用传入对象的值进行初始化。
(2)赋值语句的重载
用于将一个对象的值赋给另一个对象。
(3)赋值语句的重载与拷贝构造函数的区别
赋值语句重载需要处理自赋值的情况,并返回对象的引用以支持链式赋值。
二、对象的生存期
1.动态创建对象
使用new操作符在堆上创建对象,并使用delete操作符释放对
注意:new
和 delete
必须配对。
①使用 new
分配的内存必须用 delete
释放。
②使用 new[]
分配的内存必须用 delete[]
释放。
2.局部对象
局部对象的生存期由函数调用控制。从定义处开始,到作用域结束时自动销毁。
3.全局对象的生存期
全局对象的生存期由程序运行控制。程序启动时创建,程序结束时销毁。
三、内存泄漏和delete操作符的使用
1.内存泄漏
程序在动态分配内存后未正确释放,导致系统内存逐渐减少。
2.delete操作符的使用
用于释放动态分配的内存空间。
3.delete操作符的工作原理
调用对象的析构函数,并调用free释放堆区内存空间。
4.注意事项
delete操作符后应将指针置为空,避免悬挂指针。
四、内存分配的协议
1.new操作符的协议
当使用 new[]
分配一个包含析构函数的对象数组时,C++ 运行时通常会在实际对象数据前多分配 4 或 8 字节(取决于平台),用于存储以下信息:
(1)数组长度:记录数组中对象的数量,以便 delete[]
知道需要调用多少次析构函数。
(2)额外元数据:可能包括内存块大小、分配标志等。
注意:
仅对需要析构的对象有效,如果类没有自定义析构函数(且成员也不需要析构),编译器可能优化掉头部。
2.delete操作符的协议
当调用 delete[]
时,运行时会
(1)读取头部数据:获取数组长度。
(2)调用析构函数:按顺序为每个对象调用析构函数(次数 = 头部记录的长度)。
(3)释放内存:使用free将整个内存块(包括头部)归还给系统。
如图所示:(在visualStudio平台下,多分配4个字节)
3.new和malloc的区别
特性 | new /delete |
malloc /free |
---|---|---|
构造 / 析构函数 | 自动调用构造函数和析构函数 | 不调用任何构造 / 析构函数 |
内存分配协议 | 可能多分配头部存储元数据(如数组长度) | 仅分配请求的大小,无额外头部 |
类型安全 | 返回正确的指针类型 | 返回 void* ,需要手动转换类型 |
异常处理 | 分配失败时抛出 std::bad_alloc |
分配失败时返回 NULL |
适用场景 | 面向对象编程(创建对象) | 面向过程编程(分配原始内存) |
总结:
new[]
的协议:为需要析构的对象数组多分配头部,存储数组长度。
delete[]
的协议:读取头部长度,调用对应次数的析构函数,释放整块内存。与
malloc
的本质区别:new
是面向对象的内存分配,自动管理对象生命周期;
malloc
是低级内存分配,只处理原始字节。
五、常量对象和非常量对象
1.const与类型结合
表示该类型的变量是一个常变量
如图所示:
2.常量对象的定义
使用const关键字修饰对象,初始化后不可修改其状态,但可以被引用。
注意:必须在定义时初始化,且之后不能被赋值。
3.区别
特性 | 常量对象 | 非常量对象 |
---|---|---|
状态修改 | 禁止修改 | 允许修改 |
可调用函数 | 仅常量成员函数 | 所有成员函数 |
mutable 成员 | 可修改 | 可修改 |
典型场景 | 只读数据、多线程共享对象 | 需动态修改的对象 |
ps:在C++中,合理的使用const可以提高代码的安全性和可读性!!!