c++primer——重载运算与类型转换

1、重载的运算符是具有特殊名字的函数。对于一个运算符来说,它或者是函数成员,或者至少含有一个类类型的参数。我们只能重载已有的运算符。无法重载这个四个运算符:
::
.*
.
?:

2、部分制定了运算对象求职顺序的运算符不该被重载,特别是,逻辑与,逻辑或、取地址和逗号运算符。原因是,这些关于运算对象求职顺序的规则无法应用到重载的运算符上,去求值顺序规则无法保留。并且&&和||的重载半杯也无法保留内置运算符的短路求值属性,两个运算对象总是会被求值。

3、定义了operator==通常也要有operator!=,并且在实现时只需要定义其中一个,另外一个返回!(args)即可。

4、如果一个类包含了内在的单序比较操作,如<,它通常也有其他关系操作。

5、重载运算符的返回类型通常情况下应该与其内置版本的返回类型兼容:逻辑运算符和关系运算符应该返回bool,算数运算符应该返回一个类类型的值,赋值运算符和复合赋值运算符则应该返回左侧对象的一个引用。

6、赋值=,下表[ ], 和成员访问箭头->运算符必须是成员。

符合赋值运算符一般来说应该是成员但非必须,这一点与赋值运算符略有不同。解引用函数也通常是类的成员。

改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符通常应该是成员。

具有对称性的运算符可能转换任意一段的运算对象,例如算术、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。

7、输出运算符尽量减少格式化操作。通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符。

8、输入输出运算符必须是非成员函数。一般声明为友元函数。

9、输入运算符必须处理输入可能失败的情况,而输出运算符不需要。我们可以等读取了所有数据后赶在使用这些数据前一次性检查。当读取操作发生错误时,输入运算符应该负责从错误中恢复。

10、算术和关系运算符的形参都是常量引用。如果定义了算数运算符则一般也会定义一个对应的复合赋值运算符,通常情况下应该使用复合赋值运算符来实现算数运算符。

11、定义了相等运算符也常常包括关系运算符。如果存在唯一一种逻辑可靠的<定义,则应该考虑为这个类定义<运算符。如果类同时还包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符。

12、为了与下标的原始定义兼容,下标运算符通常以所访问的元素的引用作为返回值,下标运算符通常有两个版本,一个返回普通引用,另一个是类的常量成员并且返回常量引用。

13、为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。后置版本应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非引用。为了区分前置和后置运算符,后置版本额外接受一个(不被使用)int类型形参。

//class StrBlobPtr;
//前置版本
StrBlobPtr& StrBlobPtr::operator++(){
     check(curr, "increment past end of StrBlobPtr");
     ++curr;
     return *this;
}
//后置版本
StrBlobPtr StrBlobPtr::operator++(int){
   StrBlobPtr ret = *this;
   ++*this;      //使用前置版本
   return ret;
}

14、当我们重载箭头运算符时,可以改变的是箭头从哪个对象当中获取成员,而箭头获取成员这一事实则永远不变。必须返回类的指针或者自定义了箭头运算符的某个类的对象。

15、如果类重载了函数调用运算符,这样的类比起普通函数我们可以存储状态,使用更加灵活。该类的对象被称作函数对象。

16、函数对象常常作为泛型算法的实参。

17、lambda是函数对象,编译器将一个lambda表达式翻译成一个未命名类的未命名对象。在lambda表达式产生的类中含有一个重载的函数调用运算符。默认情况下,lambda产生的类当中的函数调用运算符是一个const成员函数。如果被声明为可变的,则调用运算符就不是const的了。

18、当一个lambda通过引用捕获变量时,编译器可以直接使用该引用,而无需在lambda产生的类中将其存储为成员。

当通过值捕获变量拷贝到lambda中,lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。

19、标准库定义了一组表示算数运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。被定义在头文件functional中。关系运算符常被用作泛型算法的谓词。
注:关联容器使用less对元素排序,因此我们可以定义一个指针的set或者在map中使用指针作为关键字而无需直接声明less。

20、两个不同类型的可调用对象却可能共享同一种调用形式。调用形式指明了调用返回的类型以及传递给调用的实参类型。

//普通函数
int add(int i, int j) {return i + j;}
//lambda
auto mod = [](int i, int j) {return i % j;};
//函数对象类
struct divide{
    int operator()(int denominator, int divisor) {
       return denominator/divisor;
    }
};
//共享同一种调用形式
int (int, int)

21、转换构造函数和类型转换运算符共同定义了类类型转换,这样的转换有时也被称作用户定义的类型转换

22、我们不允许转换成数组或者函数类型,但允许转换成指针(包括数组指针、函数指针)或者引用类型。

23、尽管编译器一次只能执行一个用户定义的类型转换,但是隐式的类类型转换可以置于一个标准(内置)类型转换之前或之后并一起使用。

//class SmallInt
//内置类型转换将double转化为int
SamllInt si = 3.14;
//SmallInt的类型转换将si转换为int
si + 3.14; //内置类型转换将所得的int继续转换成double

24、当类类型的对象和算术类型的值之间不存在明确一对一映射关系时,尽量不要定义类型转换运算符,我们要避免过度使用它。

25、c++11通过引入了显示的类型转换运算符来确保bool类型在istream中含有向bool类型转换时不发生异常。如果表达式被用作条件,则编译器会将显示的类型转换自动应用于它。

26、我们要避免有二义性的类型转换。在两种情况下可能发生多重转换路径:
(1)两个类提供相同的类型转换
(2)定义了多个转换规则
因此我们要做到
(1)不要令两个类执行相同的类型转换:如果Foo类有一个接受Bar类对象的构造函数,则不要在Bar类中定义转换目标是Foo类的类型转换运算符。
(2)避免转换目标是内置算术类型的类型转换。特别是当你已经定义了一个转换成算术类型的类型转换时,接下来
——不要在定义接受算术类型的重载运算符。
——不要定义转换到多种算术类型的类型转换。
除了显示地向bool类型的转换之外,我们应该尽量避免定义类型转换函数并尽可能地限制那些“显然正确”的非显示构造函数。

27、如果在调用重载函数时我们需要使用构造函数或者强制类型转换来改变实参的类型,则这通常意味着程序的设计存在不足。

28、在调用重载函数时,如果需要额外的标准类型转换,则该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型转换不止一个,则该调用具有二义性。

你可能感兴趣的:(c++语言学习)