C++随笔之拷贝构造函数的理解和使用

        在“C++随笔之类的构造函数”这一节中介绍了“拷贝构造函数的作用”、“如何声明拷贝构造函数”和“如何调用拷贝构造函数的方式”。但是,关于拷贝构造函数相关的内容还没介绍完。个人感觉,相较于无参构造函数和有参构造函数,拷贝构造函数的内容还是相对较多一点,因此单独写一个随笔进一步再介绍一下这个拷贝构造函数,或者说介绍一下在C++的类中如何完整的实现对象的拷贝。

1. 拷贝的理解

        简单说,就是将已有的对象a下的数据复制一份,给刚刚新创建的对象b中。此时,对象a和对象b的数据类型相同,被复制的数据内容完全相同(一般情况下内容都是全部拷贝,但是不排除会有部分拷贝的可能,毕竟人是灵活的嘛,会依据不同的场景来决定该如何实现),但是它两却是两个单独的对象,不是一个整体。拷贝构造函数类似于C#或Java中的Object下的clone函数。

2. 拷贝的分类

        2.1 浅拷贝

                (1) 特点

                        只能复制存储在内存栈中的数据,无法复制存储在内存堆中的数据。编译器默认提供的拷贝构造函数只支持浅拷贝。

                (2) 代码示例

#include<iostream>
#include<string>

using namespace std;

class User
{
public:
    string name;
    int* age_p;
    User()
    {}
    /*
    * 浅拷贝的拷贝构造函数
    */
    User(const User& other)
    {
        this->name = other.name;
        // 无法复制内存堆中的数据,只能复制存储在栈的堆内存地址数据
        this->age_p = other.age_p;
    }
};

int main()
{
    User user;
    user.name = "Sam";
    // 用户年纪数据存储在内存堆中
    user.age_p = new int(20);

    cout << "对象user下数据内容和地址" << endl;
    cout << "name: " << user.name << "\t" << "name地址: " << &user.name << endl;
    cout << "age: " << *user.age_p << "\t" << "age地址: " << user.age_p << endl;

    cout << "-----------------------------------------" << endl;
    
    // 调用编译器默认提供的拷贝构造函数
    User user_copy = user;

    cout << "对象user_copy下数据内容和地址" << endl;
    cout << "name: " << user_copy.name << "\t" << "name地址: " << &user_copy.name << endl;

    // 释放堆中存储的数据
    delete user.age_p;

    cout << "age: " << *user_copy.age_p << "\t" << "age地址: " << user_copy.age_p << endl;
}

                 (3) 运行结果

在不同的User对象下,age内存地址是完全一致的,在通过使用user_copy对象输出age数据之前(第46行),释放了对象user下的age_p(第44行),结果是user_copy下输出的age是一堆奇怪的数字。这说明age数据并没有被真正的被复制,而是两个不同的对象在共用一个age数据。

        2.2 深拷贝

                (1)特点

                        不仅可以复制存储在内存栈中的数据,还可以复制存在内存堆中的数据。

                (2) 代码示例

#include<iostream>
#include<string>

using namespace std;

class User
{
public:
    string name;
    int* age_p;
    User()
    {}
    /*
    * 深拷贝的拷贝构造函数
    */
    User(const User& other)
    {
        this->name = other.name;
        // 在内存堆中申请一个新的空间来存储age数据
        this->age_p = new int(*other.age_p);
    }
};

int main()
{
    User user;
    user.name = "Sam";
    // 用户年纪数据存储在内存堆中
    user.age_p = new int(20);

    cout << "对象user下数据内容和地址" << endl;
    cout << "name: " << user.name << "\t" << "name地址: " << &user.name << endl;
    cout << "age: " << *user.age_p << "\t" << "age地址: " << user.age_p << endl;

    cout << "-----------------------------------------" << endl;

    // 调用编译器默认提供的拷贝构造函数
    User user_copy = user;

    cout << "对象user_copy下数据内容和地址" << endl;
    cout << "name: " << user_copy.name << "\t" << "name地址: " << &user_copy.name << endl;

    // 释放堆中存储的数据
    delete user.age_p;
    
    cout << "age: " << *user_copy.age_p << "\t" << "age地址: " << user_copy.age_p << endl;
}

                (3) 运行结果

由于这是深拷贝,因此即使释放了对象user下的age数据(第44行),对user_copy下的age数据依旧没有影响,因为age也被复制了 

3. 完整的拷贝 = 拷贝构造函数 + 重载赋值运算符

        在一般的开发语言中,我们认为以下这样的代码是等价的

int a = 100;

等价于

int a;
a = 100;

或者

Object obj = new Object();
Object obj_clone = obj.Clone();

等价于

Object obj = new Object();
Object obj_clone;
obj_clone = obj.Clone();

简单说就是变量的声明和赋值是可以分开的,也可以放在一起。但是,在C++的拷贝代码中,声明和赋值分开和在放在一起并不是一个等价的操作。

User user = User();
// 调用的是拷贝构造函数(简写方式)
User user_clone = user;

不等价于

User user = User();
// 虽然看着只是声明变量,实际上调用的是无参构造函数,这是无参构造函数的简写方式
User user_clone;
// 调用的是赋值运算符
user_clone = user;

前者调用的是拷贝构造函数,后者,虽然看似只是声明了user_clone变量,实际上是调用了无参构造函数,创建了user_clone的对象,之后是调用了编译器默认提供的赋值运算符进行运算。由于赋值运算符是编译器默认提供的,因此它只支持浅拷贝。代码和运行结果如下:

#include<iostream>
#include<string>
 
using namespace std;
 
class User
{
public:
    string name;
    int* age_p;
    User()
    {}
    /*
    * 深拷贝的拷贝构造函数
    */
    User(const User& other)
    {
        this->name = other.name;
        // 在内存堆中申请一个新的空间来存储age数据
        this->age_p = new int(*other.age_p);
    }
};
 
int main()
{
    User user;
    user.name = "Sam";
    // 用户年纪数据存储在内存堆中
    user.age_p = new int(20);
 
    cout << "对象user下数据内容和地址" << endl;
    cout << "name: " << user.name << "\t" << "name地址: " << &user.name << endl;
    cout << "age: " << *user.age_p << "\t" << "age地址: " << user.age_p << endl;
 
    cout << "-----------------------------------------" << endl;
 
    // 此时调用的不是拷贝构造函数,而是编译器默认提供的赋值运算符,默认的赋值运算符只支持浅拷贝
    User user_copy;
    user_copy = user;
 
    cout << "对象user_copy下数据内容和地址" << endl;
    cout << "name: " << user_copy.name << "\t" << "name地址: " << &user_copy.name << endl;    
    cout << "age: " << *user_copy.age_p << "\t" << "age地址: " << user_copy.age_p << endl;
}

所以,要实现完成的拷贝功能,就不仅仅实现拷贝构造函数,还要重载赋值运算符,完整的深拷贝示例代码如下:

#include<iostream>
#include<string>
 
using namespace std;
 
class User
{
public:
    string name;
    int* age_p;
    User()
    {
        name = "";
        age_p = NULL;
    }
    /*
    * 深拷贝的拷贝构造函数
    */
    User(const User& other)
    {
        this->name = other.name;
        // 在内存堆中申请一个新的空间来存储age数据
        this->age_p = new int(*other.age_p);
    }
    /*
    * 重载的赋值运算符
    */
    User& operator=(const User& other)
    {
        // 判断other的内存地址是否和当前对象内存地址一致,避免自己复制自己的问题
        if (this != &other)
        {
            // 判断当前对象下是否有数据已经存储在内存堆中,如有,则释放掉
            if (this->age_p != NULL)
            {
                delete age_p;
                this->age_p = NULL;
            }
            
            // 开始深拷贝
            this->name = other.name;
            this->age_p = new int(*other.age_p);
        }

        return *this;
    }
    /*
    * 析构函数, 释放存储于堆中的数据
    */
   ~User()
   {
        if (age_p != NULL)
        {
            delete age_p;
            age_p = NULL;
        }
   }
};
 
int main()
{
    User user;
    user.name = "Sam";
    // 用户年纪数据存储在内存堆中
    user.age_p = new int(20);
 
    cout << "对象user下数据内容和地址" << endl;
    cout << "name: " << user.name << "\t" << "name地址: " << &user.name << endl;
    cout << "age: " << *user.age_p << "\t" << "age地址: " << user.age_p << endl;
 
    cout << "-----------------------------------------" << endl;
 
    // 此时调用的不是拷贝构造函数,而是重载的赋值运算符
    User user_copy;
    user_copy = user;
 
    cout << "对象user_copy下数据内容和地址" << endl;
    cout << "name: " << user_copy.name << "\t" << "name地址: " << &user_copy.name << endl;    
    cout << "age: " << *user_copy.age_p << "\t" << "age地址: " << user_copy.age_p << endl;
}

运行结果如下:

3. 结尾

        如果您觉得我还写的不错,请给我一点点的鼓励,您的鼓励就是我坚持的动力,当然啦,一切要量力而行,不要勉强哦! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鹏云友

您的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值