代码改变世界

C++Primer 第十四章

2016-06-20 23:18  szn好色仙人  阅读(337)  评论(0编辑  收藏  举报
//1.当运算符作用于类类型运算对象时,可以通过运算符重载重新定义该运算符的含义。明智的使用运算符重载能令程序更加易于编写和阅读。

//2.重载的运算符是具有特殊名字的函数,它们由关键字operator和其后要定义的运算符号共同组成。和其它函数一样,重载的运算符也包含返回类型,参数列表以及函数体。

//3.除了重载函数调用符之外,其余的重载运算符不能含有默认实参。

//4.如果运算符函数是成员函数,则其左侧运算对象绑定到隐式的this指针。
//  对于运算符函数来说,它或者是类的成员,或者至少含有一个类类型的参数。即无法改变运算符作用于内置对象时候的操作。

//5.对于重载的运算符来说,其优先级和结合律是无法被改变的。

//6.某些运算符指定了运算对象的求值顺序。因为使用重载的运算符本事是一次函数调用,所以这些运算对象求值顺序的规则无法应用到重载的运算符上。
//  && || , 的求值顺序规则无法保留下来。 && || 的短路求值属性也无法保留,因此一般不对这些运算符进行重载。
//  C++语言已经定义了, 和 & 用于类类型对象时候的特殊含义,这一点与大多数运算符都不相同,所以一般也不要进行重载。

//7.当重载了==运算符的时候,一般也需要重载!=。重载了<则一般也要重载>。若重载了算数运算符或者位运算符,则最好也提供其复合版本。

//8.当我们定义重载的运算符的时候,必须首先决定是将其声明为类的成员函数还是类的非成员函数。其区别是:类的成员函数版本的重载运算符其左侧操作对象必须是该类的对象。
//  一般来说 = () [] -> 必须定义为类的成员函数版本。
//  重载的复合运算符一般来说作为类的成员函数,但非必须。
//  改变对象状态的运算符或与给定类型密切相关的运算符,比如 递减, 递增, 解引用 通常为作为类的成员函数。
//  具有对称性的运算符可能转换任意一端的运算对象,例如 算数 相等性 关系 位运算符等,通常应该是类的非成员函数(需按照类类型对象的位置,定义多个版本的重载运算符函数)。
//  尽量使重载的运算符与其内置的版本表现一致。

//9.如果存在唯一一种可靠的<定义,则应该考虑为这个类定义<运算符(关联容器和一些算法需要此运算符)。如果类同时还包含==,则当且仅当<定义和==结果是一致的时候才定义<(这里的结果一致的意思是:当不能满足==的时候,2个对象必须有一个是<另一个的)。

//10.下标运算符的内置版本是返回引用类型,所以其重载类型最好也返回引用类型,而且通常定义下标运算符的常量版本和非常量版本,防止作用于一个常量对象时不会给其返回的对象赋值。

//11.为了与内置版本保持一致,重载前置的递增递减运算符应该返回引用类型。后置的递增递减运算符应该按值返回。后置的递增递减运算符额外接受一个不被使用的int类型的实参(将会被置为0)来区别其前置版本。

//12.很多具有互相作用的运算符的重载,其工作可以交予其具有相关联关系的运算符重载函数完成,比如==的工作可以交予!=来完成等。

//13.箭头运算符永远不能丢掉其成员访问的这个基本含义。可以改变的是箭头从哪个对象当中获取成员,而箭头获取成员这一个事实则永远不变。所以:重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的类的某个对象。
//   point->men;根据point的类型不同分别等价于:
//   A:(*point).men;            point是一个内置的指针类型
//   B:point.operator()->men;    point是类的一个对象。
class CTest
{
public:
    CTest()                    {str = "szn";}
    string* operator ->()    {return &str;}
public:
    string str;
};
CTest Test;
const char *pStr0 = Test->c_str();            //pStr0 = "szn"
const char *pStr1 = (&Test)->str.c_str();    //pStr1 = "szn"

//14.函数调用运算符可以重载为多个版本,相互之间在参数数量或者类型上有所区别。由于类的对象能存储状态,所以比起普通函数来说更加灵活。

//15.function位于头文件functional中,声明在命名空间std中,用于存储一个可调用对象。注意:重载的函数名字不能直接存入function类型的对象中,可以使用函数指针或lambda等来解决这个问题。
int Add(int value0, int value1)    {return value0 + value1;}
int Minus(int i, int j)            {return i - j;}

class CMultiply
{
public:
    CMultiply(){}
    int operator()(int value0, int value1){return value0 * value1;}
};

CMultiply multiply;
auto divide = [](int i, int j){return j == 0 ? 0 : i / j;};
auto minus = bind(Minus, _2, _1);

map<string, function<int (int , int)>> mapFun;
mapFun.insert(make_pair(string("+"), Add));
mapFun.insert(make_pair(string("-"), minus));
mapFun.insert(make_pair(string("*"), multiply));
mapFun.insert(make_pair(string("/"), divide));

int v0 = mapFun["+"](10, 5);    //v0 = 15
int v1 = mapFun["-"](10, 5);        //v1 = -5
int v2 = mapFun["*"](10, 5);    //v2 = 50
int v3 = mapFun["/"](10, 5);    //v3 = 2

//16.通过定义转换构造函数可以将指定类型的对象转换为类类型的对象。通过定义类型转换运算符可以将类类型的对象转换为指定类型的对象。转换构造函数和类型转换运算符共同组成了类类型转换。
//   在实际操作中,很少提供类的类型转换运算符,在大多数情况下,如果类型转换自动发生,用户可能感觉比较意外,而不是感觉受到了帮助。特殊的:对于类来说,定义向bool的类型转换还是比较普遍的。
class CTest
{
public:
    CTest() : value(0){}
    operator int() const {return value;}    //不能声明返回类型,形参列表必须为空,通常是const的。
private:
    int value;
};

CTest Test;
int value = Test;    //value = 0

//17.避免使用二义性的类型转换
//  A:当类A定义了一个接受B类对象的转换构造函数,同时B类定义了一个转换目标是A类的类型转换运算符。此时在将B类对象转换为A类对象的时候将产生二义性。
//  B:当类定义了多个转换规则,而这些转换涉及的类型本身可以通过其他类型转换联系在一起,此时可能造成二义性(比如类A定义了到int和double的类型转换运算符,而使用一个float类型去接收类型转换构造符的结果时,就会造成二义性)。最典型的情况是算数类型转换。
//  C:当我们调用重载的函数的时候,从多个类型转换中进行选择将变得更加复杂,比如:当几个重载函数的参数分别属于不同的类类型,并且这些类类型都定义了同样的转换构造函数,则二义性问题将进一步提升。
class A    {A(int){}};
class B    {B(int){}};    //即使这里B类的定义为:class B    {B(double){}};fun(10);的调用也会有二义性。
void fun(A){};
void fun(B){};
fun(10);    //调用将出错
//  D.如果我们对同一个类型即提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将遇到重载运算符和内置运算符的二义性问题
class A    
{
public:
    A(int a) : value(a){}
    operator int() {return value;}
    friend A operator + (const A &a0, const A &a1) {return a0.value + a1.value;}

private:
    int value;
};
A a(10);
int sum = a + 10;    //二义性冲突
//  基于上述结论得出:除了显示地向bool类型转换之外,我们尽量避免定义类型转换运算符,并尽可能的限制转换构造函数(explicit)。