声明:
- 文中内容收集整理自《C++ Primer 中文版 (第5版)》,版权归原书所有。
- 学习一门程序设计语言最好的方法就是练习编程
1、当运算符被用于类类型的对象时,C++允许我们为其指定新的定义。也能自定义类类型之间的转换规则。类类型转换隐式地将一种类型的对象转换成另一种我们所需的类型的对象。
一、基本概念
1、重载的运算符有特殊名字的函数:由关键字operator和其后要定义的符号共同决定。参数数量与该运算符的运算对象一样多。
2、如果一个运算符函数是成员函数,则第一个(左侧)运算对象绑定到隐式的this指针上,因此,成员运算符函数的(显式)参数数量比运算符的运算对象总数少一个。
3、对于一个运算符函数来说,或者是类的成员,或者至少含有一个类类型的参数:这一约定表示,当运算符作用于内置类型的运算对象时,无法改变该运算符的含义。
4、使用重载运算符本质上是一次函数的调用,所以关于运算对象求值顺序的规则无法应用到重载的运算符上。
5、通常情况下不应该重载逗号,取地址,逻辑与逻辑或运算符。
6、如果某些操作在逻辑上与运算符相关,则它们适合于定义成重载的运算符:
如果类执行IO操作,则与内置类型IO一致。
如果某个操作检查相等性,则定义operator==,通常也有operator!=。
如果包含一个内在的单序比较,则定义operator<及其他操作。
重载运算符返回类型通常与其内置版本兼容。
7、当内置的运算符和我们的操作之间存在逻辑映射关系时,运算符重载的效果最好。只有当操作的含义对于用户来说清晰明了时才采用运算符。
8、运算符定义为成员函数还是普通函数??
赋值、下标、调用、成员访问箭头运算符必须是成员。
复合赋值运算符一般来说应该是成员。
改变对象状态的运算符或者与给定类型密切相关的运算符,如递增递减解引用,通常为成员。
具有对称性的运算符可能转换成任意一端的运算对象,如算术、相等性、关系和位运算符等,通常为普通的非成员函数。
9、如果要提供含有类对象的混合类型表达式,则运算符必须为非成员函数。
二、输入和输出运算符
1、输出运算符第一个形参是一个非常量ostream对象的引用。因为向流写入内容会改变其状态。第二个形参一般是一个常量的引用。该常量是我们要打印的类类型。
2、输出运算符今年减少格式化操作。不会打印换行符。令输出运算符尽量减少格式化操作可以使用户有权控制输出的细节。
3、输出运算符应该主要负责输出内容而不是控制格式。
4、与iostream标准库兼容的输入输出运算符必须是普通的非成员函数,而不是类的额成员函数。
5、如果我们希望类自定义IO运算符,则必须将其定义为非成员函数。IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般定义为友元。
6、输入运算符第一个形参是运算符将要读取的流的引用,第二个形参是将要读入到的(非常量)对象的引用。该运算符返回某个给定流的引用。第二个形参必须是非常量,因为输入运算符本身目的就是将数据读入到这个对象。
7、输入运算符应该考虑输入可能失败的情况,输出不必。
8、输入错误:
当流含有错误类型的数据时读取操作可能失败。
当读取操作达到文件尾时或者遇到输入流的其他错误也会失败。
9、在程序中不需要逐个检查每个读取操作,而是等读取了所有数据后赶在使用这些数据之前一次性检查。
10、当发生输入错误时,输入运算符应负责从错误中恢复。
11、输入运算符通常设置failbit,此外设置eofbit表示文件耗尽,badbit表示流被破坏,最好用IO标准库标示这些错误。
三、算术和关系运算符
1、通常算术和关系运算符定义为非成员函数以允许对左侧或右侧的运算对象进行转换。
2、如果类定义了算术运算符,则一般会定义一个对应的复合赋值运算符。如果同时定义了算术运算符和复合赋值运算符,则通常情况下应该用复合赋值来实现算术运算符。
3、关系运算符应该定义顺序关系,应该考虑==的情况。。< 比较的时候比较多个成员。
四、赋值运算符
1、拷贝赋值和移动赋值运算符可以把类的一个对象赋值给该类的另一个对象。
2、和拷贝赋值和移动赋值运算符一样,其他重载的赋值运算符也必须先释放当前内存空间,再创建一个新的空间。
3、复合赋值运算符不一定是类的成员,但倾向于把包括复合赋值在内的所有赋值运算都定义在类的内部。
4、赋值运算符必须定义成类的成员,复合赋值运算符也应该这么做,这两类运算符都返回左侧运算对象的引用。
五、下标运算符
1、表示容器的类可以通过元素在容器中的位置访问元素,定义下标运算符operator[] 必须是成员函数。
2、下标运算符通常返回所访问的元素的引用。这样的好处是下标可以出现在赋值运算符的任意一端。最好定义下表运算符的常量版本和非常量版本。
3、当作用于一个常量对象时,下标运算符返回常量引用以确保不会给返回的对象赋值。
六、递增和递减运算符
1、C++语言不要求递增和递减的运算符必须是类的成员,但因为它改变的刚好是所操作对象的状态,所以建议将其设定为成员函数。
2、前置运算符返回递增或递减后对象的引用,后置运算符返回对象的原值。而非引用。
3、后置的运算符接收一个额外的(不被使用)的int类型的形参。这个形参的目的的唯一作用是区分前置版本和后置版本的函数,而不参与运算。
4、后置运算符的最终效果是:对象本身向前移动一个元素,而返回的结果仍然反映对象在未递增之前的原始值。
5、可以显式地调用一个重载的运算符,其效果与在表达式中以运算符的形式使用它一样。如果我们想通过函数调用的方式调用后置版本,则必须为他的整型参数传递一个值。这个值会被运算符函数忽略。
七、成员访问运算符
1、解引用运算符首先检查curr是否仍在作用范围内,如果是,则返回curr所指元素的一个引用。箭头运算符不执行任何自己的操作,而是调用解引用运算符并返回解引用结果元素的地址。
2、箭头运算符重载时不能丢掉成员访问这个最基本的含义。但可以改变箭头从哪个对象中获取成员。
3、重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
八、函数调用运算符
1、如果一个类重载了函数调用运算符,则可以像使用函数一样使用该类的对象。调用对象实际上是在运行重载的调用运算符。
2、函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,互相之间应该在参数数量或类型上有所区别。
3、当一个lambda表达式通过引用捕获变量时,将由程序负责确保lambda执行时引用所引的对象缺失存在。因此,编译器可以直接使用该引用而无需在lambda产生的类中将其存储为数据成员。
4、相反,通过值捕获的变量被拷贝到lambda中。因此,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。
5、lambda表达式产生的类不含默认构造函数、赋值运算符及默认析构函数;它是否含默认拷贝、移动构造函数通常视捕获的数据成员类型决定。
九、重载、类型转换与运算符
1、类类型转换运算符(conversion operator)负责将一个类类型的值转换为其他类型。
形式: operator type() const;
2、编译器通常不会将一个显式的类型转换运算符用于隐式类型转换。
3、当表达式出现在下列位置时,显式的类型转换会被隐式地执行:
·if、while及do语句的条件部分
·for语句头的条件表达式
·逻辑非运算、或、与运算的对象
·条件运算符(?:)表达式
4、向bool的类型转换通常用在条件部分,因此operator bool一般定义成explict的。
5、如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间只存在唯一一种转换方式,否则容易出现二义性。
6、在两种情况下可能产生多重转换路径。第一种是两个类提供相同的类型转换,如A类定义了个一个接受B类对象的转换构造函数,B类定义了一个转换目标是A类的类型转换运算符时,我们说他们提供了相同的类型转换。
另一种情况是类定义了多个转换规则。这些转换涉及的类型本身可以通过其他类型转换联系在一起。典型的例子是算术运算符,对于某个给定的类,最好只定义最多一个与算术类型有关的转换规则。
7、通常情况下,不要为类定义相同的类型转换,也不要在类中定义两个及两个以上转换源或转换目标是算术类型的转换。
8、无法使用强制类型转换解决二义性问题,因为强制类型转换本身也面临二义性。
9、除了显式地向bool类型的转换外,我们应该尽量避免定义类型转换函数并尽可能限制那些“显然正确”的非显式构造函数。
10、在调用重载函数时,如果需要额外的标准类型转换,则该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型不止一个,则该调用有二义性。
11、重载的运算符也是重载的函数。因此,通用的函数匹配规则同样适用于判断在给定的表达式中到底该使用内置运算符还是重载的运算符。
和普通函数不同,我们不能通过调用的形式来区别当前调用的是成员函数还是非成员函数。
12、类类型转换包含由构造函数定义的从其他类型到类类型的转换以及由类型转换运算符定义的由类类型到其他类型的换换。
第十四章结束。华为的比赛估计也是黄了,组队一定要慎重。教训呐。耽误了一段时间的看书,也怪自己不够强,不能carry。实验室的事情也多了,还得抽时间看论文。虽然感觉搞不出什么科研。真是醉了。争取在五一前看完第15章。且随疾风前行。