1 构造函数
1.1 特性补充
-
无参构造和全缺省参数构造构成重载,但是会存在调用歧义,不调用不会报错
-
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成(对内置类型而言初始化为未定义值,因为C++标准没有规定,取决于各编译器实现)
1.2 默认构造函数
-
无参构造函数
-
全缺省构造函数
-
我们没写编译器自动生成的构造函数
总结:不传参数就可以调用的函数叫做默认构造函数
三个只能存在一个
没有默认构造,就会报错
实践当中该初始化还是得初始化
C++11的补丁
【总结】
-
一般情况下构造函数都需要我们显式地去实现
-
只有少数情况下可以让编译器自动生成构造函数
例:MyQueue,成员大多是自定义类型Stack,内置类型可以给缺省值
2 析构函数
2.1 特性补充
无参数无返回值类型
一个类只能有一个析构函数,析构函数不能重载
【Q】所有类都需要写析构函数吗?
【A】不是,比如日期类,没有资源需要清理
2.2 编译器自动生成的析构函数
特性和构造函数类似:内置类型不做处理,自定义类型去调用对应析构
【实践总结】
-
有资源需要显式清理,就需要写析构,如:Stack, List...
-
有两种场景不需要写析构,默认生成就可以
-
没有资源需要清理,如:Date
-
内置类型成员没有资源需要清理,剩下都是自定义类型成员,如:MyQueue
-
3 拷贝构造
3.1 示例
3.2 自定义类型传值、指针、引用传参
语法逻辑上下面会造成无穷递归(实际上编译器会强制检查),所以拷贝的参数必须用引用
3.3 const
关键字的引入
3.3.1 场景引入
构造函数内赋值左右顺序不慎写反:
运行结果:
3.3.2 加入const
定义拷贝构造函数时,如果参数前加上 const
,这意味着承诺不会修改传入的对象
3.4 等价形式
3.5 浅拷贝
未显式定义Date类的拷贝构造函数,编译器自动生成的默认构造函数也可以完成,且不会报错
但对于Stack类而言,需要显式地写拷贝构造,否则默认生成的只会进行浅拷贝,即复制对象时只复制指向数据的指针,而不复制指针所指向的实际数据。这意味着原始对象和拷贝对象共享同一份数据
3.5.1 浅拷贝带来的问题
-
数据共享:
-
如果栈中的元素是指针,则浅拷贝会导致两个栈共享同一份数据。当其中一个栈删除元素时,另一个栈中的相应元素也会受到影响,因为它们指向相同的内存位置。
-
-
资源泄露:
-
如果栈中的元素是指针,并且这些指针指向动态分配的内存,那么浅拷贝会导致这些内存被多个对象共享。当其中一个对象销毁时,如果释放了内存,而其他对象仍然持有指向该内存的指针,则会导致悬挂指针或资源泄露。
-
-
双删问题:
-
如果栈中的元素指向动态分配的内存,那么浅拷贝会导致多个对象持有指向同一块内存的指针。当其中一个对象删除该内存时,其他对象再尝试删除这块内存就会导致双删问题,这通常会导致程序崩溃。
-
-
破坏封装性:
-
浅拷贝可能会破坏封装性,因为对象之间的状态变得相互依赖。这使得对象更难维护和测试。
-
-
异常安全问题:
-
如果在深拷贝过程中发生异常(例如内存不足),浅拷贝可能会留下不完整或无效的对象状态。
-
3.5.2 解决办法(深拷贝)
为了防止这些问题,通常需要实现深拷贝。对于栈中的每个元素,如果元素是指针,那么需要复制指针所指向的数据,而不是仅仅复制指针本身。例如,如果栈中存储的是指向字符串的指针,那么应该创建新的字符串副本,并更新指针指向新副本
【总结】
-
如果没有管理资源,一般情况不需要写拷贝构造,默认生成的拷贝构造就可以。如:Date
-
如果都是自定义类型成员,内置类型成员没有指向资源,默认生成的拷贝构造就可以。如:MyQueue
-
一般情况下,不需要显式写析构函数,就不需要写拷贝构造
-
如果内部有指针或者一些值指向资源,需要显式写析构释放,通常就需要显式写构造完成深拷贝。如:Stack Queue list等