1.运算符重载
'返回类型 operator运算符(参数列表){...} //运算符重载格式'
a1 + a2; //普通的表达式
operator+(a,b); //等价的函数调用(非成员函数)
a1 += a2; //基于“调用”的表达式
a1.operator+=(a2); //对成员运算符函数的等价调用(成员函数)
注意点:
- 只能重载已有的运算符,而无权发明新的运算符。
- 优先级和结合律与对应的内置运算符保持一致。
- 不能被重载的运算符(. | .* | ?: | ::)。
- 后置版本的++运算符以operator++(int)做区别。
2.成员或非成员的选择
①必须为成员:赋值(=),下标([ ]),调用(( )),成员访问箭头(->)
②通常为成员:复合赋值运算符(+=),递增(++)、递减(–)和解引用()
③通常非成员:算术(+),相等性(==),关系运算符(<)
④必须非成员:输入(>>),输出(<<)运算符(类似java中的toString()*),流均为标准库类,所以必须为非成员
3.函数对象
如果累定义了调用运算符,则该类的对象称作函数对象(function object)。
返回值 operator()(参数列表){...}
标准库定义的函数对象:
头文件< functional >
算术 | 关系 | 逻辑 |
---|---|---|
plus< Type > | equal_to< Type> | logical_and< Type > |
minus< Type > | not_equal_to< Type > | logical_or< Type > |
multiplies< Type > | greater< Type > | logical_not< Type > |
divides< Type > | greater_equal< Type > | |
modulus< Type > | less< Type > | |
negate< Type > | less_equal< Type > |
使用样例:sort(b,e,less< string >()* )
4.可调用对象与function
c++可调用对象:
①函数
②函数指针
③lambda表达式(匿名函数,调用一次居多)
④bind创建的对象(解决谓词参数的限定问题)
⑤重载了函数调用运算符的类(函数对象)
为什么引入function?(头文件< functional >)
例: 实现简单桌面计算器,从而定义函数表,用于存储指向这些可调用对象的“指针”
// ordinary function
int add(int i, int j) { return i + j; }
// lambda, which generates an unnamed function-object class
auto mod = [](int i, int j) { return i % j; };
// function-object class
struct div {
int operator()(int denominator, int divisor) {
return denominator / divisor;
}
};
//构建从运算符到函数指针的映射关系
map<string, int(*)(int,int)> binops;
//正确:add是一个指向正确类型函数的指针
binops.insert({"+",add});
//错误:mod不是函数指针
binops.insert({"%",mod});
//mod和divide都无法存入binops,因为它们都不是函数指针
function< T >为类模板,就是为了解决该问题,实现调用对象的适配统一。
function<T> f; //T为调用对象适配类型如:int(int,int)表示返回类型为int,参数为两个int类型的调用对象,默认为空,等价于function<T> f(nullptr);
function<T> f(nullptr);
function<T> f(obj); //obj为适配的可调用对象
所以上例中的问题可以通过以下形式解决:
map<string, function<int(int, int)>> binops = {
{"+", add}, // function pointer
{"-", std::minus<int>()}, // library function object
{"/", div()}, // user-defined function object
{"*", [](int i, int j) { return i * j; }}, // unnamed lambda
{"%", mod} }; // named lambda object
binops["+"](10,5); // calls add(10,5);
当然我们不能直接将重载函数的名字存入function类型的对象中,会产生二义性问题,此时,可以通过函数指针为中间件来解决歧义(使指针指向所需的函数,确定对应的函数类型)。
5.类型转换运算符
类型转换运算符(conversion operator)是类的一种特殊成员函数,负责将一个类类型的值转换成其它类型。
类型转换运算符既①无返回类型,也②无形参,必须定义为③成员函数,一般为④const(不改变待转换对象的内容)。
operator int() const {return val;} //将一个类的值转换为int,可以直接与int型变量进行运算
explicit operator int() const {return val;} //需要通过static_cast<int>(var)进行显示转换
就算声明为explicit,在以下情况下(作为条件),还是会进行隐式转换:
①if、while及do语句的条件部分
②for语句头的条件表达式
③逻辑运算符(!,||,&&)中的对象
④条件运算符(?:)的条件表达式
这也是为什么while(cin>>value)可以作为判断表达式的原因,此时当输入value值后,cin被istream operator bool类型转换函数隐式地进行了转换。
6.避免有二义性的类型转换
**情况一:**类B有两种方式转换到A(即A(B&)构造函数,B内有类型转换函数到A)
struct B;
struct A {
A() = default;
A(const B&); // converts a B to an A
};
struct B {
operator A() const; // also converts a B to an A
};
A f(const A&);
B b;
A a = f(b); // error ambiguous: f(B::operator A())
// or f(A::A(const B&))
情况二: 两个转换对象都是算法类型(如int,double),两种类型都无法精确匹配long型,所以会存在转换上的歧义。
struct A {
A(int = 0); // usually a bad idea to have two
A(double); // conversions from arithmetic types
operator int() const; // usually a bad idea to have two
operator double() const; // conversions to arithmetic types
};
void f2(long double);
A a;
f2(a); // error ambiguous: f(A::operator int())
// or f(A::operator double())
long lg;
A a2(lg); // error ambiguous: A::A(int) or A::A(double)
情况三: 运算符调用的歧义(SmallInt可以转换成int从而使用内置+运算符,同时int也可以通过构造函数隐式转换成SmallInt从而调用SmallInt中的+重载版本)
class SmallInt {
friend
SmallInt operator+(const SmallInt&, const SmallInt&);
public:
SmallInt(int = 0); // conversion from int
operator int() const { return val; } // conversion to int
private:
std::size_t val;
};
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // uses overloaded operator+
int i = s3 + 0; // error: ambiguous
此时s3+0;不知道该调用那个+运算符函数,把0转换成SmallInt然后使用SmallInt的+,或者把s3转换成int,然后对两个int执行内置的加法运算。