拷贝构造函数和赋值构造函数

本文详细解释了浅拷贝和深拷贝的概念及其在C++中的实现方式,展示了拷贝构造函数与赋值构造函数的区别,并提供了代码示例。

我们都知道拷贝构造函数和赋值构造函数最大的不同在于:

 

拷贝构造是确确实实构造一个新的对象,并给新对象的私有成员赋上参数对象的私有成员的值,新构造的对象和参数对象地址是不一样的,所以如果该类中有一个私有成员是指向堆中某一块内存,如果仅仅对该私有成员进行浅拷贝,那么会出现多个指针指向堆中同一块内存,这是会出现问题,如果那块内存被释放了,就会出现其他指针指向一块被释放的内存,出现未定义的值的问题,如果深拷贝,就不会出现问题,因为深拷贝,不会出现指向堆中同一块内存的问题,因为每一次拷贝,都会开辟新的内存供对象存放其值。

下面是浅拷贝构造函数的代码:

复制代码
#include <iostream>

using namespace std;

class A
{
private:
    int* n;

public:
    A()
    {
        n = new int[10];
        n[0] = 1;
        cout<<"constructor is called\n";
    }

    A(const A& a)
    {
        n = a.n;
        cout<<"copy constructor is called\n";
    }

    ~A()
    {
        cout<<"destructor is called\n";
        delete n;
    }

    void get()
    {
        cout<<"n[0]: "<<n[0]<<endl;
    }
};


int main()
{
    A* a = new A();
    A b = *a;
    delete a;

    b.get();

    return 0;
}
复制代码

运行结果如下:

 

下面是深拷贝构造函数的代码:

复制代码
#include <iostream>
#include <string.h>
using namespace std;

class A
{
private:
    int* n;

public:
    A()
    {
        n = new int[10];
        n[0] = 1;
        cout<<"constructor is called\n";
    }

    A(const A& a)
    {
        n = new int[10];
        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存
        cout<<"copy constructor is called\n";
    }

    ~A()
    {
        cout<<"destructor is called\n";
        delete n;
    }

    void get()
    {
        cout<<"n[0]: "<<n[0]<<endl;
    }
};


int main()
{
    A* a = new A();
    A b = *a;
    delete a;

    b.get();

    return 0;
}
复制代码

运行截图如下:

 

 

但是赋值构造函数是将一个参数对象中私有成员赋给一个已经在内存中占据内存的对象的私有成员,赋值构造函数被赋值的对象必须已经在内存中,否则调用的将是拷贝构造函数,当然赋值构造函数也有深拷贝和浅拷贝的问题。当然赋值构造函数必须能够处理自我赋值的问题,因为自我赋值会出现指针指向一个已经释放的内存。还有赋值构造函数必须注意它的函数原型,参数必须是引用类型,返回值也必须是引用类型,否则在传参和返回的时候都会再次调用一次拷贝构造函数。

 

复制代码
#include <iostream>
#include <string.h>
using namespace std;

class A
{
private:
    int* n;

public:
    A()
    {
        n = new int[10];
        n[0] = 1;
        cout<<"constructor is called\n";
    }

    A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归
    {
        n = new int[10];
        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存
        cout<<"copy constructor is called\n";
    }

    A& operator=(const A& a)  //记住形参和返回值一定要是引用类型,否则传参和返回时会自动调用拷贝构造函数
    {
        if(this == &a)     //为什么需要进行自我赋值判断呢?因为下面要进行释放n的操作,如果是自我赋值,而没有进行判断的话,那么就会出现讲一个释放了的内存赋给一个指针
            return *this;
        if(n != NULL)
        {
            delete n;
            n == NULL;   //记住释放完内存将指针赋为NULL
        }

        n = new int[10];
        memcpy(n, a.n, 10);
        cout<<"assign constructor is called\n";
        return *this;
    }

    ~A()
    {
        cout<<"destructor is called\n";
        delete n;
     n = NULL; //记住释放完内存将指针赋为NULL }
void get() { cout<<"n[0]: "<<n[0]<<endl; } }; int main() { A* a = new A(); A* b = new A(); *b = *a; delete a; b->get(); return 0; }
复制代码

运行截图如下:

如果我们在赋值构造函数的形参和返回值不用引用类型,代码如下:

复制代码
#include <iostream>
#include <string.h>
using namespace std;

class A
{
private:
    int* n;

public:
    A()
    {
        n = new int[10];
        n[0] = 1;
        cout<<"constructor is called\n";
    }

    A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归
    {
        n = new int[10];
        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存
        cout<<"copy constructor is called\n";
    }

    A operator=(const A a)   //传参和返回值设置错误
    {
        if(this == &a)
            return *this;
        if(n != NULL)
        {
            delete n;
            n == NULL;
        }

        n = new int[10];
        memcpy(n, a.n, 10);
        cout<<"assign constructor is called\n";
        return *this;
    }

    ~A()
    {
        cout<<"destructor is called\n";
        delete n;
    }

    void get()
    {
        cout<<"n[0]: "<<n[0]<<endl;
    }
};


int main()
{
    A* a = new A();
    A* b =  new A();

    *b = *a;

    delete a;

    b->get();

    while(1)
    {}

    return 0;
}
复制代码

运行截图如下:

 

多了两次的拷贝构造函数的调用和两次析构函数的调用。

### 拷贝构造函数赋值构造函数的区别及适用场景 #### 定义与基本区别 拷贝构造函数赋值运算符(通常称为赋值构造函数)是 C++ 中用于对象初始化赋值的两种机制。它们的主要区别在于使用场景行为。 - **拷贝构造函数**:当一个对象通过另一个已存在的对象进行初始化时,调用拷贝构造函数。它通常用于创建新对象并将其初始化为现有对象的副本[^2]。 - **赋值运算符**:当一个已存在的对象被赋予另一个对象的值时,调用赋值运算符。它用于将一个对象的内容复制到另一个已存在的对象中[^2]。 #### 使用场景 - **拷贝构造函数**: - 当通过一个对象初始化另一个对象时,例如 `MyClass b = a;`。 - 当函数参数按值传递时,例如 `void f(MyClass n)`[^1]。 - 当函数返回一个对象时,例如 `Complex Complex::add(Complex c)`[^1]。 - **赋值运算符**: - 当一个已存在的对象被赋予另一个对象的值时,例如 `c = a.add(b);`[^1]。 - 在动态分配对象或修改已有对象内容时,例如 `MyClass* p = new MyClass(); *p = a;`[^2]。 #### 示例代码分析 以下是一个示例,展示拷贝构造函数赋值运算符的不同行为: ```cpp #include <iostream> using namespace std; class MyClass { public: int x; int y; // 默认构造函数 MyClass() { x = 0; y = 0; } // 带参数的构造函数 MyClass(int a, int b) { x = a; y = b; } // 拷贝构造函数 MyClass(const MyClass& other) { x = other.x; y = other.y; cout << "拷贝构造函数调用" << endl; } // 赋值运算符重载 MyClass& operator=(const MyClass& other) { if (this == &other) { return *this; } x = other.x; y = other.y; cout << "拷贝赋值运算符调用" << endl; return *this; } }; int main() { MyClass a(5, 6); // 调用带参数构造函数 MyClass b = a; // 调用拷贝构造函数[^2] MyClass c; // 调用默认构造函数 c = a; // 调用拷贝赋值运算符 return 0; } ``` #### 输出结果分析 上述代码的输出结果为: ``` 拷贝构造函数调用 拷贝赋值运算符调用 ``` - 第一行输出表明在 `MyClass b = a;` 中调用了拷贝构造函数。 - 第二行输出表明在 `c = a;` 中调用了赋值运算符。 #### 深拷贝与浅拷贝 如果类中包含指针或其他需要动态分配资源的成员,则需要特别注意深拷贝拷贝的问题[^3]。默认的拷贝构造函数赋值运算符仅执行浅拷贝,可能导致多个对象共享同一资源,从而引发未定义行为。在这种情况下,必须显式实现深拷贝逻辑。 #### 编译器生成的默认实现 如果未显式定义拷贝构造函数赋值运算符,编译器会自动生成默认实现。默认实现按字节顺序逐个复制成员变量,适用于所有成员均为内置类型且无动态资源的情况[^4]。然而,对于包含动态分配资源的类,这种默认实现可能不适用,需要手动实现深拷贝逻辑。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值