https://blog.csdn.net/wenqian1991/article/details/34147997

一、运算符的定义

运算符重载就是运算符的“一符多用”。重载运算符是具有特殊名称的函数:保留字 operator 后接需定义的操作符符号。像任意其他函数一样,重载操作符具有返回类型和形参表,每个操作符用于内置类型都有关联的定义,当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用,最直观,使用重载操作符并不是创造命名操作。

二、在哪种情况下使用哪种重载运算符的方式合适?

C++ 提供了两种重载运算符的方式,在大多数情况下:

只有一个操作数的运算符(一目)使用类运算符重载方式为好;
一般地说,如果运算符要修改操作数(类对象)的状态(值),则应使用类运算符(成员形式)。(在计算中可能改变操作数的值得运算符被称为有副作用的运算符,诸如:=、+=、-=、*=、/=、%=、++、-- 等);
C++规定,运算符=、()、[ ]、-> 只能采用类运算符形式重载;
有两个操作数的运算符(二目)使用友元运算符重载方式为好;
友元运算符重载方式在操作数的数据类型自动转换方面更为宽容,尤其是第一个操作数希望能够隐式类型转换时,则应采用友元形式;
不允许重载的运算符有:&&、||、. 、:: 、 * 、?: 。
三、运算符重载具体讨论(返回值和参数,这里讨论几个常用的运算符)

默认地,重载运算符必须与内置操作符保持一致,也就是说重载后的运算符必须与本来内置操作符保持特性一致。函数最主要的两个就是返回值和形参。

3.1、前缀++类运算符重载函数(前缀--类似)

自增(自减)操作符的前置式定义:累加(递减)而后取出;后置式定义:取出而后累加(递减)。

我们知道,在C语言里整型变量是允许连续前缀++两次的,也叫链式操作。这样为了保证重载运算符与内置操作符++类型一致,就要求前缀++类运算符重载函数也支持连续操作(链式操作),所以前缀++类运算符重载函数的返回值必须是类名的引用。上面第二点也说了,++作为单目运算符,并且会修改操作数的值,则应定义为类运算符,这样重载函数无形参。我们就可申明该前缀++类运算符重载函数如下:

class Zoo
{
public:
    Zoo(int lion_n = 0, int tiger_n = 0){
        lion = lion_n; tiger = tiger_n; }
    ~Zoo(){}
 
    Zoo& operator++();//无参,返回值为类名的引用
private:
    int lion;
    int tiger;
};
下面就是前缀++类运算符重载函数的实现了
内置类型前缀++操作符是直接修改了操作数,然后返回修改后的操作数本身(唯一地址),不存在复制的情况,所以重载函数也应遵循这一点:

Zoo& Zoo::operator++()
{
    ++lion;
    ++tiger;
    return *this;
}

由于函数的返回值类型被定义为引用,所以不会发生复制,返回的是操作数本身,完全符合内置前缀++的语法定义。
我们再来考虑错误情况:如果前缀++类运算符重载函数的返回值是类型,也就是返回一个对象,其对应实现如下:

Zoo Zoo::operator++()
{
    ++lion;
    ++tiger;
    return *this;
}
咋一看上面的也实现了前缀++的功能,但是返回值是对象,在函数返回时会发生复制,虽然该函数成功将操作类对象的成员修改了,但是函数返回的是一个复制品,然后再执行++链式操作时,修改的会是这个复制品的值(相当于这个复制品调用前缀++类运算符重载函数),本尊并没有修改,也就是不能成功实现链式操作,不符合内置++的语法定义(C++中,前缀++是可以连续前缀两次以上的,但后缀++则不可以)。
Zoo zoo;
++(++zoo);
上面执行后,zoo.lion = 1,zoo.tiger = 1 。并不是期望的2。
至于返回其余类型那就更加错误了。

3.2、后缀++类运算符重载函数(后缀--类似)

与前缀++操作符一样,后缀++也是单目操作符,也会修改操作书本身,所以二者的形参数目和类型相同,为了区别函数,后缀++操作符接受一个额外的(即,无用的)int 型形参。使用后缀++操作符时,编译器提供0作为这个形参的实参。

与前缀++类运算符截然相反的是,后缀++返回值的类型恰恰不能是类的引用,其目的是在返回值时引起复制,即让一个并未自增的替身对象去参加表达式的后续运算,另外C/C++在语法上不允许后缀++连续运算两次以上,也就不要求返回引用,并且必须返回 const 对象。我们看看内置后缀++操作符:

int i = 0;
int j = i++;
i++++;  //违法
内置后缀++操作符,操作数 i 本身已经完成了自增,但是后续的赋值操作并不是将自增后的 i 赋值给j,而是将并未自增的替身参与赋值运算。所以在重载后缀++类运算符的时候,我们应该考虑这点,另外必须返回一个 const 对象:
const Zoo Zoo::operator++(int)
{
    Zoo ret(*this);//拷贝构造函数,构造复制品
    ++lion;//本尊自增
    ++tiger;
    return ret;//返回复制品
}
在已经定义了前缀++类运算符重载函数的情况下,后缀++类运算符重载函数一般这样实现:
const Zoo Zoo::operator++(int)
{
    Zoo ret(*this);//拷贝构造函数,构造复制品
    ++(*this);//本尊自增
    return ret;//返回复制品
}
3.3、二目运算符重载(+=,-=,+,-)
先说复合赋值操作符,上面“+=”,“-=”也可认为是赋值操作符。内置+=、-=、%= 是允许进行链式操作的(如果不确定是否允许,可以写一个测试程序判断),所以为了与内置类型的操作一致,重载函数毫无疑问是返回一个引用,也避免了创建和撤销结果的临时副本。

但是“+”“-” 等是返回一个新的结果,这就要求算术运算符的重载不能返回一个引用,另外+的表达式也不能作为左值。

int i = 1;
    int j = 2;
    i = i + j + j;//可以连续+,但是右边的i,j还是原值,(i+j) = i + i;错误!
        i += (i += j);//复合了赋值操作符,这样是允许的
有了前面分析,不难写出上面的重载函数
Zoo& Zoo::operator+=(Zoo &rhs)
{
    lion += rhs.lion;
    tiger += rhs.tiger;
    return *this;
}
继而过来讨论“+”“-”:
返回值是一个右值

前面说到了,“+”“-”是返回一个新的结果,算术运算符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行时错误。通俗一点,假如算术运算符重载函数返回一个对象的引用,这个引用是两个操作的计算结果,它的本体就会是一个局部变量(对象),返回一个局部变量的引用,是错误的。所以对于类算术运算符的重载,只能返回一个右值。

Zoo operator+(Zoo &first, Zoo &second)
{
    Zoo ret(first);//拷贝构造函数,构造一个局部变量,用于返回值
    ret += second;//运算操作
 
    return ret;//返回一个值
}
二目算术运算符重载通常使用友元运算符重载方式。
从上面也可以看出,类运算符的重载最好与内置运算符保持一致,虽然没硬性规定,但这俨然成了一个默认规定。

另外 !(逻辑反)、~(按位与)、-(负号)等与二目算术运算符有类似之处,那就他们都不会修改原对象数据成员,而是将运算结果交给一个新值,所以在重载时,需要构造一个临时对象作为返回值,返回值也就同样不能是引用。

3.4、输入输出操作符重载

支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同。

1、输出操作符 <<  的重载

为了与I/O标准库一致,操作符应接受 ostream& 作为第一个形参,对类类型 const 对象的引用作为第二个形参,并返回对 ostream 形参的引用。

重载输出操作符可能相对于比较难理解,这里简单的说下,我们只能以自定义类的友元函数的形式重载这两个运算符,这是因为如果我们用成员函数的形式来重载的话,就要改动系统的流类 istream 和 ostream 定义,这是C++不允许的,如果不定义为友元函数的话,将无法调用类对象成员数据输出。

ostream& operator<<(ostream& stream, const Zoo &object)
{
    //对object所引用的对象的数据进行的输出操作
    stream << object.lion;
    stream << object.tiger;
 
    return stream;
}
我们来看看上面这个输出操作符重载函数,第一个参数是 ostream 类的引用,而函数的返回值也是 ostream 类的引用。毫无疑问,我们调用这个运算符重载函数时。实参肯定是 cout,这样就造成了这样一种情况:实参 stream 引用 cout,而函数的返回值又引用 stream,等于函数返回值引用的实体还是 cout。这样做的目的是实现了连续的输出操作。当执行下面语句:

cout << zoo_a << zoo_b;
    //上面 cout << zoo_a 实质就是调用 operator<<(cout, zoo_a),然后返回 cout
    //下一个 << 就相当于执行 cout << zoo_b, 同上
我们不能将该操作符重载函数定义为类的成员函数,否则,左操作数将只能是该类类型的对象。IO操作符通常要对非公用数据成员进行读写,因此,类通常将IO操作符(输入输出)设为友元。
2、输入操作符 >> 的重载

为了与IO标准库一致,操作符应接受 istream& 作为第一个形参,指向它要读的流,并且返回的也是对同一个流的引用(链式操作)。它的第二个形参是对要读入的对象的非 const 引用,该形参必须为非 const,因为输入操作符的目的是将数据读到这个对象中。

更重要但通常重视不够的是,输入和输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。

输入期间的错误:任何读操作都可能因为提供的值不正确而失败;任何读入都可能碰到输入流中的文件结束或其他一些错误。也就需要对输入进行附加检查,发现有这些错误就需要我们进行处理。

3.5、不能重载的运算符 &&、|| 和 , 操作符

和 C 一样,C++ 对于真假值表达式采用所谓骤死式评估方式。意思是一旦该表达式的真假值去顶,纵使表达式中还有部分尚未检验,整个评估工作仍告结束。比如下面这种情况:

char *p;
……
if ((p != NULL) && (strlen(p) > 10) ……
你无需担心调用 strlen 时 p 是否为 NULL 指针,因为如果 p 是否为NULL 的测试结果是否定的,strlen 就绝不会被调用。事实上,对一个 NULL 指针调用 strlen,结果未可预期。
回到重载,C++ 允许我们为用户定制型别量身定做各类操作符,包括 && 和 ||,操作符重载语义上是允许的,但是我们要考虑重载会不会改变对应内置操作符的规则。拿 && 和 || 来说,重载则是对 operator && 和 operator || 两函数进行重载工作,值得注意的是,函数调用语义将会取代骤死式语义,也就是说,如果你将operator && 重载,下面这个虱子:

if (expression1 && expression2) ……
会被编译器视为以下两者之一:
if (expression1.operator&&(expression2)) ……
//假设 operator&& 是个 member function
if (operator&&(expression1, expression2)) ……
//假设 operator&& 是个全局函数
上面函数调用语义和所谓骤死式语义有两个重大的不同。第一,当函数调用动作被执行起来,所有参数值都必须评估完成,所以当我们调用 operator&& 和 operator|| 时,两个参数都已评估完成,没有什么骤死式语义。第二,C++ 语言规格并未明定函数调用动作中各参数的评估次序,所以没办法知道 expression1 和 expression2 哪个会先被评估,而内置的真假值表达式,则总是由左向右评估其自变量。
C++ 中,运算符重载的一个重要参考就是:不能修改运算符的内置语义。
————————————————
版权声明:本文为CSDN博主「selfimpr1991」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wenqian1991/article/details/34147997

C++运算符重载 ++,--,+,-,+=,-=,输出输入运算符相关推荐

  1. 运算符重载之友元运算符重载

    友元可以参考:https://blog.csdn.net/aaqian1/article/details/84427884 友元运算符重载函数: 把运算符重载函数定义为某个类的友元函数. 1.定义友元 ...

  2. 运算符重载之成员运算符重载

    成员运算符重载函数 成员运算符重载函数,即把运算符重载函数定义为某个类的成员函数. 1.定义成员运算符重载函数的语法形式 (1)在类的内部,定义成员运算符重载函数的格式如下: 函数类型 operato ...

  3. c++运算符重载与输入输出流重载

    运算符重载 运算符重载--赋予运算符具有操作自定义类型数据功能 运算符重载本身也是一种函数,我们有两种写法--友元类运算符重载函数和类运算符重载 运算符重载的实质本身就是函数调用 我们要注意它的写法: ...

  4. c 运算符重载前置++_C ++运算符重载–综合指南

    c 运算符重载前置++ Hello, folks! In this article, we will understand a very interesting yet magical power p ...

  5. python 运算符重载_Python3面向对象-运算符重载

    1:运算符重载介绍 运算符重载,就是在某个类的方法中,拦截其内置的操作(比如:+,-,*,/,比较,属性访问,等等),使其实例的行为接近内置类型. 当类的实例出现在内置操作中时(比如:两个实例相加 + ...

  6. c++,运算符重载,左移运算符自增运算符。

    假设有类A,它的私有成员是一个整型变量a_ A a; 我们想要把它的私有成员直接用 cout<<a<<endl 输出的话要用到左移运算符的重载. 我们把<<运算符重 ...

  7. C++_类和对象_C++运算符重载_函数调用运算符重载_---C++语言工作笔记060

    然后我们再来看函数调用运算符重载, 其实这个,重载后的,函数调用运算符有点像,仿函数,什么是仿函数,一会我们再说 我们去写一个MyPrint类 里面重载函数调用运算符,可以看到函数调用运算符的重载的写 ...

  8. C++_类和对象_C++运算符重载_加号运算符重载_实现两个对象相加_对象和int类型相加_通过成员函数重载+号_全局函数重载+号_以及重载_运算符重载函数实现---C++语言工作笔记055

    然后我们再来看,运算符的重载,首先我们来看加号的运算符的重载. 这个的作用是很明显的,比如我们有两个Person对象,p1,p2,如果我们想 p3 = p1+p2能可以嘛,不可以对吧,因为系统给我们提 ...

  9. 运算符重载之左移运算符重载

    左移运算符重载 #include<iostream> using namespace std; class wood {friend ostream& operator<&l ...

  10. C++ 学习 ::【基础篇:17】:C++ 类与对象:运算符重载介绍、运算符重载函数(类内与类外区别)写法及简单设计实现

    本系列 C++ 相关文章 仅为笔者学习笔记记录,用自己的理解记录学习!C++ 学习系列将分为三个阶段:基础篇.STL 篇.高阶数据结构与算法篇,相关重点内容如下: 基础篇:类与对象(涉及C++的三大特 ...

最新文章

  1. ecshop订单-》待付款,待发货,待收货,收货确认
  2. python xpath语法-Python爬虫基础之XPath语法与lxml库的用法详解
  3. mahout安装测试
  4. java 二维数组位置_请完成下列Java程序:查找一个矩阵中的鞍点,对于一个二维数组中的鞍点,该点位置上的元素在该行上...
  5. 浅谈python socket编程
  6. View的缩放操作--CGAffineTransformMakeScale:
  7. Android 系统性能优化(11)---UC性能优化方案
  8. 【Maven】log4j-slf4j-impl cannot be present with log4j-to-slf4j
  9. Linux下的系统排错以及无图形修改root密码
  10. JQuery jsonp使用小记
  11. netsuite和java_Netsuite - 如何根据项目和位置搜索填充子列表
  12. 利用Swoole编写一个TCP服务器,顺带测试下Swoole的4层生命周期
  13. 利润表模板excel_让财务人看完心动的369个Excel财务分析图表,老板都忍不住点赞...
  14. Arduino开发ESP8266之ADS1115模数转换
  15. BUUCTF-CRYPTO-强网杯2019 Copperstudy
  16. 国际短信通道短信后台软件路由流程—移讯云短信系统
  17. [数据分析] [保姆级教程] 数据差异分析方法
  18. OpenGL3D图形、旋转、纹理、键盘移动、光照、滤波、透明(完整)
  19. 递增有序顺序表的插入 (20分) 实验目的:1、掌握线性表的基本知识 2、深入理解、掌握并灵活运用线性表。3、熟练掌握线性表的存储结构及主要运算的实现 已知顺序表L递增有序,将X插入到线性表的适当位置
  20. UEFI 及 legacy 重装系统

热门文章

  1. Android 图片压缩的几种方法
  2. 区块链名词解析:ICO、IFO、IEO和IMO,分别是什么呢?
  3. Flink 实现Locality 模式调度
  4. python如何编写温度转换_用python写温度转换
  5. table td 调整margin无效
  6. GitHub开源组件集锦
  7. mac误删除文件恢复,mac文件丢失如何找回
  8. Ubuntu18.04+TITAN XP+anaconda+cuda10+cudnn+pytorch
  9. AR涂涂乐⭐九、视频展示及涂涂乐源文件
  10. JS时间戳进行判断,判断是否超时三十分钟