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

一、基本概念

  • 重载运算符是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成。也包含返回类型、参数列表以及函数体。
  • 当一个重载的运算符是成员函数时,this绑定到左侧运算对象。动态运算符符函数的参数数量比运算对象的数量少一个
  • 对于一个运算符函数来说,它或者是类的成员,或者至少含有一个类类型的参数。
  • 只能重载大多数的运算符,而不能发明新的运算符号。
  • 重载运算符的优先级和结合律跟对应的内置运算符保持一致。
  • 通常情况下,不应该重载逗号、取地址、逻辑与、逻辑或运算符。
可以被重载 不可以被重载
+ - * / % ^ :: .* . ?:
& ~ ! , =
< > <= >= ++ --
<< >> == != &&
+= -= /= %= ^= &=
*= <<= >>= [] ()
-> ->* new new[] delete delete[]
  • 调用方式:

    • data1 + data2;
    • operator+(data1, data2);
  • 是否是成员函数
    • 赋值(=)、下标([])、调用(())、成员访问运算符(->) 必须是成员
    • 复合赋值运算符一般来说应该是成员,但并非必须。
    • 改变对象状态的运算符或与给定类型密相关的运算符,如递增、递减和解引用运算符,通常是成员
    • 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,通常是非成员

二、输入和输出运算符

1. 重载输出运算符 <<

  • 通常情况下,输出运算符的第一个形参是一个非常量的引用。非常量是因为向流中写入会改变其状态;引用是因为我们无法复制一个ostream对象。
  • 通常情况下,输出运算符的第二个形参是一个常量的引用,常量时因为打印对象不会改变对象的内容;引用是希望避免复制实参。
ostream &operator<<(ostream &os, const &item) {
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
return os;
}

2. 重载输入运算符 >>

  • 通常情况下,输入运算符的第一个参数是运算符将要读取的流的引用,第二个形参是将要读入到的非常量对象的引用
  • 输入运算符必须处理输入可能失败的情况,而输出运算符不需要。
istream &operator>>(istream &is, Data &item) {
double pirce;
is >> item.isbn >> item.units_sold >> price; if (is)
item.revenue = item.units_sold * price;
else
item = Data(); return is;
}

三、算术和关系运算符

  • 如果类同时定义了算数运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算数运算符。

1. 相等运算符

  • 如果定义了operator==,则这个类也应该定义operator!=
  • 相等运算符和不等运算符的一个应该把工作委托给另一个。
  • 相等运算符应该具有传递性。
  • 如果某个类在逻辑上有相等性的含义,则该类应该定义operator==,这样做可以使用户更容易使用标准库算法来处理这个类。

2. 关系运算符

  • 如果存在唯一一种逻辑可靠的<定义,则应该考虑为这个类定义<运算符。如果同时还包含==,则当且仅当<的定义和++产生的结果一直时才定义<运算符。

四、赋值运算符

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

五、下标运算符

  • 下标运算符必须是成员函数
  • 一般会定义两个版本:
    • 1.返回普通引用。
    • 2.类的常量成员,并返回常量引用。
class StrVec {
public:
std::string& operator[](std::size_t n) { return elements[n]; }
const std::string& operator[](std::size_t n) const {return elements[n]; }
private:
std::string *elements;
};

六、递增和递减运算符

  • 定义递增和递减运算符的类应该同时定义前置版本和后置版本。通常应该被定义成类的成员。
  • 为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用
  • 递增和递减运算符的工作机理:检查值是否有效,现在给定的索引值是否有效,如果check函数没有抛出异常,则运算符返回对象的引用。
class StrBloPtr {
public:
// 递增和递减前置运算符
StrBloPtr& operator++();
StrBloPtr& operator--();
}; StrBloPtr& StrBloPtr::operator++() {
// 如果curr已经指向了容器的尾后位置,则无法递增它
check(curr, "++");
++curr;
return *this;
} StrBloPtr& StrBloPtr::operator--() {
// 如果curr是0,则继续递减它将产生一个无效下标
--curr;
check(curr, "--");
return *this;
}
  • 由于前置和后缀版本使用的是同一个符号,意味着其重载版本所用的名字将是相同的,并且运算对象的数量和类型也相同。所以普通的重载形式无法区分这两种情况。
  • 为了解决这个情况,后置版本接受一个额外的,不被使用的int类型的形参。因为不会用到,所以无需命名。
  • 为了和内置版本保持一致,后置运算符应该返回递增或递减前对象的值,而不是引用
class StrBoldPtr {
public:
// 递增和递减后置运算符
StrBlodPtr operator++(int);
StrBlodPtr operator--(int);
}; StrBoldPtr StrBoldPtr::operator++(int) {
StrBoldPtr ret = *this; // 记录当前的值
++*this; // 前置++需要检查有效性
return ret; // 返回之前记录的状态
} StrBoldPtr StrBoldPtr::operator--(int) {
StrBoldPtr ret = *this; // 记录当前的值
--*this; // 前置--需要检查有效性
return ret; // 返回之前记录的状态
}

七、成员访问运算符

  • 箭头运算符必须是类的成员。解引用运算符通常也是类的成员,景观并非必须如此。
  • 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
  • 解引用和乘法的区别是一个是一元运算符,一个是二元运算符。

八、函数调用运算符

  • 可以像使用函数一样,调用该类的对象。因为这样对待类同时也能存储状态,所以与普通函数相比更加灵活。
  • 函数调用运算符必须是成员函数。
  • 一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。
  • 如果累定义了调用运算符,则该类的对象称作函数对象。

1. lambda是函数对象

  • lambda捕获变量:lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数。

2. 标准库定义的函数对象

  • 可以在算法中使用标准库函数对象
算术 关系 逻辑
plus equal_to logical_and
minus not_equal_to logical_or
multiplies greater logical_not
divides greater_equal
modulus less
negate less_equal

3. 可调用对象与function

操作 解释
function f; f是一个用来存储可调用对象的空function,这些可调用对象的调用形式应该与类型T相同。
function f(nullptr); 显式地构造一个空function。
function f(obj) 在f中存储可调用对象obj的副本。
f 将f作为条件:当f含有一个可调用对象时为真;否则为假。
定义为function的成员的类型
result_type 该function类型的可调用对象返回的类型。
argument_type 当T有一个或两个实参时定义的类型。如果T只有一个实参,则argument_type。
first_argument_type 第一个实参的类型。
second_argument_type 第二个实参的类型。

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

1. 类型转换运算符

  • 类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式为:operator type() const;
  • 一个类型转换函数必须是类的成员函数。它不能声明返回类型,形参列表也必须为空。类型转换函数通常应该是const。
  • 避免过度使用类型转换函数。
  • C++11引入了显式的类型转换运算符(explicit operator)。当表达式出现在下列位置时,显式的类型转换将被隐式地执行:
    • if、while及do语句的条件部分
    • for语句头的条件表达式
    • !、||、&&的运算对象
    • 条件运算符的条件表达式
  • 向bool的类型转换通常用在条件部分,因此operator bool一般定义成explicit的。

2. 避免有二义性的类型转换

  • 通常情况下,不要为类定义相同的类型转换,也不要在类中定义两个及两个以上转换源或转换目标是算术类型的转换。
  • 在调用重载函数时,如果需要额外的标准类型转换,该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型转换不止一个,则该调用具有二义性。
  • 当使用两个用户定义的类型转换时,如果转换函数之前或之后存在标准类型转换,则标准类型转换将决定最佳匹配到底时哪个。

3. 函数匹配和重载运算符

  • 如果a是一种类型,则表达式a sym b可能是:

    • a.operatorsym(b);
    • operatorsym(a,b);
  • 如果我们对同一类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将会遇到重载运算符与内置运算符的二义性问题。

【C++】《C++ Primer 》第十四章的更多相关文章

  1. C++ Primer : : 第十四章 : 重载运算符与类型转换之类型转换运算符和重载匹配

    类型转换运算符 class SmallInt { public: SmallInt(int i = 0) : val(i) { if (i < 0 || i > 255) throw st ...

  2. C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符

    重载前须知 重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成. 因为重载运算符时函数, 因此它包含返回值.参数列表和函数体. 对于重载运算符是成员函数时, 它的第一个 ...

  3. C++Primer 第十四章

    //1.当运算符作用于类类型运算对象时,可以通过运算符重载重新定义该运算符的含义.明智的使用运算符重载能令程序更加易于编写和阅读. //2.重载的运算符是具有特殊名字的函数,它们由关键字operato ...

  4. C++ Primer Plus学习:第十四章

    第十四章 C++中的代码重用 包含对象成员的类 将类的对象作为新类的成员.称为has-a关系.使用公有继承的时候,类可以继承接口,可能还有实现(纯虚函数不提供实现,只提供接口).使用包含时,可以获得实 ...

  5. C Primer Plus_第四章_字符串和格式化输入输出_编程练习

    Practice 1.输入名字和姓氏,以"名字,姓氏"的格式输出打印. #include int main(void) { char name[20]; char family[2 ...

  6. 《Linux命令行与shell脚本编程大全》 第十四章 学习笔记

    第十四章:呈现数据 理解输入与输出 标准文件描述符 文件描述符 缩写 描述 0 STDIN 标准输入 1 STDOUT 标准输出 2 STDERR 标准错误 1.STDIN 代表标准输入.对于终端界面 ...

  7. perl 第十四章 Perl5的包和模块

    第十四章 Perl5的包和模块 by flamephoenix 一.require函数  1.require函数和子程序库  2.用require指定Perl版本二.包  1.包的定义  2.在包间切 ...

  8. Gradle 1.12 翻译——第十四章. 教程 - 杂七杂八

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  9. C和指针 (pointers on C)——第十四章:预处理器

    第十四章 预处理器 我跳过了先进的指针主题的章节. 太多的技巧,太学科不适合今天的我.但我真的读,读懂.假设谁读了私下能够交流一下.有的小技巧还是非常有意思. 预处理器这一章的内容.大家肯定都用过.什 ...

随机推荐

  1. angular 双向数据绑定与vue数据的双向数据绑定

    二者都是 MVVM 模式开发的典型代表 angular 是通过脏检测实现,angular 会将 UI 事件,请求事件,settimeout 这类延迟的对象放入到事件监测的脏队列,当数据变化的时候,触发 ...

  2. 题解 CF1446D2 【Frequency Problem (Hard Version)】

    给出一个跑得快一点的做法,洛谷最优解 (时间是第二名的 \(\frac{1}{2}\)), CF 第一页 D1 首先找到整个序列的众数 \(G\), 很容易证明答案序列中的两个众数中其中一个是 \(G ...

  3. 在虚拟机中安装Linux系统CentOS7详细教程!!!超详细!!!!一看就会!!!手把手教学!!!

    一.CentOS的下载 CentOS是免费版,推荐在官网上直接下载.https://www.centos.org/download/ DVD ISO:普通光盘完整安装版镜像,可离线安装到计算机硬盘上, ...

  4. .Net Core 学习之旅知乎版

    @[yuyue](.Net Core 学习之旅-.netCore Developer RoadMap) # .Net Core 学习之旅 随着.NET5.O 的正式推出,微软的VS大一统目的逐步成型, ...

  5. 测开之数据类型&#183; 第3篇《列表推导式、字典推导式、2种方式创建生成器》

    坚持原创输出,点击蓝字关注我吧 作者:清菡 博客:oschina.云+社区.知乎等各大平台都有. 目录 一.列表推导式 二.字典推导式 三.2种方式创建生成器 1.生成器表达式 2.函数里面,通过 y ...

  6. 移动端 canvas基础1

    一.canvas画布 Canvas是HTML5中新出的一个元素,开发者可以通过JS脚本动态绘制图像. #1. 创建canvas画布 在页面中创建canvas标签,并设置其id和宽高 (不要通过css设 ...

  7. js--数组的filter()过滤方法的使用

    前言 你还在通过for循环遍历数组吗?你还在遍历之后一项一项的通过if判断过滤你需要的数据吗?你还在写着一大堆代码实现一个简单的过滤数据功能吗?那么,今天他来了.他就是这里要介绍的es6中数组filt ...

  8. Liunx运维(五)-信息显示与搜索文件命令

    文档目录: 一.uname:显示系统信息 二.hostname:显示或设置系统的主机名 三.dmesg:系统启动异常诊断 四.stat:显示文件或文件系统状态 五.du:统计磁盘空间使用情况 六.da ...

  9. 一场由fork引发的超时,让我们重新探讨了Redis的抖动问题

    摘要:一次由fork引发的时延抖动问题. 背景介绍 华为云数据库GaussDB(for Redis) 是一款基于计算存储分离架构,兼容Redis生态的云原生NoSQL数据库:它依靠共享存储池实现了强一 ...

  10. 如何去掉ul和li前面的小黑点

    做网站的时候经常会遇到如上图所示的小圆点,难看不说,还容易影响布局,下面就介绍几种消除小圆点的方法: 1. 找到相关CSS文件,在.ul 和.li 部分添加: 1 list-style: none; ...