C++ Primer(第五版)第十四章-重载运算符与类型转换。

前述: 不知不觉,竟然已经有3个月没有更新了。的确,自从开始看标准库后,因为书上内容实在太多,而且慢慢偏离了只记关键点的初衷,越来越求全、求多,导致笔记就这么一下子断了。而且一断就是三个月,零一天!决心每天记一点,只记关键!不管顺序。

  1. 成员访问运算符->的重载,只需要返回需要访问的对象指针即可

    对,Primer上说,成员访问符->就是这么特殊,不管你怎么想重定义它,它永远都保留了访问成员的功能。

    所以,你必须返回一个指针,或者能够返回一个指针、重载了->的对象。

    后面一句有点绕,就说这个运算符可以递归的。但不管怎样,递归的最后一层,一定是一个指针。

    定义永远会很麻烦,自己实现时记得就好了。绝对返回一个指针,或者正确重载了->的对象。

  2. lambda表示式就是一个匿名函数对象

    首先,什么是函数对象? 就是一个类,如果重载了函数调用运算符(),那么这个类的对象就称为函数对象。

    著名的boost::program_options::options_description中的add_options()函数返回的对象(应该就是本身)就是一个函数对象,所以可以神奇的一直用()来添加参数。第一眼看到后真是觉得仅为天人啊。

    后来看到标准库中的一些比较函数,如 std::greater<T>就是一个重载了()的类,所以传参时需要构建一个函数对象:

     vector<int> nums = {2,1,4,8,5} ;
     sort(nums.begin(), nums.end(), greater<int>())
    

    ok,函数对象的一个不一般的地方就是,首先,它是一个对象(类),它可以有成员变量!可以有构造函数!其次,就是具有函数的功能,可以被调用。而这些东西,不就是构成了lambda表达式所必须的条件吗!

    捕获的东西,不管是引用、还是值,都被作为匿名类的成员变量,并自动构建对应的构造函数,在构造的那一刻初始化。

    注意的是,lambda表达式的结果是一个匿名函数对象,而不是类。而重载()得到一个可以生成函数对象的类。

    类与对象,似乎不应该在这里提?没什么太大的意思了。

  3. 标准库中的函数对象

    标准库主要对

    算术(+,-,*,/,%,-(取负))、

    关系(==,!=,>,>=,<,<=)、

    逻辑(&&,||,!)

    操作预定义了函数对象。

    这些对象都是模板类对象。

    最后,Primer提到——如果我们要为指针做排序,使用常规的<是不对的!其行为是未定义的。我们需要使用less<Type>函数对象来完成指针的排序。

    WHY? 在爆栈上找到了一段描述:

     "If two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results of p<q, p>q, p<=q, and p>=q are unspecified."
    

    经过多次阅读,大概就是说如果两个指向同类型的指针不是在同一数组中,或者应用于不同的函数(?),或者其中有一个为空,那么两个指针的比较就是未定义的!不过使用less就OK…

    仍然没有具体的原因,不过的确更加清晰了。估计是多了更多的细致规则吧。

  4. 标准库function对象为各种类型的、具有同样调用形式的可调用对象建立共同的绑定

    C++中可调用对象有——函数、函数指针、lambda表达式,函数对象,bind创建的对象。

    即使这些对象有相同的调用形式——即返回类型和参数形式,如 int (int,int),仍然不能放在一起,用一个类型指向他们。(注:函数和函数指针二者倒是几乎完全等价的)。

    有时我们需要这样一个入口,使得其能够存储具有相同调用形式的所有可能的函数对象——比如做一个map,用来完成算术符字符串到函数调用的映射。

    C++11新标准中的function类型解决了此问题。

    其构建形式 function<retType(args)> f = xxx ;可以为具有相同调用形式的所有可调用对象提供统一的类型支持。

    此外,在上述map字符串到可调用对象的例子中,Primer还说了如何处理重载的情况:对于重载,其函数名称相同。而我们放入到map时,如果直接放函数名(函数指针),并不能根据map中映射的类型推断出所使用的重载函数中具体的哪个。此时,列举了两种解决办法: 1. 新建一个函数指针并用对应签名的重载函数赋值,此时是可以区分的(因为右边需要明确指明函数调用形式); 2. 套上一层lambda调用,在lambda内部做重载函数调用。

    最后,对function对象(包括lambda对象),对象是对象,指针是指针——不要被函数与函数指针弄乱了。建议函数与函数指针也按照常规思路来使用。

  5. 类的转换

    类型转换运算的操作:

    operator `Type`() const 
    {
    
    }
    

    其中的Type就是要转换到的其他类类型。该转换函数无返回值,因为其返回值必须是Type类型的对象。其次,const是可选的,建议加上,因为类型转换一般不会改变成员变量,而且我们也需要在const对象上做类型转换。

    上述叫类型转换运算符,但其实接受单变量的构造函数也算是类型转换的一部分。即类似 [const] char * -> string 这样的。这算是转入,上述是转出。

  6. 定义explicit的bool转换。

    转换为bool很有必要,因为内置类型都支持将变量放到条件判断中。

    声明为explicit有必要,否则带来的隐式转换可能导致意外。

    看如下:

    在早期版本的C++中,可能出现这样的编译通过的错误:

     int i = 42;
     cin << i;
    

    注意,cin可是向右的啊!但是如果不是explicit,那么cin就可以隐式转换为bool,又经过内置转换变为整型,然后<< 就是移位运算…完全不一样了。

    但是定义了explicit不就只能显示使用了吗?不是的!

    C++11中规定,在条件判断语句中,如果存在类型向bool的转换,那么会自动执行explicit的转换。真是不知道这个是怎么做到的… 难道是加了一条规则吗…

  7. 二义转换

    后面一节都在将二义转换,看得晕啊。

    大概就是

    1. 如果定义了从该类型转换为不同算术类型的转换的话,很可能带来二义性!

      比如分别定义从该类型转为double和float,这两个并没有什么优先级关系,歧义。定义转为int也不行啊,想想存在一种情况,就是定义的类型转换都不是精确的,比如转为long double,那么你转为double或者int其实优先级都是一样的!所以,歧义。

      总结来说,最多定义一种转换为算术类型的转换。

    2. A可以转为B,但是B又可以转为A。

      如上,如果有 A + B这样的表达式,你说是A转为B,还是B转为A呢(假设都支持加法)!!

      这一般是即定义了从A到B的构造函数,又定义了从B到A的类型转换函数。有病啊喂!!

      反正这种东西,少定义最好。同类结合(包括继承)才是正统啊。仅仅定义一些直观的转换(与内置类型间的)就好。

    最后,如果对于等式B = A + int , 如果B支持 A + B,且有int到B的转换,但是又有A到int,那么两种可能(B = A + B / B = int + int)中, 转为内置类型再相加(即 B = int + int)优先级更高(G++上如此。)