C++拷贝构造函数详解(深度解析)
目录
在 C++ 中,拷贝构造函数(Copy Constructor) 是一个非常重要的特殊构造函数,它决定了对象在“复制”时的行为,尤其在涉及指针和资源管理时,直接影响程序是否安全稳定。
一、什么是拷贝构造函数?
如果一个构造函数满足以下条件:
-
第一个参数是自身类类型的引用
-
后面参数都有默认值(可选)
那么它就是拷贝构造函数。
标准形式
class Date {
public:
Date(const Date& d) {
// 拷贝成员
}
};
二、拷贝构造函数的特点
是构造函数的重载
拷贝构造本质仍然是构造函数,只不过用途是:
用一个已有对象初始化另一个对象
例如:
Date d2(d1); // 拷贝构造
Date d3 = d1; // 拷贝构造
必须使用引用参数(不能传值!)
错误写法:
Date(Date d) // 会无限递归
为什么错误?
这里要讲一点:C++规定,函数传值传参要调用拷贝构造,c语言没有这个概念(因为没有类和对象这个概念)
假如我要调用一个函数
void Func1(Date d)
{
cout << &d << endl;
d.Print();
}
int main()
{
Date d1(2024, 7, 12);
d1.Print();
// C++的规定,传值传参要调用拷贝构造
Func1(d1);
}
当d1传入后,不会立马调func1,会先进入拷贝构造(下图1,2步骤),生成一个d1的临时对象来代替func1的d形参(3,4步骤),然后才调用func1(步骤5)

而如果是拷贝构造

d1传给d2,调用拷贝构造,而调用拷贝构造之前,要传参给形参d,c++规定,传值传参要调用拷贝构造,那么语法上d1传参就形成了一个新的拷贝构造,也就是图中红色的Date d(d1),如果完成了红色的Date d(d1),那么d1就会完成对d2的拷贝构造。
但是朋友们,这不可能啊,红色的Date d(d1)是完不成的啊,它又会继续进行拷贝构造,又去重复以上操作,我们熟说的套娃,那么就开始了无尽的拷贝构造,递归回不来了,就是无穷递归,最后程序崩溃
因此c++这里就规定必须传引用,这里d就是d1的别名,就不需要来构造一个跟d1一样的d来完成构造,直接用d1的数据来完成就可以了
Date(Date& d)//传引用
此外,来看一个场景
Date( Date& d)
{
if(d._year = _year)
{
//一些逻辑
}
}
Date d2(d1);
本来我要把d1拷贝构造给d2,但是写反了,而且编译器还检查不出来,这就非常的扯蛋了
所以c++中又喜欢给Data前加一个const,来保证d不被改变
Date(const Date& d)
其实很多场景下d基本都是不被改变的,在不希望d被改变的情况下,我们都加const
那么在上面的调用func函数下,我们也可以传引用,因为这样就减少了拷贝,不改变d的情况下也加const(自定义类型都可以传引用,内置类型影响不大)
调用场景非常多
这些情况都会调用拷贝构造:
-
用一个对象初始化另一个对象
-
函数传值参数
-
函数传值返回
void func(Date d); // 传值 → 拷贝构造
Date func() {
Date tmp;
return tmp; // 返回时可能拷贝
}
编译器会自动生成拷贝构造
如果你不写,编译器会生成默认版本:
默认行为:浅拷贝(逐字节复制)
Date d2 = d1; // 编译器自动生成
特点:
-
内置类型:直接复制
-
自定义类型:调用其拷贝构造
三、浅拷贝 vs 深拷贝
Date类(浅拷贝可以)
class Date {
int _year;
int _month;
int _day;
};
编译器默认拷贝就足够,因为:
-
没有指针资源
-
不涉及动态内存
默认拷贝构造可用
Stack类(必须深拷贝)
class Stack {
int* _a;
size_t _capacity;
size_t _top;
};
问题:默认拷贝是浅拷贝
Stack st2 = st1;
结果:
st1._a ─────┐
├──> 同一块堆空间
st2._a ─────┘
那么析构时,st1析构了这块空间后,st2又去析构这块空间,程序析构2次,就会报错崩溃
正确做法:深拷贝
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
核心思想:
重新开空间 + 复制内容
四、拷贝构造与赋值运算符区别
| 类型 | 作用 |
|---|---|
| 拷贝构造 | 新对象 = 已有对象 |
| operator= | 已有对象 = 已有对象 |
Date d3 = d1; // 拷贝构造
d3 = d2; // 赋值重载
五、返回值与拷贝构造
传值返回
Date func() {
Date tmp;
return tmp;
}
这里返回时会返回临时对象,c++规定,返回临时对象也会调用拷贝构造
引用返回(避免拷贝)
Date& func()
{
static Date tmp;
return tmp;
}
优点:减少拷贝
注意:不能返回局部变量引用(会变野引用)
Date& func()
{
Date tmp;
return tmp;
}
tmp是一个临时创建的对象,当函数调用结束后,tmp就会被销毁,而我们返回了一个引用,该引用就会指向一个被销毁的变量(野引用)
六、拷贝构造与对象组合
class MyQueue {
private:
Stack pushst;
Stack popst;
};
在这里MyQueue没有写拷贝构造函数,那么就会调用 默认拷贝构造,Stack是自定义类型,那么就会调用Stack的拷贝构造,前提是Stack 必须正确实现深拷贝,那么MyQueue不用显示写拷贝构造,就完成了拷贝构造
更多推荐
所有评论(0)