【C++核心编程】拷贝构造&赋值运算符&深拷贝浅拷贝

 🔥博客主页: 我要成为C++领域大神

🎥系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】

❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于分享知识,欢迎大家共同学习和交流。

拷贝构造

在C++中,用一个已存在的对象创建新的对象时,不会调用普通构造函数,而是调用拷贝构造函数。如果类中没有定义拷贝构造函数,编译器将提供一个默认的拷贝构造函数,其功能是将已存在对象的成员变量赋值给新对象的成员变量。

用已存在的对象创建新对象的语法

类名 新对象名(已存在的对象名);
类名 新对象名 = 已存在的对象名;

拷贝构造函数的定义

类名(const 类名& 对象名) { ... }

注意事项

  • 访问权限:拷贝构造函数必须是 public。
  • 函数名:函数名必须与类名相同。
  • 返回值:没有返回值,不写 void。
  • 默认提供:如果类中定义了拷贝构造函数,编译器将不提供默认的拷贝构造函数。
  • 值传递调用:以值传递方式调用函数时,如果实参为对象,会调用拷贝构造函数。
  • 值返回对象:函数以值的方式返回对象时,可能会调用拷贝构造函数(VS会调用,Linux不会,g++编译器做了优化)。
  • 重载:拷贝构造函数可以重载,可以有默认参数。

拷贝构造的参数必须是对象的引用,const条件不是严格必须的,但是加上最好。

为什么函数参数一定要是引用呢?

如果拷贝构造函数中的参数不是一个引用,即形如CTest(const CTest tst),那么就相当于采用了值传递的方式,而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。

注意:不是为了减少一次内存拷贝,而是为了防止无限制的内存拷贝

示例:

#include<iostream>
using namespace std;
class CTest {
public:
int m_age;
string m_name;
//没有重载的普通构造函数
CTest() {
    m_name.clear();
    m_age = 0;
    cout << "调用了普通构造函数" << endl;
}
// 没有重载的拷贝构造函数(默认拷贝构造函数)。  
CTest(const CTest& tst) { m_name = "漂亮的" + tst.m_name; m_age = tst.m_age - 1;  cout << "调用了CTest(const CTest &tst)拷贝构造函数。\n"; }

// 重载的拷贝构造函数。  
CTest(const CTest& tst, int ii) { m_name = "漂亮的" + tst.m_name; m_age = tst.m_age - ii;  cout << "调用了CTest(const CTest &tst,int ii)拷贝构造函数。\n"; }

// 析构函数。  
~CTest() { cout << "调用了~CTest()\n"; }

// 显示姓名和年龄。
void show() { cout << "姓名:" << m_name << ",年龄:" << m_age << endl; }
};
int main() {
    CTest g1;
    g1.m_name = "赵今麦"; g1.m_age = 22;
    CTest g2(g1, 3);
    g2.show();
}

浅拷贝

浅拷贝指的是复制对象时,仅复制对象的基本数据成员,对于指针等成员,仅复制它们的地址而不复制它们所指向的内容。这意味着如果两个对象共享一个指向某个资源的指针,修改一个对象会影响另一个对象

并且是new申请的空间,那么会导致多个对象回收同一块内存空间,引起非法操作错误。

类提供的默认的拷贝构造函数是浅拷贝构造函数。

因此为了避免这种情况出现,当类中有指针成员时,我们需要重载拷贝构造函数

浅拷贝示例

#include <iostream>

class ShallowCopyExample {
public:
    int* data;

    // 构造函数
    ShallowCopyExample(int value) {
        data = new int(value);
    }

    // 析构函数
    ~ShallowCopyExample() {
        delete data;
    }

    // 打印值
    void print() const {
        std::cout << "Value: " << *data << std::endl;
    }
};

int main() {
    ShallowCopyExample obj1(42);
    ShallowCopyExample obj2 = obj1;  // 进行浅拷贝

    obj1.print();  // 输出: Value: 42
    obj2.print();  // 输出: Value: 42

    // 修改obj2中的值
    *obj2.data = 84;
    obj1.print();  // 输出: Value: 84
    obj2.print();  // 输出: Value: 84

    return 0;
}

obj1和obj2共享同一个内存地址。当我们修改obj2.data时,obj1.data也会改变,因为它们指向相同的内存地址

如何实现深拷贝

什么是深拷贝

为了避免浅拷贝带来的问题,我们通常会实现深拷贝(Deep Copy)。深拷贝会创建一个新的内存空间,并复制对象中所有指针指向的数据,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值。

这样就不会出现通过成员指针对指向数据进行修改,另外一个对象的指针成员的指向也被修改。还有一块内存被释放两次的错误。

#include<iostream>
using namespace std;
class CTest {
public:
    int m_age;
    string m_name;
    int* m_ptr;
    //没有重载的普通构造函数
    CTest() {
        m_name.clear();
        m_age = 0;
        m_ptr = nullptr;
        cout << "调用了普通构造函数" << endl;
    }
    // 没有重载的拷贝构造函数(默认拷贝构造函数)。  
    CTest(const CTest& tst) { m_name = "漂亮的" + tst.m_name; m_age = tst.m_age - 1;m_ptr = tst.m_ptr; cout << "调用了CTest(const CTest &tst)拷贝构造函数。\n"; }

    // 重载的深拷贝构造函数。  
    CTest(const CTest& tst,int ii) {
        if (tst.m_ptr == nullptr) {
            if (m_ptr != nullptr) {
                delete m_ptr;
                m_ptr = nullptr;
            }
        } 
        else 
        {
            m_ptr = new int;
            memcpy(m_ptr, tst.m_ptr, sizeof(int));
        }
        m_name = "漂亮的" + tst.m_name; m_age = tst.m_age - ii;
        cout << "调用了 CTest(const CTest &tst)深拷贝构造函数" << endl;
    }

    // 析构函数。  
    ~CTest() { cout << "调用了~CTest()\n"; }

    // 显示姓名和年龄。
    void show() { cout << "姓名:" << m_name << ",年龄:" << m_age << endl; }

};
int main() {
    CTest g1;
    g1.m_name = "赵今麦"; g1.m_age = 22; g1.m_ptr = new int(58);
    CTest g2(g1,3);
    *g2.m_ptr = 23;
    g1.show();
    g2.show();
}

修改一个指针变量,不会影响另外一个指针变量。

深拷贝和浅拷贝的区别

浅拷贝会把指针变量的地址复制; 深拷贝会重新开辟内存空间。

当数据成员中有指针时,必须要用深拷贝。

默认赋值运算符operator=

对象的赋值运算是指将一个已经存在的对象的值赋给另一个已经存在的对象。如果类的定义中没有重载赋值运算符,编译器会提供一个默认赋值运算符。如果类中重载了赋值运算符,编译器将不再提供默认的赋值运算符。

重载赋值运算符的语法如下:

类名& operator=(const 类名& 源对象);

需要注意的是,编译器提供的默认赋值运算符执行的也是浅拷贝。如果类的对象不涉及堆内存,默认的浅拷贝赋值运算符通常是足够的。然而,如果对象包含指向堆内存的指针,那么就需要实现深拷贝。

赋值运算和拷贝构造是不同的概念:

  • 拷贝构造:用于创建一个新对象,并用一个已存在的对象进行初始化。
  • 赋值运算:用于将一个已存在对象的值赋给另一个已存在的对象。
#include<iostream>
using namespace std;
class CTest {
public:
    int m_a;
    int m_arr[5];
    int* m_ptr;
    CTest() :m_a(4), m_arr{0,1,2},m_ptr(nullptr) {}
    int operator+(/*CTest * const this*/ int a) {
        return this->m_a + a;
    }
    CTest& operator=(const CTest& g) {
        if (this == &g) return *this;
        if (g.m_ptr == nullptr) {
            if (m_ptr != nullptr) {
                delete m_ptr;
                m_ptr = nullptr;
            }
        }
        else {
            if (m_ptr == nullptr) m_ptr = new int;
            memcpy(m_ptr, g.m_ptr, sizeof(int));
        }
        m_a = g.m_a;
        cout << "调用了重载赋值符" << endl;
        return *this;
    }
};
int main() {
    CTest tst1;
    CTest tst2;
    tst2.m_ptr=new int(15);
    tst1=tst2;
    cout<<*tst1.m_ptr<<endl;
    return 0;
}

在 C++ 中,编译器会自动为类生成四个默认函数:

  1. 默认构造函数:一个空实现的构造函数。
  2. 默认析构函数:一个空实现的析构函数。
  3. 默认拷贝构造函数:对成员变量进行浅拷贝的构造函数。
  4. 默认赋值运算符:对成员变量进行浅拷贝的赋值运算符。
  • 40
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值