在C++98之前,对于一个类来说,通常定义有拷贝构造、析构函数、拷贝赋值操作函数
而在C++11后,三个函数变成了"Big Five",所以完整的五个函数应该是,(构造函数),析构函数,拷贝构造函数,拷贝赋值运算符,移动构造函数(C++11),移动赋值运算符(operator =)(C++11),当类中有指针成员,一定要重写拷贝构造和拷贝赋值
<
针对构造函数,析构函数,拷贝赋值和拷贝构造,我们详细解读(参考7.三大函数:拷贝构造,拷贝复制,析构_哔哩哔哩_bilibili)
首先看什么情况下,我们会用到这两个函数,比如我们定义一个类:
class String{
...
}
然后在main函数中去引用该类,我们具体看这三种函数的使用
int main()
{
String s1();//该操作使用构造函数
String s2("hello");//该操作使用构造函数
String s3(s1); //该操作就是拷贝构造
上述等同于 String s3 = s1 //拷贝构造
s3 = s2; //该操作就是拷贝赋值
}
当我们的对象不包含指针的时候,可以使用默认的拷贝构造和拷贝赋值,就会一个位一个位的复制
当我们的对象包含指针当进行拷贝构造的时候,它会拷贝一份指针指向同一个对象,比如上述的s3的指针会指向s1的内存空间,当s1对象销毁,s3指针会无效,这就是浅拷贝(两个人看同一个东西)
所以当类中包含指针对象,一定要重写拷贝构造和拷贝赋值
比如创造字符串的时候,实际上是创建一个指针,当确定好字符后,再开辟字符大小的内存,让指针指向该内存,所以字符串是指针
class String
{
public:
String(const char* cstr = 0); //构造函数,传输进去一个char指针,默认为0
String(const String& str); //构造函数,传输进去的是自己的类名,所以是拷贝构造
String& operator = (const String& str);//有赋值符operator,传输进去的也是自己,所以是拷贝赋值
~String();
char& get_c_str() const { return m_data;}
private:
char* m_data //创建字符串指针
}
完整的构造函数
String::String(const char* cstr = 0)//加const表示该指针传进来不会再变化
{
if(cstr){
m_data = new char[strlen(cstr + 1)];
strcpy(m_data,cstr); //strcpy是string copy的缩写,是一个复制字符串的函数,m_data是一个字符数组,cstr是另一个字符数组,该数组包含了要复制的字符串
}
else{//未指定初值
m_data = new char[1];
*m_data = '\0'; //*m_data是解引用,拿出实际的内存去分配
}
}
完整的析构函数
String::~String()
{
delete[] m_data;
}
完整的拷贝构造函数
String::String(const String& str) //传进来是String的引用,不会发生改动,所以使用const关键字
{
m_data = new char[strlen(str.m_data)+1]
strcpy(m_data,str.m_data);
}
以传入类为蓝本创建新类,即
String s3(s1)
这种拷贝就是深拷贝(两个人看两个东西),把整个数据都拷贝过来了,不只是指针
完整的拷贝赋值函数
String & String::operator = (const String& str)
{
if(this == &str) //检测自我赋值(self assignment),这块一定要写,不然后面会直接把自己清空
return *this;
delete[] m_data;//把自己清空
m_data = new char[strlen(str.m_data) + 1];//重新分配空间
strcpy(m_data,str.m_data);//把右边的拷贝过来
return *this;
}
也就是s3 = s2
>
再看看移动构造函数和移动赋值运算符
移动构造函数
MyArray(MyArray&& other) noexcept : data(other.data), size(other.size) { //接收一个右值引用other作为参数,并标记为noexcept,表示不会抛出异常,data(other.data),size(other.size)
//data被初始化为other.data(指向数组的指针),size被初始化为other.size的值,这样新对象就有了原来other的资源
//这时候已经完成了移动
other.data = nullptr; // 将源对象置于有效但未定义状态
other.size = 0;
std::cout << "移动构造函数被调用\n";
}
移动赋值运算符
MyArray& operator=(MyArray&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
移动构造函数和移动赋值运算符的区别:
移动构造函数主要用于构建一个新的对象,将另一个对象的资源移动到新对象中,移动赋值运算符则用于将一个对象的资源移动到另一个已存在的对象中,覆盖该对象的当前内容
具体的调用:
int main(){
MyArray tempArray;
tempArray.data = new in[5]{1,2,3,4,5};
tempArray.size = 5;
MyArray array = std::move(tempArray);//调用移动构造函数,使用move操作把临时数组的资源给新的数组,此时临时数组就不再拥有这些资源,内容清空,指针变为空指针
MyArray array2;
array2.data = new int[3]{6,7,8};
array2.size = 3;
Array2 = std::move(array1); //调用移动赋值运算符,array1 的资源已被移动到 array2,array1 的 data 为 nullptr,size 为 0
}