运算符重载 :关于非成员函数的好处&返回值引用
来源:互联网 发布:vue.js和jquery 编辑:程序博客网 时间:2024/05/29 08:04
一.关于参数类型不是对象的情况:(非成员函数赞。)
类:
主函数:
重点是倒数第二行,
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转换成RMBRMB(double value=0.0){yuan=value;jf=(value-yuan)*100+0.5;}//RMB转换成doubleoperator 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; //读起来像是地址
(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取消已占用的资源,再开辟新空间放置新内容。
- 运算符重载 :关于非成员函数的好处&返回值引用
- 将重载运算符定义为成员函数还是普通的非成员函数
- 关于C++成员函数和运算符的重载
- C++基础知识复习--运算符重载(非成员函数重载和成员函数重载)
- 关于运算符重载 成员函数运算符重载 友元函数运算符重载
- C++ 友元函数和非成员运算符重载
- 赋值运算符重载函数 返回引用和返回对象的区别
- 成员函数和运算符的重载
- 复数的运算符重载----成员函数
- 重载的操作符成员函数与非成员函数
- 类成员函数返回值的引用
- 运算符重载 成员函数
- 运算符重载 定义为类成员函数or非成员函数
- 引用作为函数返回值的好处及使用
- 引用作为函数参数以及返回值的好处
- 关于拷贝构造函数和重载成员运算符=
- 运算符重载--函数返回值
- 运算符重载--函数返回值
- 计算机界面设计
- HashMap
- MySQL触发器使用详解
- 报错类型及导致的原因
- MFC简版计算器
- 运算符重载 :关于非成员函数的好处&返回值引用
- Leetcode:Climbing Stairs
- Dataquest学习总结[8]-Machine Learning
- 数据结构
- ubuntu git 服务器环境搭建
- canvas 画一幅画
- 如何构建一个分布式爬虫:基础篇
- 数据结构与算法(26)——排序(一)
- Java面试题全集(上)