Day 6:拷贝构造、深浅拷贝和类型转换
今天目标:理解对象复制时会发生什么,掌握拷贝构造函数、深拷贝与浅拷贝、delete/default,并认识 C++ 的四种显式类型转换。
对应代码:
2-代码/day6-/29-拷贝构造函数示例.cpp
2-代码/day6-/30-拷贝构造函数示例2.cpp
2-代码/day6-/31-delete与default示例.cpp
2-代码/day6-/32-显示类型转换示例.cpp
1、拷贝构造函数
拷贝构造函数用于用一个已经存在的对象,初始化另一个同类型的新对象。
基本格式:
class Demo
{
public:
Demo(const Demo& rhs);
};
说明:
Demo(const Demo& rhs)
表示参数是本类类型的 const 引用,这种构造函数就是拷贝构造函数。
示例:
class Demo
{
public:
Demo(int x, int y) : _x(x), _y(y) {}
Demo(const Demo& rhs)
{
_x = rhs._x;
_y = rhs._y;
cout << "Demo(const Demo&)" << endl;
}
private:
int _x;
int _y;
};
如果没有自己写拷贝构造函数,编译器会自动生成一个,默认按成员变量逐个复制。
2、拷贝构造函数的调用时机
常见调用场景有三种。
2.1、用已有对象创建新对象
Demo d1(100, 200);
Demo d2(d1);
Demo d3{d1};
Demo d4 = d1;
这些都是用 d1 初始化一个新的 Demo 对象,会调用拷贝构造函数。
2.2、对象作为函数参数按值传递
void foo(Demo d)
{
}
Demo d1(100, 200);
foo(d1);
调用 foo(d1) 时,形参 d 是一个新对象,需要用 d1 拷贝初始化。
2.3、函数按值返回对象
Demo foo()
{
Demo d(1, 1);
return d;
}
返回对象时,可能调用拷贝构造函数。
注意:
现代编译器可能会进行返回值优化,省略某些拷贝构造函数调用。
所以实际运行时,不一定每次都能看到拷贝构造函数输出。
3、浅拷贝和深拷贝
3.1、浅拷贝
浅拷贝就是简单复制成员变量的值。
如果成员变量是普通类型,例如 int、double,浅拷贝通常没有问题。
但如果成员变量是指针,浅拷贝只会复制指针地址:
两个对象的指针成员指向同一块堆内存。
这样会产生问题:
1. 修改一个对象的数据,可能影响另一个对象。
2. 两个对象析构时,会对同一块内存 delete 两次。
3. 程序可能崩溃。
3.2、深拷贝
深拷贝是在拷贝对象时,重新申请一块新的内存,再把原对象中的数据复制过去。
示例:
class Demo
{
public:
Demo(int x, int y)
{
_x = new int(x);
_y = new int(y);
}
~Demo()
{
delete _x;
delete _y;
}
Demo(const Demo& rhs)
{
_x = new int(*rhs._x);
_y = new int(*rhs._y);
}
private:
int* _x;
int* _y;
};
这样拷贝后:
d1._x 和 d2._x 指向不同的内存。
d1._y 和 d2._y 指向不同的内存。
两个对象互不影响,析构时也不会重复释放同一块内存。
一句话理解:
浅拷贝:只复制地址。
深拷贝:重新申请内存,再复制内容。
4、delete 和 default 关键字
这里的 delete 和释放堆内存的 delete p; 不是同一种用法。
4.1、= delete
= delete 用来删除某些函数,禁止别人调用。
常见用途:禁止对象拷贝。
class Demo
{
public:
Demo() = default;
Demo(const Demo&) = delete;
};
int main()
{
Demo d1;
// Demo d2 = d1; // 错误,拷贝构造函数被删除
}
适合场景:
对象管理独占资源,不希望被复制。
例如文件描述符、锁、某些硬件资源等。
4.2、= default
= default 表示要求编译器生成默认版本的函数。
class Demo
{
public:
Demo() = default;
};
常见用途:
1. 明确保留默认构造函数。
2. 让代码意图更清楚。
3. 配合 = delete 控制类能做什么、不能做什么。
小结:
= delete 禁止编译器生成或禁止调用某个函数。
= default 要求编译器生成默认版本的函数。
5、C++ 显式类型转换
C 语言风格转换:
int a = (int)3.14;
C++ 更推荐使用下面四种显式类型转换,因为语义更清楚。
6、static_cast
static_cast 用于比较正常、风险较低的类型转换。
常见用途:
1. 基础类型之间转换。
2. void* 和其他类型指针之间转换。
3. 有继承关系的类指针之间转换。
示例:
double d = 3.14;
int a = static_cast<int>(d);
说明:
3.14 转成 int 后,小数部分会被丢掉,结果是 3。
7、reinterpret_cast
reinterpret_cast 用于非常底层、风险较高的强制解释。
常见用途:
1. 不同类型指针之间强转。
2. 整数和指针之间强转。
示例:
double d = 3.14;
int* p = reinterpret_cast<int*>(&d);
注意:
reinterpret_cast 只是换一种类型去解释同一块内存。
它不保证转换后的结果合理,也不保证访问是安全的。
普通业务代码中应尽量少用。
8、const_cast
const_cast 用来去掉指针或引用上的 const 属性。
示例:
int a = 100;
const int* p = &a;
int* q = const_cast<int*>(p);
*q = 200;
这种情况中,a 本身不是常量,只是通过 const int* 不能修改,所以去掉 const 后修改通常可以。
但如果原对象本身就是 const,再强行修改就是未定义行为:
const int a = 100;
const int* p = &a;
int* q = const_cast<int*>(p);
*q = 200; // 未定义行为
注意:
const_cast 只能去掉访问路径上的 const。
如果对象本身真的是 const,不应该修改它。
9、dynamic_cast
dynamic_cast 主要用于多态类型之间的安全向下转换。
一般用于继承和虚函数相关场景:
Base* p = new Derived;
Derived* q = dynamic_cast<Derived*>(p);
当前阶段先记住:
dynamic_cast 常用于有继承关系、有虚函数的类之间转换。
后面学习继承和多态时再重点掌握。
10、Day6 总结
1. 拷贝构造函数用于用已有对象初始化新对象。
2. 拷贝构造函数参数通常写成 const 类名&。
3. 对象按值传参、按值返回、用已有对象创建新对象时,可能调用拷贝构造。
4. 类中有指针成员并管理堆内存时,要注意深拷贝。
5. 浅拷贝只复制地址,深拷贝会重新申请内存并复制内容。
6. = delete 可以禁止某些函数被调用,例如禁止拷贝。
7. = default 可以要求编译器生成默认版本的函数。
8. static_cast 用于常规转换。
9. reinterpret_cast 用于底层重新解释,风险较高。
10. const_cast 用于去掉 const,但不能修改真正的 const 对象。
11. dynamic_cast 用于多态类型转换,后面继承多态再深入。
一句话记忆:
Day6 的重点是对象复制:有资源就想深拷贝,不想被复制就 delete;类型转换要选语义明确的 C++ 写法。






Comments | NOTHING