重载运算函数如果是非成员的,则参数数量与该运算符作用的运算对象一样多。如果是成员函数,则运算符的第一个(左侧)运算对象绑定到隐式的this指针上。
一个运算符函数,它或者是类的成员,或者至少含有一个类类型的参数。当运算符作用于内置类型的运算对象时,我们无法改变该运算符的含义。
只能重载已有的运算符,无权发明新的。优先级和结合律与对应的内置运算符保持一致。
不能被重载的运算符: :: , . ?:*
data1+data2; 非成员函数等价的调用为 operartor+(data1, data2);
data1+=data2;成员函数等价的调用为 data1.operator+=(data);
因为重载运算符本来就是一次函数调用,所以关于运算符求值顺序的规则无法应用到重载运算符上。
不应该重载:
重载运算符使用与内置类型一致的含义。
如果类含有算术运算符或者位运算符,则最好提供对应的复合赋值运算符。
输出运算符<<第一形参是非常量ostream对象的引用。因为向流中写入内容会改变流的状态,而流无法拷贝。
第二个参数通常是常量引用(要打印的类类型)。
返回值为向流中写好内容的ostream形参。
输出运算符尽量减少格式化操作,不应该打印换行符。
与iostream标准库兼容的输入输出运算符必须是普通非成员函数。如果是成员函数,则他们的左侧运算对象将是我们类的一个对象。
重载输入运算符>>,第一形参是要读取的流的引用,第二个形参是要读到对象的引用,目的是将数据读到这个对象中。
输入运算符必须处理输入可能失败的情况,而输出运算符不用。
当读取操作发生错误时,输入运算符应该负责从错误中恢复。(赋予类类型默认初始化值)
一般定义为非成员的,一般不需要改变运算对象的状态,所以相残都是常量引用。
相等运算符和不等运算符中的一个应该把工作委托给另一个。
如果定义<时,类中还包含==,则当且仅当<的定义和==产生的结果一致时才定义<。
赋值运算符必须定义成类的成员,复合赋值运算符(+=等)通常情况也应该这样做。都返回左侧运算对象的引用。
下标运算符必须是成员函数。
通常以所访问元素的引用作为返回值。通常定义两个版本,一个返回普通引用,一个返回常量引用。
C++并不要求递增和递减运算符必须是类的成员,但它们改变的正好是所操作对象的状态,所以建议将其设定为成员函数。
前置递增运算符:
Class A {
public:
ClassA& operator++(); // 前置递增运算符
}
ClassA& ClassA::operator++(){
Check(curr, "错误原因:超过队列长度了"); // 首先要检查ClassA是否有效,索引是否有效
++curr; // 没有抛出异常再加
return *this; // 返回对象的引用
}
为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。
为了区分前置和后置,后置版本接受一个额外的(不被使用)int类型的形参。该形参作用是区分前置和后置,不是要参与运算的。
Class A {
public:
ClassA operator++(int); // 后置递增运算符
}
ClassA ClassA::operator++(){
ClassA ret = *this; // 记录当前值
++*this; // 调用前置递增运算符
return ret; // 返回之前记录的状态
}
后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非引用。
显式地调用后置运算符:
ClassA p(a1);
p.operatpr++(0); // 后置递增运算符
p.operatpr++(); // 前置递增运算符
解引用运算符(*)和箭头运算符(->)。
箭头运算符必须是类的成员。解引用运算符通常也是类的成员(不是必须)。
class StrBlobPtr{
public:
std::string& opetator*() const{
// 检查curr是否在作用范围,如果是,返回curr所指元素的第一个引用
auto p = check(curr, "结束后取消引用");
return (*p)[curr];
}
std::string* operator->() const{
return & this->operator*();
}
}
可以令解引用(*)完成指定的操作(返回固定值,打印对象信息等都可以)。
箭头运算符不执行任何自己操作,而是调用解引用运算符,并返回解引用元素的地址。
箭头运算符不能丢掉成员访问这个最基本的含义。我们可以改变的是箭头从哪个对象中获取成员,而箭头获取成员这一事实永远不变。
形如point->mem的表达式,point必须是指向类对象的指针或者是一个重载了->的类的对象。
等价于:
(*point).mem; // 内置的指针类型
point.operator()->mem; // 类的一个对象
如果类重载了函数调用运算符,则我们可以像使用函数一样,使用该类的对象。
struct absInt{
int operator() (int nVal) const {
return nVal < 0 ? -nVal : nVal;
}
}
absInt abs;
int nU = abs(-31);
如果类定义了调用运算符,则该类的对象称作函数对象。
因为调用这种现象,所以我们说这些对象的“行为像函数一样”。
函数对象常常作为反省算法的形参。
编译器将lambda表达式翻译成一个未命名类的未命名对象(函数对象)。产生的类只有一个函数调用运算符成员。
通过值捕获的变量被拷贝到lambda中,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。
lambda表达式产生的类不含默认构造函数,赋值运算符及默认析构函数。
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类。这些类为模板类。
例如:plus表示加 ,equal_to表示是否相等 ,logical_and表示和。
C++语言有几种可调用的对象:函数、函数指针、lambda表达式和bind创建的对象。
调用形式:指明了调用返回的类型以及传递给调用的实参类型。如: int(int, int)
两种不同类型的可调用对象可能共享一种调用形式。一种调用形式对应一种函数类型。
希望有一个函数表,用来存储指向这些可调用对象的“指针”。
// 但只能往里添加函数类型,对于lambda表达式和函数对象不能添加
map
为解决这个问题,标准库提供了一个名为function的新标准库类型。
fuction f; // function是一个模板
function
不能把重载函数的名字存入function类型的对象中(区分不开)。
解决上面二义性的问题有两种方式:存储函数指针和使用lambda。
类类型转换。 通过定义类型转换运算符,就可以实现类类型的类型转换。这样的转换有时也被称作用户定义的类型转换。
类型转换运算符是类的一种特殊成员函数。 负责将一个类类型的值转换成其他类型。
形式:operator DirType() const; // DirType表示目标类型
类型转换运算符可以面向任何类型进行定义(除void以外。)
只要该类型能作为函数的返回类型。因此不允许转化为数组或函数类型,但允许转化成指针或引用类型。
类型转换运算符没有显示的返回值没有形参,而且必须定义成成员函数。
因为类型转换运算符是隐式执行的,所以无法给这些函数传递实参。
尽管类型转换函数不负责指定返回类型,但实际上每一个类型转换函数都会返回一个对应类型的值。
如果类类型和转换类型之间不存在明显的映射关系,则这样的类型转换具有误导性,应该避免使用。
C++11新标准引入了显示的类型转换运算符。
explicit operator DirType() const;
编译器通常不会将此类运算符用于隐式类型转换。有一个例外,如果表达式用作条件,则编译器会将显式类型转换自动应用于它。
如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间存在唯一一种转换方式。
写的代码存在二义性的两种可能:
A类定义了接受B对象的转换构造函数。同时B类定义了一个转换目标是A类的类型转换运算符。
第二种情况是类定义了多个转换规则。
如果类中定义了一组类型,转换他们的转换源类型。本身可以通过其他类型转换联系在一起。
在调用重载函数时,我们需要使用构造函数或者强制类型转换来改变实参的类型。最好不要这么设计。 会造成调用具有二义性。
对运算符重载也是重载的函数。
调用命名函数的语法形式,对于成员函数和非成员函数来说是不相同的。
通过类类型的对象(或者该对象的指针及引用)进行函数调用时,只考虑类的成员函数。
在表达式中使用重载运算符,无法判断正在使用的是成员函数,还是非成员函数。可能会造成二义性。