[读书笔记] C++Primer (第5版) 第14章重载运算与类型转换

1.重载运算符基本概念:

重载运算函数如果是非成员的,则参数数量与该运算符作用的运算对象一样多。如果是成员函数,则运算符的第一个(左侧)运算对象绑定到隐式的this指针上。
一个运算符函数,它或者是类的成员,或者至少含有一个类类型的参数。当运算符作用于内置类型的运算对象时,我们无法改变该运算符的含义。
只能重载已有的运算符,无权发明新的。优先级和结合律与对应的内置运算符保持一致。
不能被重载的运算符: :: , . ?:*
data1+data2; 非成员函数等价的调用为 operartor+(data1, data2);
data1+=data2;成员函数等价的调用为 data1.operator+=(data);
因为重载运算符本来就是一次函数调用,所以关于运算符求值顺序的规则无法应用到重载运算符上。
不应该重载:

  • 逗号、取地址:以为已经定义了这两个运算符用于类类型对象时的特殊含义。
  • 逻辑与、逻辑或:因为无法保留短路求值属性,有可能会造成误解。

重载运算符使用与内置类型一致的含义。
如果类含有算术运算符或者位运算符,则最好提供对应的复合赋值运算符。

  • 赋值(=)、下标([])、调用( () )和成员访问箭头(->)运算符必须是成员函数
  • 复合赋值运算符一般来说应该是成员,但并非必须。区别于赋值运算符
  • 该表对象状态的运算符或与给定类型密切相关的运算符,如递增、解引用等,通常应该是成员的
  • 具有对称性的运算符(可以转换任意一端的操作对象),如算术,关系等,通常应该是非成员函数

2.输出与输入运算符:

输出运算符<<第一形参是非常量ostream对象的引用。因为向流中写入内容会改变流的状态,而流无法拷贝。
第二个参数通常是常量引用(要打印的类类型)。
返回值为向流中写好内容的ostream形参。
输出运算符尽量减少格式化操作,不应该打印换行符。
与iostream标准库兼容的输入输出运算符必须是普通非成员函数。如果是成员函数,则他们的左侧运算对象将是我们类的一个对象。
重载输入运算符>>,第一形参是要读取的流的引用,第二个形参是要读到对象的引用,目的是将数据读到这个对象中。
输入运算符必须处理输入可能失败的情况,而输出运算符不用。
当读取操作发生错误时,输入运算符应该负责从错误中恢复。(赋予类类型默认初始化值)

3.算术和关系运算符:

一般定义为非成员的,一般不需要改变运算对象的状态,所以相残都是常量引用。
相等运算符和不等运算符中的一个应该把工作委托给另一个。
如果定义<时,类中还包含==,则当且仅当<的定义和==产生的结果一致时才定义<。

4.赋值运算符

赋值运算符必须定义成类的成员,复合赋值运算符(+=等)通常情况也应该这样做。都返回左侧运算对象的引用。

5.下标运算符:

下标运算符必须是成员函数。
通常以所访问元素的引用作为返回值。通常定义两个版本,一个返回普通引用,一个返回常量引用。

6.递增和递减运算符:

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++();		// 前置递增运算符

7.成员访问运算符:

解引用运算符(*)和箭头运算符(->)。
箭头运算符必须是类的成员。解引用运算符通常也是类的成员(不是必须)。

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; // 类的一个对象

8.函数调用运算符:

如果类重载了函数调用运算符,则我们可以像使用函数一样,使用该类的对象。

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 binops;
为解决这个问题,标准库提供了一个名为function的新标准库类型。
fuction f; // function是一个模板
function> f1 = add; // 可以吧所有可调用对象都添加到这个map中
不能把重载函数的名字存入function类型的对象中(区分不开)。
解决上面二义性的问题有两种方式:存储函数指针和使用lambda。

9.重载、类型转换和运算符:

类类型转换。 通过定义类型转换运算符,就可以实现类类型的类型转换。这样的转换有时也被称作用户定义的类型转换。
类型转换运算符是类的一种特殊成员函数。 负责将一个类类型的值转换成其他类型。
形式:operator DirType() const; // DirType表示目标类型
类型转换运算符可以面向任何类型进行定义(除void以外。)
只要该类型能作为函数的返回类型。因此不允许转化为数组或函数类型,但允许转化成指针或引用类型。
类型转换运算符没有显示的返回值没有形参,而且必须定义成成员函数。
因为类型转换运算符是隐式执行的,所以无法给这些函数传递实参。
尽管类型转换函数不负责指定返回类型,但实际上每一个类型转换函数都会返回一个对应类型的值。
如果类类型和转换类型之间不存在明显的映射关系,则这样的类型转换具有误导性,应该避免使用。
C++11新标准引入了显示的类型转换运算符。
explicit operator DirType() const;
编译器通常不会将此类运算符用于隐式类型转换。有一个例外,如果表达式用作条件,则编译器会将显式类型转换自动应用于它。
如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间存在唯一一种转换方式。
写的代码存在二义性的两种可能:
A类定义了接受B对象的转换构造函数。同时B类定义了一个转换目标是A类的类型转换运算符。
第二种情况是类定义了多个转换规则。
如果类中定义了一组类型,转换他们的转换源类型。本身可以通过其他类型转换联系在一起。
在调用重载函数时,我们需要使用构造函数或者强制类型转换来改变实参的类型。最好不要这么设计。 会造成调用具有二义性。
对运算符重载也是重载的函数。
调用命名函数的语法形式,对于成员函数和非成员函数来说是不相同的。
通过类类型的对象(或者该对象的指针及引用)进行函数调用时,只考虑类的成员函数。
在表达式中使用重载运算符,无法判断正在使用的是成员函数,还是非成员函数。可能会造成二义性。

你可能感兴趣的:(C++)