目录

一、什么是拷贝构造函数?

标准形式

二、拷贝构造函数的特点

是构造函数的重载

必须使用引用参数(不能传值!)

为什么错误?

调用场景非常多

这些情况都会调用拷贝构造:

编译器会自动生成拷贝构造

 默认行为:浅拷贝(逐字节复制)

三、浅拷贝 vs 深拷贝

 Date类(浅拷贝可以)

Stack类(必须深拷贝)

问题:默认拷贝是浅拷贝

正确做法:深拷贝

核心思想:

四、拷贝构造与赋值运算符区别

五、返回值与拷贝构造

传值返回

引用返回(避免拷贝)

六、拷贝构造与对象组合


在 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不用显示写拷贝构造,就完成了拷贝构造


更多推荐