编译器会自动为类构造四个函数
1.无参构造函数(无参且函数体是空的)
2.析构函数(无参,且函数体是空的)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符的重载 operator=, 对属性进行值拷贝(也就是说,在我们实际重载之前,编译器以及写过一个值拷贝的构造函数了)
我们先来了解一下编译器自动生成的赋值=重载函数是什么样,看下面的cpp
class person {
public:
int m_A;
person(const int &a) {
m_A = a;
}
//下面那三行就是编译器自动生成的一个重载函数,可写可不写,最终结果都一样。
//person& operator=(person& p) {
//m_A = p.m_A; //仅仅是简单的值拷贝
//return *this; }
};
int main() {
person p1(10);
person p2(20);
p2 = p1;
cout << p2.m_A << endl;
}
我们不难看出,如果仅仅只是简单的类的赋值,编译器自动生成的完全够用。 但是你想一想,如果涉及到指针形式,只进行简单的值拷贝就会出现内存重复释放的问题,这就会爆断点,是个很可怕的事情,所以我们学习怎么在类中给赋值运算符=进行深拷贝重载相当必要。
下面写一个问题代码,我们看看指针形式的时候会怎么出错。
#include<iostream>
using namespace std;
class person {
public:
int *m_A;
person(int a) {
m_A = new int(a);
}
person& operator=(person& p) {
m_A = p.m_A; //这个构造函数只是简单的把传入的p的m_A的指针赋值给了当前对象的m_A;我们假设传入的那个对象的m_A地址是0x1111
return *this; //那么经过简单的赋值拷贝后当前对象的m_A地址也是0x1111,而不是新地址,这就埋下了祸根
}
~person() {
if (m_A != NULL) { //祸根开始显现
delete m_A; //两个类的对象,因此析构函数会调用两次,第一个调用的时候先判断,指针不为空,指针指向的内容被释放,
m_A = NULL; //第二次再调用,我们再判断当前对象的m_A是否为空(注意,一定是当前对象,而不是上一次调用的对象),发现也不为空,这时候我们就要去释放那个地址指向的内存了,但是编译器发现,那块内存已经被释放掉了,这时问题就爆出来了,就是内存重复释放。
}
}
};
int main() {
person p1(10);
person p2(20);
p2 = p1;
}
下面给出正确的例子:
#include<iostream>
using namespace std;
class person {
public:
int* m_A;//定义了一个int类型的指针,之后会在构造函数中利用new向堆区申请一块内存存放传进来的数据
person(int a) {
m_A = new int(a);
}
person & operator=(person &p) {
if (m_A != NULL) {
delete m_A;
m_A = NULL;
}
//m_A = p.m_A;编译器提供的浅拷贝书写格式
//浅拷贝就是把地址简单复制
m_A = new int(*p.m_A);
//这个才是深拷贝,把复制那个地址中的内容,再在堆区申请一份内存把这个内容存进去得到一个新地址。
return *this;//返回当前对象
}
~person() {
if (m_A != NULL) {
delete m_A;
m_A = NULL;
}
}
};
int main() {
person p1(10);
person p2(20);
p2 = p1;
cout << *p2.m_A << endl;
}
//使用这个判断语句的目的是使代码更加健壮,好习惯,涉及到指针一定要想着判断指针是否为空
if (m_A != NULL) {
delete m_A;
m_A = NULL;
}
=运算符重载必须依靠类的成员函数来实现:
赋值运算符=重载:必须依托类的成员函数来实现,不能用全局函数来实现
赋值运算符重载函数只能是类的非静态的成员函数,不能是静态成员函数,也不能是友元函数
原因:
其实,之所以不是静态成员函数,是因为静态成员函数只能操作类的静态成员,不能操作非静态成员。如果我们将赋值运算符重载函数定义为静态成员函数,那么,该函数将无法操作类的非静态成员,这显然是不可行的。
在前面的讲述中我们说过,当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动提供一个。现在,假设C++允许将赋值运算符重载函数定义为友元函数并且我们也确实这么做了,而且以类的引用为参数。与此同时,我们在类内却没有显式提供一个以本类或本类的引用为参数的赋值运算符重载函数。由于友元函数并不属于这个类,所以,此时编译器一看,类内并没有一个以本类或本类的引用为参数的赋值运算符重载函数,所以会自动提供一个。此时,我们再执行类似于str2=str1这样的代码,那么,编译器是该执行它提供的默认版本呢,还是执行我们定义的友元函数版本呢?
为了避免这样的二义性,C++强制规定,赋值运算符重载函数只能定义为类的成员函数,这样,编译器就能够判定是否要提供默认版本了,也不会再出现二义性。