C++11基础语法知识总结(五)

重载运算与类型转换

一、重载运算的基本概念

当重载的运算符为成员函数时,this指针绑定到左侧运算对象上。成员运算符函数的显式参数数量比运算对象少一个。对于一个重载的二元运算符来说,左侧运算对象传入第一个参数,右侧运算对象传入第二个参数。如果作为成员运算符,则this指针所指内容默认作为左侧运算符对象,函数只需传入一个参数,作为右侧运算对象。
1、直接调用一个重载的运算符函数

f1 += f2;//类似内置版本的调用
f1.operator+=(f2);//显式类的成员函数调用

2、某些运算符不应该被重载

通常情况下,不应该重载逗号、取地址、逻辑与、逻辑或运算符。因为这些运算符的运算规则特殊,重载会导致和内置版本规则相异,使用户无法适应。

3、使用与内置类型一致的含义
a、如果类执行IO操作,则定义移位运算符使其与内置类型的IO保持一致
b、如果类定义了operator==,那它也应该定义operator != 。
c、重载运算符的返回类型通常与内置版本的返回类型兼容,逻辑运算符和关系运算符关系运算符应该返回bool,算术类型应该返回一个类类型的值等。

4、重载运算符作为成员或者非成员
a、赋值、下标、调用和成员运算符都必须时成员
b、复合赋值运算符一般是成员
c、改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常是成员。
d、具有对称性的运算符(两边的对象呼唤等价),如算术(+、-、*、/)、相等==等,通常是非成员函数。

二、输入输出运算符

1、重载operator<<,它的第一个形参是一个非常量的ostream对象的引用。因为向流写入内容会改变其状态,同时我们是无法直接复制一个ostream的对象的。

2、输出运算符尽可能减少格式化操作,这样可以让用户有权控制输出的细节

3、输入输出运算符必须是非成员函数,因外它的左侧运算对象必须是iostream对象,而不能是某个其他的类对象。

4、输入运算符必须处理输入失败出现的错误。

三、算术和关系运算符

由于算术和关系运算符通常是可交换的(Obj1+Obj2与Obj2+Obj1是等价的),所以应该定义成类的非成员函数。
1、如果类同时定义了算术运算符和相关的复合赋值运算符,通常应该使用复合赋值运算符来实现算术运算符。
2、如果某个类在逻辑上有相等性的含义,则应该定义operator==

3、关系运算符定义规则:
a、定义顺序关系,令其与关联容器中对关键字的要求一致
b、如果类同时定义了operator==,则应该确定在定义时operator<的规则与其保持一致。

四、赋值运算符

1、无论形参是什么类型,赋值运算符都必须定义为成员函数
2、复合赋值运算符通常情况下应该定义为类的成员

五、下标运算符

1、下标运算符必须是成员函数
2、下标运算符通常定义两个版本,一个返回普通引用,一个是类的常量成员函数并且返回常量引用。

六、递增/递减运算符

定义递增/递减运算符的类应该同时定义前置版本和后置版本,同时,他们应该被定义为类的成员

1、前置运算符返回递增/递减后的对象的引用
2、后置运算符定义时接受一个额外的int类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参。这个形参只是为了区分前置和后置版本。
3、后置运算符应该返回对象的原值,返回的是一个值而非引用。显式地调用后置运算符必须传递一个整形值

七、成员访问运算符(*,->)

1、在迭代器和智能指针类要定义解引用运算符和箭头运算符。
2、解引用运算符返回返回所指元素的一个引用,箭头运算符只是调用解引用运算符并返回解引用结果元素的地址。
3、箭头运算符必须时类的成员,解引用运算符通常也是类的成员。
4、这两个运算符通常定义为const的,因为获取一个元素不会改变对象本身的状态,它获取的时这个对象所绑定的非常量的元素。
5、对于箭头运算符,如果是指向类对象的内置指针类型或者是一个重载了operator—>的类的对象,等价的显式调用形式分别为:

*point).mem;//point是一个内置指针类型
point.operator()->mem;//point是一个类的对象

对于定义了operator—>的类的对象,是一个递归调用过程,他不断返回一个重载了—>运算符的类对象,直到获取一个它所绑定的对象的内置类型指针,再按内置指针来调用—>运算符。

八、函数调用运算符()

函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符,其参数数量或类型应该不同。

8.1、lambda是函数对象

a、当定义一个lambda时,编译器会合成一个未命名类的类对象,这个类中含有一个函数调用运算符。
b、默认情况下lambda不能改变它捕获的变量,因此lambda合成的类的中的函数调用运算符是一个const成员函数。
c、lambda通过引用捕获变量时,由程序保证执行时引用的对象确实存在,因此合成的类中不需要将其存储为成员变量。
d、当采用值捕获时,则会定义相应的数据成员,同时定义构造函数来初始化数据成员。
e、lambda表达式产生的类不含有默认构造函数、赋值运算符及默认析构函数;是否含有默认的拷贝/移动构造函数则视捕获数据成员类型而定。

8.2、可调用对象与function

不同类型的可调用对象可能有相同的调用形式:

//函数
int add(int i, int j)
{
	return i + j;
}
//lambda表达式
auto mod = [](int i, int j) {return i + j; };
//仿函数
struct divide 
{
	int operator()(int i, int j)
	{
		return i - j;
	}
};

以上三种类型的可调用对象的调用形式都是int(int,int);但是这三种可调用对象的类型实际上是不同的,而且难以显式表达。
假设一种场景:需要用容器存储多个调用形式相同的不同类型的可调用对象,那么容器的模板类型该设定成什么呢?

8.3、标准库function类型

a、通过使用function模板类就可以解决上述问题,function的模板参数就是可调用对象的调用形式,这样就将调用形式相同的不同类型的可调用对象统一成一种类型了。

function<int(int, int)> f1 = add;
function<int(int, int)> f2 = divide();
function<int(int, int)> f3 = mod;

b、重载的函数与function

当一个函数被重载了,我们就不能使用函数名来存入function中了:

int add(int i, int j)
{
	return i + j;
}
string add(string i, string j)
{
	return i + j;
}

如上例,如果存入add,会存在二义性,解决这个问题的方法就是存入函数指针,通过函数指针的返回值类型或参数类型来区分,或者函数对象及lambda来消除二义性(lambda其实隐含也是采用函数对象来区分的。)

int(*fp)(int, int) = add;
function<int(int, int)> f1 = fp;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值