赋值运算符重载
赋值运算符的重载在这几个默认的成员函数中的位置也算是举足轻重的,它也是不好理解的一个,下面是我自己写的一个复数类,
这里面会将赋值运算符重载踏踏实实的过完。赋值运算符的重载的基本格式:
operator + 合法的运算符 构成函数名(重载<运算符的函数名:operator< )。
class Complex{public:Complex(double real = 0.0, double image = 0.0){_real = real;_image = image;}Complex(const Complex& com) //拷贝构造函数{cout << "compelx(Complex& com" << endl;_real = com._real;_image = com._image;}Complex Add(Complex &com){Complex cmp;cmp._real = _real + com._real;cmp._image = _image + com._image;return cmp;}Complex operator+(Complex &com)//重载加法{Complex cmp;cmp._real = _real + com._real;cmp._image = _image + com._image;return cmp;}Complex& operator++() //前置加加{_real++;_image++;return *this;}Complex& operator++(int) //后置加加{Complex tmp(*this);_real++;_image++;return tmp;}Complex operator-(Complex &com)//重载减法{Complex cmp;cmp._real = _real - com._real;cmp._image = _image - com._image;return cmp;}//这里有一个问题,为什么重载等号时,要用引用呢??Complex& operator=(Complex& c)//重载等号{cout << "0perator" << endl;if (this != &c){_real = c._real;_image = c._image;}return *this;}Complex& operator+=(Complex &com)//重载加等{_real += com._real;_image += com._image;return *this;}~Complex()//析构函数{cout << "~Complex()" << endl;}void Display()//打印{cout << _real << "+" << _image << "i" << endl;}private:double _real;double _image;};int main(){Complex c1(1.0, 1.0);Complex c2(2.0, 2.0);c2 = c1; //等价于c2.operator=(c1); 只要你进行了重载后比如 c2=c1c1++; //等价于c1.++(); 系统会默认为你重载后的形式++c1; //等价于c1.++(c1) c2 += c1; //等价于c2.operator+=(c1)c1.Display();c2.Display();return 0;}
这是运行结果~:
有木有觉得很迷。。。。。
其实赋值运算符的重载并不是很难,但是这里有几个很重要的点,运算符重载函数里面的参数是很关键的。
还有你一定要注意参数的位置,以及想一想使用时函数的样子。
比如c1++ 你在使用它的时候你的脑子里想的要是 C1.++()这样出错的概率才会减少,你的程序严密性就会高。
下面来说赋值运算符重载如何使用!!!
1.参数
一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用(如上面例1),加const是因为:
①我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
②加上const,对于const的和非const的实参,函数就能接受;如果不加,就只能接受非const的实参。
用引用是因为:
这样可以避免在函数调用时对实参的一次拷贝,提高了效率。但是比如++运算符就不适合用const,const只是一个建议,它就是提
升你文件的严密性
2.返回值
我在代码段中有这么一个问题。
这里有一个问题,为什么重载等号时,要用引用呢??
一般地,返回值是被赋值者的引用,即*this(如上面例1),原因是
①这样在函数返回时避免一次拷贝,提高了效率。
②更重要的,这样可以实现连续赋值,即类似a=b=c这样。如果不是返回引用而是返回值类型,那么,
执行a=b时,调用赋值运算符重载函数,在函数返回时,由于返回的是值类型,所以要对return后边的
“东西”进行一次拷贝,得到一个未命名的副本(有些资料上称之为“匿名对象”),然后将这个副
本返回,而这个副本是右值,所以,执行a=b后,得到的是一个右值,再执行=c就会出错。
3.赋值运算符重载函数要避免自赋值
对于赋值运算符重载函数,我们要避免自赋值情况(即自己给自己赋值)的发生,一般地,我们通过比较赋值者与被赋值者的地址
是否相同来判断两者是否是同一对象(正如例1中的if (this != &str)一句)。
为什么要避免自赋值呢?
①为了效率。显然,自己给自己赋值完全是毫无意义的无用功,特别地,对于基类数据成员间的赋值,还会调用基类的赋值运算符
重载函数,开销是很大的。如果我们一旦判定是自赋值,就立即return *this,会避免对其它函数的调用。
②如果类的数据成员中含有指针,自赋值有时会导致灾难性的后果。对于指针间的赋值(注意这里指的是指针所指内容间的赋值,
这里假设用_p给p赋值),先要将p所指向的空间delete掉(为什么要这么做呢?因为指针p所指的空间通常是new来的,如果在为p重
新分配空间前没有将p原来的空间delete掉,会造成内存泄露),然后再为p重新分配空间,将_p所指的内容拷贝到p所指的空间。如
果是自赋值,那么p和_p是同一指针,在赋值操作前对p的delete操作,将导致p所指的数据同时被销毁。那么重新赋值时,拿什么来
赋? 所以,对于赋值运算符重载函数,一定要先检查是否是自赋值,如果是,直接return *this。
4.浅拷贝和深拷贝
拷贝构造函数和赋值运算符重载函数都会涉及到这个问题。
所谓浅拷贝,就是说编译器提供的默认的拷贝构造函数和赋值运算符重载函数,仅仅是将对象a中各个数据成员的值拷贝给对象b中
对应的数据成员(这里假设a、b为同一个类的两个对象,且用a拷贝出b或用a来给b赋值),而不做其它任何事。
假设我们将例1中显式提供的拷贝构造函数注释掉,然后同样执行MyStr str3 = str2;语句,此时调用默认的拷贝构造函数,它只是
将str2的id值和nane值拷贝到str3,这样,str2和str3中的name值是相同的,即它们指向内存中的同一区域(在例1中,是字符
串”hhxx”)。如下图
这样,会有两个致命的错误
①当我们通过str2修改它的name时,str3的name也会被修改!
②当执行str2和str3的析构函数时,会导致同一内存区域释放两次,程序崩溃!
这是万万不可行的,所以我们必须通过显式提供拷贝构造函数以避免这样的问题。先判断被拷贝者的name是否为空,若否,dalete
name(后面会解释为什么要这么做),然后,为name重新申请空间,再将拷贝者name中的数据拷贝到被拷贝者的name中。执行后,
如图
这样,str2.name和str3.name各自独立,避免了上面两个致命错误。
我们是以拷贝构造函数为例说明的,赋值运算符重载函数也是同样的道理。
最后还有几个赋值运算符重载的几个小知识:
1.重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
2.5个C++不能重载的运算符: .* / : : / sizeof / ?: /.
3. C++规定,赋值运算符重载函数只能是类的非静态的成员函数,不能是静态成员函数,也不能是友元函数
4.赋值运算符重载函数不能被继承
5.当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动生成这
样一个赋值运算符重载函数。