在“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. 结尾
如果您觉得我还写的不错,请给我一点点的鼓励,您的鼓励就是我坚持的动力,当然啦,一切要量力而行,不要勉强哦!