前言
探索C++的旅程是一场充满挑战与成就的冒险,每一行代码都是思维的延伸,每一个语法细节背后都藏着设计的智慧。在这里,记录的不只是规则的集合,更是理解、调试与突破的过程。希望这些笔记能成为未来回溯的路标,也愿它们为同样踏上这条路的人点亮一盏微光。希望自己能在热爱的道路上发光发亮。
注:本系列的章节是参考C++ Primer的目录,但是每章的知识点全是个人的理解以及补充
第十四章—重载运算与类型转换
14.1 基本概念
-
运算符重载
- 形式:函数返回类型 operator运算符(参数列表)
-
为什么要进行运算符重载
- 为了使用户自定义自定义类型的数据的操作与内置数据类型的数据操作形式一致
- C++中预定义的运算符重载为类的成员函数或者友元函数,使得对用户的自定义数据类型的数据(对象)的操作形式与C++内部定义的数据一致、
- 运算符重载的实质就是函数重载或函数多态
-
运算符重载的规则
- 当一个重载运算符是成员函数时,this指针绑定到左侧运算对象上,成员运算符的函数的(显式)参数数量比运算对象的数量少一个
- 为了防止用户对标准类型进行运算符重载,C++规定重载的运算符的操作对象必须至少有一个是由自定义类型或者枚举类型
- 重载运算符之后,其优先级和结合性还是固定不变的
- 重载并不会改变运算符的用法,原来有几个操作数、操作数在左边还是右边,这些都不会改变
- 重载运算符函数不能有默认参数,否则就改变了运算符操纵数的个数
- 重载逻辑运算符(&&、||)后,不在具备短路求值的特性
- 逻辑运算符(与、或)和逗号运算符的运算对象求值顺序规则无法被保留下来
- 不能臆造一个并不存在的运算符(@$)
- 通常情况下,不应该重载逗号、取地址、逻辑运算符,这样会导致使用类的用户无法适应
- 只有当操作的含义对于用户来说清晰明了的时候才使用重载运算符,如果用户对运算符可能有几种不同的理解,则使用这样的运算符将产生二义性
-
不可以重载的运算符
- .(点运算符):底层对象寻址依赖,重载回破坏类的类型安全
- ::(作用域解析符):编译期绑定,与运行时多态冲突
- ?:(条件运算符):三目逻辑复杂,重载易导致歧义
- sizeof,typeid:编译器内置类型,无法被修改
要注意啊sizeof和typeid不是一个函数,而是一个运算符
-
重载运算符的调用形式
- data1+data2; //普通调用
- operator+(data1,data2) //显示调用(等价于函数的调用)
-
重载运算符应该与使用内置类型一致的含义
- 如果类执行IO操作,则定义移位运算符使其与内置类型的IO保持一致
- 如果类的某个操作是检查相等性,则定义operator==;如果类有了operator==,意味通常会有operator!=
- 如果类包含一个内在的单序比较操作,则定义operator<;如果类有了operator<,则应该还有其他关系的操作
- 重载运算符的返回值应该和内置类型相兼容。
补充赋值运算符的内置类型:赋值之后,左侧运算对象与右侧运算对象的值相等,并且运算符应该返回它左侧运算对象的一个引用。
-
运算符重载的形式
- 采用普通函数的重载形式
-
代码演示
#include <iostream> #include <math.h> using namespace std; struct Point { public: Point(int _ix = 0, int _iy = 0) : ix(_ix) , iy(_iy) {} ~Point() { cout << "~Point()" << endl; } int getIx() const { return ix; } int getIy() const { return iy; } void printf() const { cout << "(" << ix << "," << iy << ")"; } private: int ix; int iy; }; Point operator+(const Point& PT1, const Point& PT2) { return Point ((PT1.getIx() + PT2.getIx()), (PT1.getIy() + PT2.getIy())); } void test0() { Point pt1(100, 2); Point pt2(2, 1); Point pt3 = pt1 + pt2; pt3.printf(); cout << endl; } int main() { test0(); }
具有对称性的运算符可能转换任意一端的运算对象,例如:算术、相等性、关系和位运算符,通常设为普通函数
b. 采用成员函数的重载形式
-
代码演示
#include <iostream> #include <math.h> using namespace std; struct Point { public: Point(int _ix = 0, int _iy = 0) : ix(_ix) , iy(_iy) {} ~Point() { cout << "~Point()" << endl; } int getIx() const { return ix; } int getIy() const { return iy; } void printf() const { cout << "(" << ix << "," << iy << ")"; } Point operator+( const Point& PT2) { return Point(((*this).getIx() + PT2.getIx()), (this->getIy() + PT2.getIy())); } private: int ix; int iy; }; void test0() { Point pt1(100, 2); Point pt2(2, 1); Point pt3 = pt1 + pt2; pt3.printf(); cout << endl; } int main() { test0(); }
- 当我们把运算符定义为成员函数的时候,它的左侧运算对象必须是运算符所属类的一个对象
- 赋值(=)、下标([ ])、调用( ())、和成员访问箭头运算符必须是成员函数,因为,对象本身发生了改变
- 复合赋值运算符一般应该为成员函数
- 改变对象状态的运算符或者给定类型密切相关的运算符(递增、递减、解引用)运算符,通常为成员
-
代码演示
#include <iostream> #include <math.h> using namespace std; struct Point { public: Point(int _ix = 0, int _iy = 0) : ix(_ix) , iy(_iy) {} ~Point() { cout << "~Point()" << endl; } int getIx() const { return ix; } int getIy() const { return iy; } void printf() const { cout << "(" << ix << "," << iy << ")"; } friend Point operator+(const Point pt1, const Point& pt2); private: int ix; int iy; }; Point operator+(const Point pt1, const Point& pt2) { return Point((pt1.ix + pt2.ix), (pt1.iy + pt2.iy)); } void test0() { Point pt1(100, 2); Point pt2(2, 1); Point pt3 = pt1 + pt2; pt3.printf(); cout << endl; } int main() { test0(); }
重载输出输入运算符一般用友元函数(详细解析请看下一张小节)