前言:
在C++中,有三大函数复制控制(复制构造函数,赋值操作符,析构函数),而在C++11中,加入了移动构造函数,移动赋值操作符。我就斗胆将他们命名为六大函数好了。
一、构造函数
c++primer中说过:构造函数是特殊的成员函数,只要创建类类型的新对象,都要执行构造函数。构造函数的工作就是保证每个对象的数据成员具有合适的初始值。
构造函数与其他函数不同:构造函数和类同名,没有返回类型。
构造函数与其他函数相同:构造函数也有形参表(可为void)和函数体。 (参数表为void的构造函数为默认构造函数)
1、构造函数初始化表
A() :a(0){}
我们使用构造函数初始化表示初始化数据成员,然而在没有使用初始化表的构造函数则在构造函数体中对数据成员赋值。
2、默认实参构造函数
A(int i = 1) :a(i), ca(i), ra(i){}
3、默认构造函数
合成的默认构造函数:当类中没有定义构造函数(注意是构造函数)的时候,编译器自动生成的函数。
但是我们不能过分依赖编译器,如果我们的类中有复合类型或者自定义类型成员,我们需要自己定义构造函数。
自定义的默认构造函数:
A(): a(0) {}
A(int i = 1): a(i) {}
可能疑问的是第二个构造函数也是默认构造函数么?是的,因为参数中带有默认值。
二、移动构造函数
A(A && h) : a(h.a)
{
h.a = nullptr; //还记得nullptr?
}
可以看到,这个构造函数的参数不同,有两个&操作符, 移动构造函数接收的是“右值引用”的参数。
还要来说一下,这里h.a置为空,如果不这样做,h.a在移动构造函数结束时候执行析构函数会将我们偷来的内存析构掉。h.a会变成悬垂指针。
移动构造函数何时触发? 那就是临时对象(右值)。用到临时对象的时候就会执行移动语义。
这里要注意的是,异常发生的情况,要尽量保证移动构造函数 不发生异常,可以通过noexcept关键字,这里可以保证移动构造函数中抛出来的异常会直接调用terminate终止程序。
右值引用:
这里有一个函数就是 move函数,它能够将左值强制转换成右值引用。
三、移动赋值操作符
他的原理跟移动构造函数相同,这里不再多说。
给出实现代码:
A & operator = (A&& h)
{
assert(this != &h);//assert作用是如果它的条件返回错误,则终止程序执行
a = nullptr;
a = move(h.a);
h.a = nullptr;
return *this;
}
复制控制
四、复制构造函数
他是一种特殊的构造函数,具有单个形参,形参是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造函数。
必须定义复制构造函数的情况:
1.、类有一个或者多个数据成员是指针。
2、有成员表示在构造函数中分配的其他资源。另外的类在创建新对象时必须做一些特定的工作。
下面给出赋值构造函数的编写:
A(const A& h) :a(h.a){}
如果不想让对象复制呢? 那就将复制构造函数声明为:private;
3、浅复制和深复制概念介绍
浅复制:也就是在对象复制时,只是对对象中的数据成员进行简单的赋值,如果对象中存在动态成员,即指针,浅拷贝就会出现问题。(c++类中默认生成的复制函数为浅复制,如果类中有指针成员,必须使用深复制)
深复制:对于深拷贝,针对成员变量存在指针的情况,不仅仅是简单的指针赋值,而是重新分配内存空间。
五、赋值操作符
他跟构造函数一样,赋值操作符可以通过制定不同类型的右操作数而重载。
赋值和复制经常是一起使用的,这个要注意。
下面给出赋值操作符的写法:
A& operator = (const A& h)
{
assert(this != &h);//assert作用是如果它的条件返回错误,则终止程序执行
this->a = h.a;
return *this;
}
六、析构函数
是构造函数的互补,当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。析构函数可用于释放对象时构造或在对象的生命期中所获取的资源。不管类是否定义了自己的析构函数,编译器都会自动执行类中非static数据成员的析构函数。
析构函数的运行:
当对象引用或指针越界的时候不会执行析构函数,只有在删除指向动态分配对象的指针或实际对象超出作用域时才会调用析构函数。
合成析构函数:
编译器总是会合成一个析构函数,合成析构函数按对象创建时的逆序撤销每个非static成员。要注意的是,合成的析构函数不会删除指针成员所指向的对象。
最后要注意的是:类如果需要析构函数,那么他肯定也需要复制构造函数和赋值操作符。
七、1个完整的六大函数的代码示例
#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;
class Temp
{
public:
Temp(const char* str = nullptr);//构造函数
Temp(Temp&& t);//移动构造函数
Temp& operator = (Temp&& t);//移动赋值操作符,重载=操作符
Temp(const Temp& t);//复制构造函数
Temp& operator = (Temp& t);//复制赋值操作符
~Temp(void);//析构函数
public:
char* m_pData;
};
//构造函数
Temp::Temp(const char* str)
{
if (!str)
{
m_pData = nullptr;
}
else
{
this->m_pData = new char[1];
*m_pData=*str;
}
cout<<"构造函数:"<<m_pData<<endl;
}
//移动构造函数
Temp::Temp(Temp&& t):m_pData(move(t.m_pData))
{
t.m_pData = nullptr;
cout<<"移动构造函数:"<<m_pData<<endl;
}
//移动赋值操作符,重载=操作符
Temp& Temp::operator = (Temp&& t)
{
assert(this != &t);//assert作用是如果它的条件返回错误,则终止程序执行
this->m_pData = nullptr;
this->m_pData = move(t.m_pData);
t.m_pData = nullptr;
cout<<"移动赋值操作符:"<<this->m_pData<<endl;
return *this;
}
//复制构造函数
Temp::Temp(const Temp& t)
{
if (!t.m_pData)
{
this->m_pData = nullptr;
}
else
{
//深复制
this->m_pData = new char[1];
*m_pData=*t.m_pData;
}
cout<<"复制构造函数1:"<<m_pData<<endl;
//cout<<"复制构造函数2:"<<t.m_pData<<endl;
}
//复制赋值操作符
Temp& Temp::operator = (Temp& t)
{
if (this != &t)
{
delete[] this->m_pData;
if (!t.m_pData)
{
this->m_pData = nullptr;
}
else
{
//深复制
this->m_pData = new char[1];
*m_pData=*t.m_pData;
}
}
cout<<"复制赋值操作符1:"<<m_pData<<endl;
//cout<<"复制赋值操作符2:"<<t.m_pData<<endl;
return *this;
}
//析构函数
Temp::~Temp(void)
{
if (this->m_pData)
{
delete[] this->m_pData;
this->m_pData = nullptr;
//cout<<"delete析构函数"<<endl;
}
cout<<"析构函数"<<endl;
}
int main()
{
// your code goes here
char a=50;
cout<<"main1"<<endl;
Temp temp1(&a);//调用构造函数
Temp temp2(temp1);//已知对象赋值给未知对象,调用复制构造函数
Temp temp3=temp1;//已知对象赋值给未知对象,调用复制构造函数
temp3=temp1;//已知对象赋值给已知对象,调用复制赋值操作符
Temp temp5(&a);
//Temp d=std::move(temp5);//调用移动构造函数
Temp d(&a);
d=std::move(temp5);//调用移动赋值操作符函数
return 0;
}