1.从概念上区分:
复制构造函数是构造函数,而赋值操作符属于操作符重载范畴,它通常是类的成员函数
2.从原型上来区分:
复制构造函数原型ClassType(const ClassType &);无返回值
赋值操作符原型ClassType& operator=(const ClassType &);返回值为ClassType的引用,便于连续赋值操作
3.从使用的场合来区分:
复制构造函数用于产生对象,它用于以下几个地方:函数参数为类的值类型时、函数返回值为类类型时以及初始化语句,例如(示例了初始化语句,函数参数与函数返回值为类的值类型时较简单,这里没给出示例)
ClassType a; //
ClassType b(a); //调用复制构造函数
ClassType c = a; //调用复制构造函数
而赋值操作符要求‘=’的左右对象均已存在,它的作用就是把‘=’右边的对象的值赋给左边的对象
ClassType e;
Class Type f;
f = e; //调用赋值操作符
复制构造函数是去完成对未初始化的存储区的初始化,而赋值操作符则是处理一个已经存在的对象。对一个对象赋值,当它一次出现时,它将调用复制构造函数,以后每次出现,都调用赋值操作符。
构造函数、析构函数、赋值函数是每个类最基本的的函数。每个类只有一个析构函数和一个赋值函数。但是有很多构造函数(一个为复制构造函数,其他为普通构造函数。对于一个类A,如果不编写上述四个函数,c++编译器将自动为A产生四个默认的函数,即:
- A(void) //默认无参数构造函数
- A(const A &a) //默认复制构造函数
- ~A(void); //默认的析构函数
- A & operator = (const A &a);//默认的赋值函数
既然能自动生成函数,为什么还需要自定义?原因之一是“默认的复制构造函数”和"默认的赋值函数“均采用”位拷贝“而非”值拷贝“
位拷贝 v.s. 值拷贝
如果一个类拥有资源,当这个类的对象发生复制过程时,资源重新分配,就叫做深拷贝。
为便于说明,以自定义String类为例,先定义类,而不去实现
#include <iostream>
using namespace std;
class String
{
public:
String(void);
String(const String &other);
~String(void);
String & operator =(const String &other);
private:
char *m_data;
int val;
};
位拷贝(浅拷贝)拷贝的是地址,而值拷贝(深拷贝)拷贝的是内容。
如果定义两个String对象a, b。当利用位拷贝时,a=b,其中的a.val=b.val;但是a.m_data=b.m_data就错了:a.m_data和b.m_data指向同一个区域。这样出现问题:
- a.m_data原来的内存区域未释放,造成内存泄露
- a.m_data和b.m_data指向同一块区域,任何一方改变,会影响到另一方
- 当对象释放时,b.m_data会释放掉两次
因此
当类中还有指针变量时,复制构造函数和赋值函数就隐含了错误。此时需要自己定义。
结论
- 有一种特别常见的情况需要自己定义复制控制函数:类具有指针哈函数。
- 赋值操作符和复制构造函数可以看成一个单元,当需要其中一个时,我们几乎也肯定需要另一个
- 三法则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数
注意
- 如果没定义复制构造函数(别的不管),编译器会自动生成默认复制构造函数
- 如果定义了其他构造函数(包括复制构造函数),编译器绝不会生成默认构造函数
- 即使自己写了析构函数,编译器也会自动生成默认析构函数
因此此时如果写String s是错误的,因为定义了其他构造函数,就不会自动生成无参默认构造函数。
复制构造函数 v.s. 赋值函数
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:
String(const char *str);
String(const String &other);
String & operator=(const String &other);
~String(void);
private:
char *m_data;
};
String::String(const char *str)
{
cout << "自定义构造函数" << endl;
if (str == NULL)
{
m_data = new char[1];
*m_data = ' ';
}
else
{
int length = strlen(str);
m_data = new char[length + 1];
strcpy(m_data, str);
}
}
String::String(const String &other)
{
cout << "自定义拷贝构造函数" << endl;
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
}
String & String::operator=(const String &other)
{
cout << "自定义赋值函数" << endl;
if (this == &other) // 在赋值函数中一定要记得比较
{
return *this;
}
else
{
delete [] m_data;
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
return *this;
}
}
String::~String(void)
{
cout << "自定义析构函数" << endl;
delete [] m_data;
}
int main()
{
cout << "a("abc")" << endl;
String a("abc");
cout << "b("cde")" << endl;
String b("cde");
cout << " d = a" << endl;
String d = a;
cout << "c(b)" << endl;
String c(b);
cout << "c = a" << endl;
c = a;
cout << endl;
执行结果
说明几点
1.赋值函数中,上来比较 this == &other 是很必要的,因为防止自复制,这是很危险的,因为下面有delete []m_data,如果提前把m_data给释放了,指针已成野指针,再赋值就错了
2.赋值函数中,接着要释放掉m_data,否则就没机会了(下边又有新指向了)
3. 拷贝构造函数是对象被创建时调用,赋值函数只能被已经存在了的对象调用
注意:String a("hello"); String b("world"); 调用自定义构造函数
String c=a;调用拷贝构造函数,因为c一开始不存在,最好写成String c(a);
原文链接: https://www.cnblogs.com/balingybj/p/4731690.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/220670
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!