C++Primer5th 第十四章 重载运算与类型转换


第十四章 重载运算与类型转换

14.1 基本概念

重载运算符是函数,名字比较特殊: 由关键字operator和定义的运算符号共同组成,包含返回类型,参数列表和函数体

重载运算符的参数的数量与该运算符作用的运算数量一致

  • 一元运算符有一个参数
  • 二元运算符有两个参数
  • 但当重载运算符是成员函数时,第一个参数(左侧)默认是this,此时参数数量比运算对象的数量少一个

除了operator(),其余不可含有默认参数

运算符函数,它或是类的成员,或是至少含有一个类类型的参数

  • 这意味着不能改变作用于内置类型的运算对象

重载运算符优先级和结合律规律不变

调用运算符的方式
//非成员运算符函数
data1 + data2;
operator+ (data1,data2);
//成员运算符函数,跟成员函数的调用一致
data1 += data2;
data1.operator+=(data2);
不应该被重载的运算符
  • 逻辑与& ,逻辑或| , 逗号运算符 , , 有求值顺序
  • && 和 || 短路求值属性
  • 取地址符号& 和 逗号运算符 , , 已经定义了用于类类型对象时候的特殊含义
使用与内置类型一致的含义
  • 要考虑是否需要重载,重载的逻辑含义应与内置类型是一致的, 而且当你重载了其中一个,与其匹配的通常也需要
    • 例如重载== 就要重载 != ;重载<,就要重载对应的其他的
赋值和复合赋值运算符
  • 赋值运算符的行为:赋值后,左侧和右侧对象值相等,赋值运算符返回左侧运算对象的引用
    • 重载的赋值运算符要继承内置版本的含义
选择作为成员或者非成员
重载时首先决定重载为类的成员还是非成员函数
 1. 赋值,下标,调用,成员访问箭头必须是成员
 2. 复合赋值运算符一般来说是成员
 3. 改变对象状态的的运算符,例如递增,递减,解引用通常是成员
 4. 具有对称性运算符可能转换任何一端的对象,例如算术,相等,关系,位运算等通常为非成员


14.2 输入和输出运算符

<< 和 >> 执行内置类型的版本, 类需要自定义适合其对象的新版本

14.2.1 重载输出运算符<<

  • 通常情况,输出运算符第一个形参是非常量的ostream对象的引用
    • 非常量因为向流写内容会改变其状态
    • 引用是因为无法直接赋值一个ostream对象
  • 第二个形参是常量的引用
    • 常量是因为打印不改变对象内容
    • 引用是为了避免拷贝实参
//重载类的输出运算符
ostream& operator<<(ostream &os, const ClassName &it) {
    os << it.isbn() << " ";
    return os;
}
输出运算符尽量减少格式化操作
  • 给用户足够的自由,主要负责打印内容而不是控制格式,输出运算符不应该打印换行符
输入输出运算符必须是非成员函数
  • 为了与iostream标准库的IO兼容, 重载的需要是普通的非成员函数,同时IO需要声明为类的友元

14.2.2 重载输入运算符>>

  • 其第二个对象必须是非常量,因为读取的流要读入到这个对象
istream &operator>>(istream &is, ClassName &it) {
    int c;
    if (is) 
        it.revenue = item.sold * price;
    else
        it = ClassName(); //默认初始化
    return is;
}
  • 输入运算符需要考虑输入可是失败的情况,并进行处理
输入时的错误
  • 可能发生的错误:
    • 流含有错误类型的数据读取操作可能失败
    • 读取至文件末尾或遇到输入流的其他错误
  • 出错后输入运算符应该负责从错误中恢复


14.3 算术和关系运算符

  • 通常情况下,算术和关系运算定义为非成员函数,为了实现允许左右侧对象进行转换, 由于不需改变运算对象的状态,因此形参均是常量的引用
  • 如果类定义了算术运算符,一般也应定义复合赋值运算符,此时,最好用复合赋值运算符来定义算术运算符
ClassName operator+(const ClassName &l, const ClassName &r) {
    ClassName sum = l;
    sum += r;  //用复合赋值运算符来定义算术运算符
    return sum;
}

14.3.1 相等运算符

  • 返回类型是bool类型,定义一个== ,再用== 来定义!= , 意味着其中一个来定义, 另一个则只是调用定义的

14.3.2 关系运算符

  • 由于一些关联容器和算法需要用到小于运算符,因此定义operator<比较有用
  • 如果存在唯一的逻辑可靠的<定义,应该定义<运算符,如果同时包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符


14.4 赋值运算符

  • 我们可以重载赋值运算符,不论形参,赋值运算符都必须定义为成员函数
  • 复合赋值运算符也应该定义成类的成员,这两类运算符都应该返回左侧运算对象的引用


14.5 下标运算符

  • operator[]
  • 下标运算符必须是成员函数
  • 下标运算符通常以所访问元素的引用值作为返回值 ,这样做下标可以作为左值或右值
  • 如果一个类包含下标运算符,则它会定义两个版本:一个返回普通的引用,另一个是类的常量成员并返回常量引用


14.6 递增和递减运算符

  • 由于递增递减改变对象的状态,因此最好把他们设定为成员函数
  • 并且应该同时定义前置版本和后置版本
定义前置版本
  • 前置运算符应该返回递增或递减后对象的引用
区分前置和后置
  • 由于都是++或–,所以重载运算符无法区分,因此后置版本接受额外的int类型的参数,但是并不被使用,只是为了区分
ClassName & operator++()  //前置
ClassName operator++(int)  //后置,定义时也无需为用不到的形参命名
  • 为了与内置版本保持一致,后置运算符返回对象的原值,返回值而不是引用
显示地调用后置运算符
  • 如果使用函数调用的方式使用后置版本,必须给它的整形参数传值
ClassName p(a1);
p.operator++(0);  //后置版本,必须传值以告诉编译器使用后置版本
p.operator++();   //前置版本


14.7 成员访问运算符

  • 解引用运算符 * 和 箭头运算符 ->
  • * 通常是类成员, -> 必须是类成员
限定箭头运算符返回值
  • 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象


14.8 函数调用运算符

调用对象实际上是运行重载的调用运算符,调用运算符必须是成员函数

  • 定义了调用运算符的类称为 函数对象

14.8.1 lambda是函数对象

当我们编写了lambda后,编译器将该表达式翻译成了一个未命名类的未命名的对象,lambda表达式产生的类中含有一个重载的函数调用运算符

  • 引用捕获,要由程序确保捕获值存在
  • 值捕获,lambda产生的类需要有构造函数以及为每一个值捕获变量建立对应的数据成员

14.8.2 标准库定义的函数对象

标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行明明操作的调用运算符,这些类被定义为模板类

  • 作用:通常用于代替算法中的默认运算符

14.8.3 可调用对象与function

C++中可调用对象:函数、函数指针、lambda表达式、bind创建的对象、重载函数调用的类

  • 两个不同类型的可调用对象可以有一种调用形式,调用形式指明了返回类型和实参类型

    • 例如: int(int, int) 是一个函数类型,接受两个int,返回一个int
  • 对于具有相同调用形式的可调用对象,如果希望定义一个函数表来存储这些可调用对象

    • 方法一:使用map,构建运算符到指针的映射
      • 例如map<string, int(*)(int, int)> binops;
      • 创建了一个字符串到函数指针的映射 binops
      • 插入可调用对象:binops.insert({"+", add});
      • 但是就不可以插入lambda,因为它是个类,不是函数指针
      • 但是无法处理不同类的可调用对象
    • 方法二:标准库的function类型
      • 在头文件functional中
      • function是一个模板
      • function<int(int, int)>
      • function就可以处理所有调用形式相同的可调用对象
    • 两个结合
      • map<string, function<int(int, int)>> binops
  • 重载的函数不能用名字存入,可以用函数指针或者是用lambda表达式

14.9 重载、类型转换与运算符

用户定义的类型转换(类类型转换):由转换构造函数和类型转换运算符共同定义。

14.9.1 类型转换运算符

  • 类型转换运算符是类的一种特殊成员函数,负责将一个类类型的值转换成其他类型。
  • 类型转换运算符必须是类的成员函数,灭有返回类型,形参列表为空,类型转换函数应该是const
    • operator type() const; //type表示某种可作为函数返回类型的类型
定义含有类型转换运算符的类
//尽管编译器一次只能执行一个用户定义的类型转换,但是隐式的用户定义类型转换可以置于一个标准类型转换之前或之后。
operator int() const {return val};
SmallInt si =3.14;
//内置类型转换将double实参转换为int
//再调用SmallInt(int)构造函数转换为Smallint类型
  • 类型转换运算是隐式执行的,因此无法给这些函数传递实参
显示的类型转换运算符
  • explicit 不会隐式引用
  • 必须显示地请求类型转换
  • 例外:用作条件时,编译器会将显示的转换被隐式的执行
转换为bool
  • while(std::cin >> value) cin被istream operator bool 类型转换函数隐式地执行了转换

14.9.2 避免二义性的类型转换

必须确保类类型和目标类型只存在唯一转换方式,否则可能出现二义性

  • 两种可能出现二义性的情况
    • 两个类提供相同的类型转换,A类定义接收B类对象的转换构造函数,B类定义了转换目标是A类的类型转换运算符
    • 类定义了多个转换规则,而这些转换规则可通过其他方式转换
  • 类中不要定义两个或两个以上的与算术类型相关的转换

14.9.3 函数匹配与重载运算符

  • 如果我们既定义了转换运算符,又重载了算术运算符,那么可能出现二义性问题
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值