C++ Primer(第五版)第四章-表达式读书笔记。

写在前面:前几天和学弟说好一起刷Primer,最近这几天每天看1、2页,虽然比较少,也不怎么能记住,不过还是觉得很开心。一直在看与记之间平衡,因为记到文档里消耗很多时间,而且其实也没什么效果。只是有时作为装逼的资本,或者是过去时间消耗的痕迹。因为看书时间本来也不多,这里就只记之前没有见过或者不熟悉的。从第四章开始。之前有两章笔记内容写在仓库里,当时花了很多时间来记录,最后不了了之。还是不应该成为负担。

  1. 指针不能赋给整型

     int i = &k ;
    

    会报编译错误!

    所以

     d = i = ptr = 0 ;
    

    会报错,但是如下不会报错:

     prt = 0 ;
     d = i = 0 ;
    
  2. 在while循环中使用赋值语句

     while( (i = get_value()) != 42 ){}
    
  3. 复合赋值运算提高性能

     i += 5
    

    上述+=复合赋值运算符变为汇编只需要一次求值操作。

     i = i + 5
    

    上述非复合赋值,会有两步求值操作!第一步是算i+5,第二步是赋值运算。

    所以使用复合赋值运算是可以提高性能的!

  4. *ite++被推荐使用

     *ite++ ;
     <==> 
     *(ite++) ; 
     <==>
     *ite ;
     ++ite ;
    

    Primer上说,C++程序是推崇简洁的。

    不过后面又说如果一个赋值语句左右两名都包含同一变量,那么这种写法会带来未定义的可能。

    类似

     //想要将字符串转换为大写
     while(*beg)
         *beg = toupper(*beg++) ; 
    

    这句代码在GCC下应该是能够正常执行的。不过Primer说这可能带来歧义性。不同的编译器可能产生不同的汇编代码。

    老实说,现在有些排斥这种代码简洁不过可能带来一些理解困难甚至歧义的写法。同时,函数名现在也是越写越长。所以看到Primer这么写还是有点为难的。不过,风格问题,也没什么标准~自己喜欢就好。反正,能轻松看懂的代码至少是没有问题的。不过太多冗长也是有些繁琐。所以以后还是要做好权衡啊。

  5. 运算符优先级仅针对不同边的运算符。

    如下两种情况:

     ++iter->empty() ;
     iter++->empty() ;
    

    第一种,会编译错误。因为第一次情况下,会考虑优先级,而成员访问运算符优先级高,先右后左,所以++一个右值就错了。

    第二种因为运算符在同一边,所以就是普通的由左往右的运算了。

    本来还算清晰的东西,放在一起就很容易混淆了。

  6. 成员访问符得到的成员大多是左值。

     ++(*iter) ;
    

    就真的把此位置的东西++了。

    大多:书上说,当使用.,且对象本身是右值时,访问的成员也是右值。这里有些不是很清楚,毕竟没有亲自试试,不好说。是不是算是一个坑?

  7. 条件运算符(三元运算符)可嵌套,优先级低

    嵌套

     string s = (grade < 60) ? "fail" 
                             : (grade > 90) ? "high pass" : "pass" ;
    

    低优先级(低于输出重载的<<):

     cout << grade < 60 ? "fail" : "pass" ;
    

    上述代码要报错:<的优先级小于<<,先进行<<即输出,再返回cout对象。然后<又大于? : ,这样就将cout与整型做比较了,编译报错。

    所以只需加一个括号就可以了:

     cout << ( grade < 60 ? "fail" : "pass" ) ;
    
  8. 关于位运算的3个点

    1. 小整型(8位unsigned char)做位运算将被自动“提升(promoted)”为大整型(int、long等)

       unsigned char a = 1 ;
       ~a // 不是FE,而是FF FF FF FE 或  FF FE (可能根据int的具体表示位数)
      

      不过并不清楚,到底提升为int系还是long系。

    2. 无符号整数的位运算才有意义

      有符号的移位,并没有明确定义。

    3. 位运算的优先级与重载的<<相同: 小于运算符,高于关系比较符。

      运算符 > 位运算 > 关系比较符 > 条件运算符(三元运算符)

    其他硬件相关的:

    int不一定是32位,但long一定是。

    1UL这种字面值可以直接表示无符号长整型。

  9. sizeof 运算符

    这是一个非常神奇的东西。

    首先,它肯定不是函数! 它被认为是运算符,具有运算符的优先级!然而,编译器实现时,把它作为了编译预处理符号

    1. 不是函数

      有两种使用形式:一种加括号,所以有时被误认为是函数。一种不加括号。Primer中这样写的:

       sizeof (type)
       sizeof expr
      

      实际情况是,任意表达式都可以使用加括号的形式;然而,对于类型使用sizeof,必须加括号

      其次,区别于函数的地方——它根本不对括号中的内容求值!!记住它的本质,预处理!!

       int *p = nullptr ;
       size_t size_p = sizeof(*p) ;
      

      上述代码正确运行!因为解引用根本不会被执行。预处理时应该变为了:

       int *p = nullptr ;
       size_t size_p = 4 ;
      

      (假设int占4个字节)直接替换为一个实际值了。

    2. 是运算符

      有优先级。

       sizeof x + y
       <==>
       sizeof(x) + y 
      
       sizeof p->call()
       <==>
       sizeof(p->call())
      

      即优先级与运算符相同,所以第一个实例会满足从左往右运算;优先级低于成员运算符,所以先成员运算。

    3. 是编译预处理

      第一点也提到了。

       string s = "12345678901234567890" ;
       cout << sizeof(s) ; // 为8 , 为string的固定大小
              
       vector<int> a{1,2,3,4,5,6,7,8,9,10} ;
       cout << sizeof(a) ; // 为24 , 同理为vector固定大小。
              
       int *p = new int[24] ;
       cout << sizeof(p) ; // 4 , 指针固定大小
      

      因为上述类型预处理时,是不可能获得其运行时大小的,所以直接返回类型本身的代销。

      但是数组的预处理时,可以确定大小!!

       char *a = “1234” ;
       cout << sizeof(a) ; // 5 , 因为有`\0` 
      

      这个实在太复杂了。需要后续补充。

  10. 逗号运算符,一个奇怪的表达式

    逗号运算符,所有运算符中优先级最低,但是有个结算点的概念:

    先算逗号左边的,然后抛弃左边的值,算右边。返回右边的值!

    奇怪的表达式

    Primer课后习题:

     someValue ? ++x , ++y ? --x , --y 
    

    要注意的是,这段代码左边没有赋值操作。

    写了一个完整的代码测试了下:

     int x = 10 , y = 10 ;
     true ? ++x , ++y : --x , --y;
     cout << x << " " << y << endl ;
    

    结果为: 11 10 (linux , g++)

    百思不得其解,改为false

     int x = 10 , y = 10 ;
     true ? ++x , ++y : --x , --y ;
     cout << x << " " << y << endl ;
    

    结果为: 9 9

    再测试了下赋值:

     int x = 10 , y = 10 ;
     int c = true ? ++x , ++y : --x , --y ;
     cout << x << " " << y <<" " << c << endl ;
    

    一下子发现了问题!!编译报错了,

    --y : expected unqualified-id before ‘–’ token

    所以,肯定了一点,逗号把上述表达式分为了两大不相关部分:

    1. someValue ? ++x , ++y ? --x

    2. --y

    再由结果,可知:根本不要管什么++的优先级与?:优先级关系,就是先根据?:做出判断,然后执行相应的代码!为true,就++x , ++y , 为false时,就只--x

    看了这个表达式,一下子有些失望。

    这C++写起非常规表达式来,根本就猜不透——还是太年轻啊。

    网上搜了一下”顺序点与副作用”,有这么一篇文章

    里面有个表达式:

     int a = (++a) + (++a) + (++a) + (++a) ;
    

    真是orz傻了…

    这是一个未定义的表达式。因为表达式中只有分号有顺序点,但是有5个副作用!不同编译器会给出不同的结果。

  11. 类型转换

    名词: 隐式转换(implicit conversion)

    根据类型自动进行转换,不需程序员手动管理

    例 : int a = 3.141 + 2 ;

    首先,在算术运算过程中,整型转为double!所以右侧运算结果为double类型;然后,做初始化操作,由于左侧是整型,则double转为int,且直接丢掉小数部分。

    算术运算的隐式转换规则:

    1. 绝对规则: 根据两个变量实际占据的空间大小,小空间的必然转为大空间的类型

      实际占据空间,主要是int等在不同机器上占据空间不同

    2. 在空间占据相同情况下,有符号向无符号转换。

      有符号转为无符号,其值为该有符号数对该无符号数所能表示的最大数取模。

       -1(8位的)
       -> 
       255
      

      本质上,就是用新类型的表示范围,去表示原来的数据。-1在8位时就是1111 1111,故无符号表示,正好就是255。

    3. 小于int的整型族(short , char)运算,必然先提升为int!再做其他转换或运算。

      ‘a’ + 3.1415L ‘a’先提升为int,再转为double(注,浮点数后面的L,表示double,而不是long)

    名词:显式转换

    4种:

    1. static_cast(var)

    2. const_cast<NO_CONST_TPE>(CONST_VAR)

      只能去掉变量的const属性

    3. reinterpret_cast

      “通常为运算对象的位模式提供较低层次上的重新解释”

      “本质上依赖机器”

      想起师兄在LTP 3.3.1中使用了这个转换,导致linux上模型在win上不能加载(因为其中一个int值转换失败了,导致模型头部信息检查失败)。后来换成static_cast就好了。(当然,这个方案没有被采用)感觉还是不要使用这个转换。

    4. dynamic_cast

      运行时类型识别。

    高级:通过const_cast来操作const对象

    如下代码:

    const People cp ;
    const People *cptc = &cp ;
    People *cpt = const_cast<People *>(cptc) ;
    cpt->learn_knowledge("haha") ;
    Ball b ;
    b.set_name("basketball") ;
    cpt->play(b) ; 
    cpt->make_self_introduction() ;