重载运算
-
重载运算符函数的参数数量与运算符作用的运算对象数量一样,二元运算符,左侧运算对象传递第一个参数,右侧运算对象传递第二个参数。
-
除了重载的函数调用运算符operator()之外,其他重载运算符不能含有默认参数。
-
如果运算符函数是成员函数,它左侧第一个运算对象为隐式的this指针,成员运算符函数的显式参数数量比运算符运算对象总数少一个。
-
可将运算符作用于类型正确的实参以运算符形式间接调用重载的运算符,也可像普通函数调用直接调用运算符函数。
-
重载的运算符,优先级和结合律与对应的内置运算符保持一致。
-
大多数运算符可被重载,不可被重载运算符(::)、(.*)、(.)、(?:)。
-
逻辑与、逻辑或和逗号运算符重载无法保留求值顺序,&&和||运算符重载无法保留短路求值属性,不建议重载。
-
不建议重载取地址运算符。
-
重载运算符使用与内置类型一致的含义:
①赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员函数。
②复合赋值运算符一般来说应该是成员。
③改变对象状态的运算符、与给定类型密切相关的运算符,例递增、递减和解引用运算符,通常是成员。
④具有对称性的运算符有可能转换一侧运算对象类型,例算术、相等性、关系和位运算符等,通常是普通的非成员函数。
重载运算符
-
输入输出运算符
①输出运算符第一个形参是非常量ostream对象的引用,第二个形参是常量的引用,返回ostream的形参流的引用。
②输入运算符第一个形参是运算符将要读取流的引用,第二个形参是将要读入的非常量对象的引用,返回给定流的引用。
③输入运算符必须处理输入可能失败的情况,而输出运算符不需要。
④执行输入运算符可能发生以下错误:a.当流含有错误类型数据时读取操作可能失败;b.当读取操作到达文件末尾或遇到输入流其他错误时。 -
关系运算符
①如果存在唯一一种逻辑可靠的<定义,则应该考虑为类定义<运算符,如果类同时包含==,当且仅当<的定义和==产生的结果一致时才定义<运算符。 -
赋值运算符
①除了拷贝运算符和移动运算符,可另定义使用其他类型作为右侧运算对象的赋值运算符,例标准库vector类定义了第三种赋值运算符,接受花括号元素列表作为参数。
②赋值运算符包括复合赋值运算符必须定义成类的成员,这两类运算符都应该返回左侧运算对象的引用。 -
下标运算符
①表示容器的类可通过元素在容器中的位置访问元素,这些类一般会定义下标运算符operator[],且必须是成员函数。
②下标运算符返回元素的引用,通常定义两个版本,一个返回普通引用,另一个是类的常量成员返回常量引用。 -
递增和递减运算符
①同时定义前置版本和后置版本,通常应定义为类的成员。
②为了区分前置版本和后置版本,后置版本接受一个额外的不被使用的int类型形参,当使用后置运算符时,编绎器为这个形参提供一个值为0的实参,且int形参无须命名。
③前置运算符应返回递增或递减后对象的引用。
④后置运算符应返回对象的原值,返回形式是一个值而非引用。 -
成员访问运算符
①在迭代器及智能指针类中常使用解引用运算符和箭头运算符。
②重载的箭头运算符必须返回类的指针或自定义过箭头运算符的某个类对象。 -
函数调用运算符
①如果类重载函数调用运算符,调用对象即运行重载的调用运算符,像使用函数一样使用该类对象。
②函数调用运算符必须是成员函数,类可以定义不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。
lambda函数对象
-
当编写一个lambda后,编绎器将表达式翻译成未命名类的未命名对象,lambda表达式生成的未命名类中含有重载的函数调用运算符。
-
lambda表达式通过引用捕获变量,由程序负责确保lambda执行时所引用对象确实存在,编绎器可以直接使用该引用无须在lambda产生的未命名类中存储为数据成员。
-
相反,通过值捕获的变量被拷贝到lambda中,lambda产生的未命名类为每个值捕获变量建立对应的数据成员,同时创建构造函数,使用捕获变量的值来初始化数据成员。
-
lambda表达式产生的类不含默认构造函数、赋值运算符及默认析构函数,是否含有默认的拷贝/移动构造函数视捕获的数据成员类型而定。
-
含有重载函数调用运算符的类对象可作为泛型算法的实参,替代lambda表达式。
标准库定义的函数对象
-
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类。
①每个类定义调用运算符执行运算符操作。
②类定义成模板形式,指定调用运算符的形参类型。 -
算术
①plus<Type>
②minus<Type>
③multiplies<Type>
④divides<Type>
⑤modulus<Type>
⑥negate<Type> -
关系
①equal_to<Type>
②not_equal_to<Type>
③greater<Type>
④greater_equal<Type>
⑤less<Type>
⑥less_equal<Type> -
逻辑
①logical_and<Type>
②logical_or<Type>
③logical_not<Type> -
运算符函数对象可替换算法中的默认运算符,可在泛型算法中使用标准库函数对象。
-
标准库函数对象同样适用指针,比较两个无关指针为未定义的行为,标准库函数对象可实现比较指针内存地址,vector容器sort指针。
-
泛型算法中,可使用lambda表达式,可使用重载了函数调用运算符的类对象,可使用标准库定义的函数对象,类对象和函数对象可以是未命名的临时对象。
可调用对象与function
-
C++语言中有几种可调用对象,函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。
-
function是一个模板,尖括号内表示对象的调用形式,可用该模板指向相同调用形式的所有可调用对象,函数指针、函数对象类的对象、lambda。
①function<T> f; f是存储可调用对象的空function,可调用对象的调用形式与函数T相同。
②function<T> f (nullptr); 显式的构造一个空function。
③function<T> f(obj); 在f中存储可调用对象obj的副本。
④f; 将f作为条件,当f含有可调用对象时为真,否则为假。
⑤f(args); 调用f中可调用对象,参数是args。 -
定义为function<T>的成员类型。
①result_type,该function类型可调用对象的返回类型。
②argument_type,当T有一个或两个实参时定义的类型,如果T只有一个实参,则argument_type是该类型的同义词。
③first_argument_type,second_argument_type,如果T有两个实参,first_argument_type和second_argument_type分别代表两个实参类型。 -
调用形式指明调用返回类型以及调用的实参类型,一种调用形式对应一个函数类型。
-
不同的函数类型可能有相同的返回类型和形参,即可具有相同的调用形式。
类型转换
-
类型转换运算符是类的特殊成员函数,将一个类类型的值转换成其他类型。
①operator type() const; type表示转换类型,可表示除void外任意类型,该类型作为函数的返回类型,不允许转换成数组或函数类型,但允许转换成指针(包括数组指针和函数指针)或引用类型。
②类型转换运算符既没有显式的返回类型,也没有形参,必须定义成类的成员函数,不改变待转换对象的内容,一般被定义成const成员。 -
编绎器一次只能执行一种用户定义的类型转换,可以与标准内置类型转换一起使用。
-
类型转换运算符是隐式执行的,为避免隐式转换带来问题,C++11引入显式类型转换运算符,与显式构造函数一样,使用explicit关键字定义显式的类型转换,使用static_cast显式请求强制转换。
①大多数情况下,类型转换自动发生,可能产生意外结果。
②类定义向bool的类型转换,bool是算术类型,类类型对象转换成bool后能被用在任何需要算术类型的上下文中。 -
但当表达式出现在下列位置时,显式的类型转换将被隐式执行:
①if、while及do语句的条件部分。
②for语句头的条件表达式。
③逻辑非运算符(!)、逻辑或运算符(||)、逻辑与(&&)运算符的运算对象。
④条件运算符的条件(?:)表达式。 -
C++11标准下,IO标准库定义了一个向bool显式转换的规则,在条件中使用流对象,会使用为IO类型定义的operator bool的转换。
①向bool的类型转换通常使用在条件部分,因此operator bool一般定义成explicit的。
避免有二义性的类型转换
-
不要为类定义相同的类型转换,也不要在类中定义两个及以上的转换源或转换目标是算术类型的转换。
①不要令两个类执行相同的类型转换,A类定义接受B类对象的转换构造函数,及B类定义转换目标是A类的类型转换运算符。
②避免转换目标是内置算术类型的类型转换,因转换源或转换目标本身可执行内置算术类型转换;已经定义了一个转换成算术类型的类型转换时,不再重载运算符,类型对象转换成算术类型后,使用内置运算符即可;不再定义转换到多种算术类型的类型转换,标准类型转换完成向其他算术类型的转换。
③总结,除了显式地向bool类型转换外,应尽量避免定义类型转换函数并尽可能地限制那些“显然正确”的非显式构造函数。 -
当调用重载函数,如果多个类型转换都提供了可行匹配,则这些类型转换具有二义性,即使一个调用需要额外的标准类型转换而另一个调用能精确匹配。
①当使用两个用户定义的类型转换时,如果转换函数之前或之后存在标准类型转换,则标准类型转换级别将决定最佳匹配是哪个函数。
②当调用重载函数时,两个或多个用户定义的类型转换都提供了可行匹配,则认为这些类型转换一样好,不考虑标准类型转换的级别。
函数匹配与重载运算符
-
当使用重载运算符作用于类类型的运算对象,候选函数中包含该运算符的普通非成员版本和内置版本,如果左侧运算对象是类类型,则定义在该类中的运算符重载版本也包含在候选函数内。
-
当调用一个命名函数时,具有该名字的成员函数和非成员函数不会彼此重载。
-
当通过类类型的对象进行函数调用时,只考虑该类的成员函数。
-
当表达式中使用重载的运算符时,无法判断正在使用的是成员函数还是非成员函数。
-
如果对一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则重载的运算符与内置运算符将产生二义性问题。