运算符重载 :关于非成员函数的好处&返回值引用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Mr_zhuo_/article/details/72630999

一.关于参数类型不是对象的情况:(非成员函数赞。)

类:


主函数:


重点是倒数第二行,

c = 5.6 + b; //如果我只有类里面的+成员函数,这里是没法匹配的。即使我有double转换成Complex的构造函数,依然没法匹配。

要知道,成员函数的+是和左操作数匹配的5.6是个double型的数据,那么它会想要把右操作数b也转换成double型,再进行计算。这显然是不现实的,首先,我没有写将Complex型转换成double型的函数;再者,复数类转换成double类根本就没有意义,所以这是一种错误的做法。

正确做法:(c = 5.6 + b;)


有三种方法:
1.将 + 运算符的重载从成员函数改成非成员函数,声明为友元。
说明:这样做是调用了非成员的 + 函数,在传参的时候讲double型隐式转换为Complex型(通过double转Complex的构造函数),再进行计算。
2.直接写一个 + 的非成员函数,参数分别为(double,const Complex& )
说明:这样就根本用不到 double 转Complex的构造函数了,c = 5.6 + b;直接调用这个函数就可以了。但是有一点,比较麻烦,如果像下面这样:

c = 5.6 + b;
c = b + 5.6;
c= a + b;

这样岂不是要写三个 + 运算符重载函数?

Complex operator +(double a,const Complex &b){ }
Complex operator +(const Complex &a,double b){ }
Complex operator +(const Complex &a,const Complex &b){ }
当然可以偷个懒,写成:
Complex operator +(double a,const Complex &b){ }
inline Complex operator + (const Complex &a,double b){
	return a + b;
}
Complex operator +(const Complex &a,const Complex &b){ }

可是这样依然这样太麻烦。

3.转换运算符
说明:对于复数类,不能将double和Complex随意相互切换,因为Complex型的不是都可以转换成double型的。然而对于有的情况,比如RMB类,就可以RMB和double随意相互切换。这时当运算符的参数是(RMB , double )或(double , RMB ),就可以用转换运算符。
在RMB类里面写:

//double转换成RMB
RMB(double value=0.0){
yuan=value;
jf=(value-yuan)*100+0.5;
}
//RMB转换成double
operator double(){
    return yuan+jf/100.0;
}
有了这两个函数,就不需要写运算符重载函数了。因为遇到RMB它就自将其转换成double再调用原本的+运算符函数进行运算。
不过这有一个缺点:可能或出现转换的二义性。

总结:个人建议用非成员函数进行双目运算符的重载。这样只要写了相应转换类型的构造函数,就不会出问题,而且简洁,一个运算符重载函数就够了。


二.关于运算符重载里面的引用

写两个例子:
1.双目运算符 +
(1)成员函数:
Increase Increase::operator + (Increase & s){
	Increase result(value+s.value);
	return result;
}
(2)非成员函数:(友元):
Increase operator + (Increase & s1,Increase &s2){
	Increase result(s1.value+s2.value);
	return result;
}

参数用了引用:

为什么不直接将s1,s2作为参数呢?
注意这里我们是对类的操作。如果参数类型不是引用,那么传参的时候就需要一个对象副本,将实参复制到形参,这种复制是浅复制,并不会为形参分配内存空间,这就导致如果类中有指针类型,形参和实参的指针就指向同一块内存,再调用析构函数时重复delete,出现错误。
当然你可以用指针,但是指针用了之后可读性不佳:
Increase operator + (Increase* s1,Increase* s2)
//调用时
Increase s1(2);
Increase s1(4);
Increase s3 = &s1 + &s2; //读起来像是地址


2.单目运算符 ++
(1)成员函数:
//前置 ++
Increase& Increase::operator ++(){
	value++;
	return *this;
}
//后置 ++
Increase Increase::operator ++(int ){
	Increase old(*this);
	value ++;
	return old;
}
(2)非成员函数:(友元):
//前置 ++
Increase& operator ++(Increase& s){
	s.value++;
	return s;
}
//后置 ++
Increase operator ++(Increase& s,int ){
	Increase old(s);
	s.value ++;
	return old;
}

前置++返回值用了引用:(为了提高函数效率,减小开销)

因为需要我这个值本身改变,那么用引用传进来,并且返回值就是它本身。用引用返回值则没有返回值的临时副本,从始至终都是它。

如果不用引用返回,那么就会返回一个s的临时副本,虽然可以达到相同的效果,但是增加了不必要的开销,函数效率下降。

后置++返回值没有用引用:(防止局部变量被引用到全局)

后置++其实就是 ++了但是返回的是旧值。那这个值改变了,所以用引用传进来。但是返回的是一个中间值,不是其本身。因为这个中间变量是一个临时变量,是在局部作用域里面,如果用引用返回,是很危险的。所以不能加引用。


3.赋值运算符
例:
#include <iostream>
using namespace std;

class StupidClass {
    int flag;
    public:
        StupidClass(int flag): flag(flag) {
            cout << "Constructor " << flag << endl;
        }
        ~StupidClass() {
            cout << "Destructor " << flag << endl;
        }
        StupidClass(const StupidClass& rhs) {
			flag=rhs.flag;
            cout << "Copy Constructor *this=" << flag << " rhs=" << rhs.flag << endl;
        }
        StupidClass& operator=(const StupidClass& rhs) {
            cout << "Operator = *this=" << flag << " rhs=" << rhs.flag << endl;
            return *this;
        }
        StupidClass& operator+=(const StupidClass& rhs) {
            cout << "Operator += *this=" << flag << " rhs=" << rhs.flag << endl;
            flag += rhs.flag;
            return *this;
        }
};

int main() {
    StupidClass var1(1), var2(2);
    StupidClass var3 = var1 += var2;          // 1
    //StupidClass &var3 = var1 += var2;       // 2
    var3=var1;
    return 0;
}
上述代码
(1)用语句1
输出结果:
Constructor 1
Constructor 2
Operator += *this=1 rhs=2
Copy Constructor *this=3 rhs=3
Operator = *this=3 rhs=3
Destructor 3
Destructor 2
Destructor 3

(2)用语句2
输出结果:
Constructor 1
Constructor 2
Operator += *this=1 rhs=2
Operator = *this=3 rhs=3
Destructor 2
Destructor 3

返回值都用了引用:

(1)减小不必要的开销(如果不加引用,则会返回临时副本,这没必要,因为自身本来就改变了,且我们需要这个改变了的值)。

(2)另外,防止++出现问题。

说到这里了,就总结下没有引用返回的运算符重载结果的++出现的问题:

1.如果赋值运算符函数返回值不加引用:
RMB operator = (RMB & s){
    yuan=s.yuan;
    jf=s.jf;
}
RMB a(5.2), b (2.6);
(b = a) ++;       //结果b=5.2,而不是5.3
就是因为b = a返回值是一个b的副本,副本进行++,这条语句结束以后这个副本才消失,然而b实际上并没有++
2.前面已说,后置++运算符重载函数返回是不带引用的。

a=5;
(a++)++;  //结果a=6

与上面同样的道理. 所以不能连续++,达不到想要的效果.


简单总结:就是返回值不带引用的话,返回的是临时副本。

这时需要警戒存不存在把局部变量扩展到全局的问题; 还有就是++的问题。

赋值运算符重载还有一点要注意:

void fn(A & a){
    A a1=a;    //这是拷贝构造函数,没有调用=运算符函数
    A1=a;      //这是赋值运算符,没有调用拷贝构造函数。
} 
在浅拷贝会出现问题的时候,用赋值运算符重载函数,在这个函数里面用delete取消已占用的资源,再开辟新空间放置新内容。

猜你喜欢

转载自blog.csdn.net/Mr_zhuo_/article/details/72630999