拷贝构造 赋值构造 浅拷贝 深拷贝
拷贝构造函数
拷贝构造是在传入一个类的实例作为参数创建新的类的时候触发的
class ctest
{
public:
ctest(ctest& t)
{
cout << "ctest(ctest& t)" << endl;
}
};
也可以为参数增加const修饰防止修改。触发拷贝构造的情况一般有两种
ctest a;
ctest b(a);
ctest c = a;
b和c都会调用拷贝构造,b很好理解,直接传递了一个实例a,所以从函数的参数列表对应,也是应该调用拷贝构造。对于c可能有人认为是赋值构造,实际上是错误的,因为赋值的时候c是没创建的,相当于用a初始化创建c,所以还是拷贝构造。并且赋值构造并不能算作构造函数,它有返回值,赋值构造可以叫做赋值函数。
注意
拷贝构造函数必须是传入的引用或者指针也可以,但是不可以传递形参,现在的编译器已经可以直接识别错误了。因为如果传递形参,那么在形参传递过程种需要创建局部变量,那么又会调用赋值构造,就会导致死循环创建。
赋值构造
赋值构造相当于重载了操作符等号(=)
class ctest
{
public:
ctest& operator = (ctest& t)
{
return *this;
}
};
赋值构造函数必须是返回自己的引用,因为有连等的操作,可以不返回,但是会在连等的情况下出问题,按照C++的等号操作符的规则,是需要返回自己的引用
赋值构造在已存在的实例赋值操作时触发,如果实例是在创建的时候调用等号,就会调用上面的拷贝构造
ctest a;
ctest b;
b = a;
注意与上面的示例区别
浅拷贝
对于有指针类型成员的类,拷贝构造和赋值构造的时候需要注意,因为如果我们不重载拷贝构造或者赋值构造,类是有默认的函数的,它的操作就是直接赋值,那么就会导致两个类的指针保存了同一个值,也就是指向了同一块空间,这样不管在析构还是在使用的时候都会引发问题
class ctest
{
public:
ctest()
{
m_data = NULL;
}
~ctest()
{
if (m_data != NULL)
{
delete[] m_data;
m_data = NULL;
}
}
char* m_data;
};
void test()
{
ctest a;
a.m_data = new char[10]{ 'a','b' };
//下面这两个类都会在析构的时候报错
ctest b(a);
ctest c;
c = a;
}
int main()
{
test();
return 0;
}
在test函数种,类a申请了内存,并且做了初始化,然而b和c都在构造和赋值的时候让自己内部的m_data值等于了a的m_data。那么指向同一块内存的多个指针在析构的时候进行多次释放,导致崩溃。这种情况下需要深拷贝
深拷贝
深拷贝就是在拷贝和复制的时候进行一个额外的操作,防止出现多个类实例公用一块空间而导致不可预料的问题
class ctest
{
public:
ctest()
{
m_data = NULL;
}
ctest(const char * p)
{
if (p != NULL)
{
m_data = new char[strlen(p) + 1]();
strcpy(m_data, p);
}
}
ctest(const ctest& t)
{
if (t.m_data != NULL)
{
m_data = new char[strlen(t.m_data) + 1]();
strcpy(m_data, t.m_data);
}
}
ctest& operator=(const ctest& t)
{
if (this != &t && t.m_data != NULL)
{
m_data = new char[strlen(t.m_data) + 1]();
strcpy(m_data, t.m_data);
}
return *this;
}
~ctest()
{
if (m_data != NULL)
{
delete[] m_data;
m_data = NULL;
}
}
private:
char* m_data;
};
void test()
{
ctest a("abc");
ctest b(a);
ctest c;
c = a;
}
int main()
{
test();
return 0;
}
上面就是实现了一个简单的string类,其中就用到了深拷贝,在构造和赋值的时候,都会为自己的类重新申请一块空间保存传递进来参数的数据,而不是直接赋值,这样就可以避免在析构的时候对同一块内存多次释放的问题。