1. 程式人生 > 實用技巧 >《C++ Primer》筆記 第4章 表示式

《C++ Primer》筆記 第4章 表示式

  1. C++的表示式要不然是右值(right-value or read-value),要不然就是左值(left-value or location-value)。
  2. 當一個物件被用作右值的時候,用的是物件的值(內容);當物件被用作左值的時候,用的是物件的身份(在記憶體中的位置)。
  3. 需要右值的地方可以用左值來代替,但是不能把右值當成左值(也就是位置)使用。當一個左值被當成右值使用時,實際使用的是它的內容(值)。到目前為止,已經有幾種我們熟悉的運算子是要用到左值的:
    • 賦值運算子需要一個(非常量)左值作為其左側運算物件,得到的結果也仍然是一個左值。
    • 取地址符作用於一個左值運算物件,返回一個指向該運算物件的指標,這個指標是一個右值。
    • 內建解引用運算子、string和vector的下標運算子的求值結果都是左值。
    • 內建型別和迭代器的遞增遞減運算子作用於左值運算物件,其前置版本所得的結果也是左值。
  4. 如果表示式的求值結果是左值,decltype作用於該表示式(不是變數)得到一個引用型別。
  5. 如果改變了某個運算物件的值,在表示式的其他地方不要再使用這個運算物件。
  6. 如果m%n不等於0,則它的符號和m相同。
  7. 邏輯與(&&)和邏輯或(||)的短路求值。
  8. 賦值運算子:如果左側運算物件是內建型別,那麼初始值列表最多隻能包含一個值,而且該值即使轉換的話其所佔空間也不應該大於目標型別的空間。對類型別來說,賦值運算的細節由類本身決定。無論左側運算物件的型別是什麼,初始值列表都可以為空。此時,編譯器建立一個值初始化的臨時量並將其賦給左側運算物件。(例:int a[5] = {};
      k = {3.14}; // 錯誤:窄化轉換(換成圓括號正確)
      vector<int> vi; // 初始值為空
      vi = {0, 1, 2, 3, 4, 5}; // vi現在含有6個元素了,0到5
    
  9. 箭頭運算子作用於一個指標型別的運算物件,結果是一個左值。點運算子分成兩種情況:如果成員所屬的物件是左值,那麼結果是左值;反之,如果成員所屬的物件是右值,那麼結果是右值。
  10. 隨著條件運算(?:)巢狀層數的增加,程式碼的可讀性急劇下降。因此,條件運算的巢狀最好別超過兩到三層。
  11. 條件運算子的優先順序非常低,因此當一條長表示式中嵌套了條件運運算元表示式時,通常需要在它兩端加上括號。
  12. 關於符號位如何處理沒有明確的規定,所以強烈建議僅將位運算子用於處理無符號型別。
  13. 使用位運算:
      quiz1 |= 1UL << 27; // 表示學生27通過了測驗
      quiz1 &= ~(1UL << 27); // 學生27沒有通過測驗
      bool status = quiz1 & (1UL << 27); // 學生27是否通過了測驗
    
  14. sizeof運算子的運算物件有兩種形式sizeof (type)sizeof expr。在第二種形式中,sizeof返回的是表示式結果型別的大小。與眾不同的一點是,sizeof並不實際計算其運算物件的值(有點像decltype)。例:sizeof data.revenue;等價於sizeof Sales_data::revenue;
  15. sizeof不需要真的解引用指標也能知道它所指物件的型別。
  16. sizeof運算子的結果部分地依賴於其作用的型別:
    • 對char或者型別為char的表示式執行sizeof運算,結果得1。
    • 對引用型別執行sizeof運算得到被引用物件所佔空間的大小。
    • 對指標執行sizeof運算得到指標本身多佔空間的大小。
    • 對解引用指標執行sizeof運算得到指標指向的物件所佔空間的大小,指標不需有效(因為不實際解引用)。
    • 對陣列執行sizeof運算得到整個陣列所佔空間的大小(這裡不再是陣列首地址,和decltype類似),等價於對陣列中所有的元素各執行一次sizeof運算並將所得結果求和。注意,sizeof運算不會把陣列轉換成指標來處理。
    • 對string物件或vector物件執行sizeof運算只返回該型別固定部分的大小,不會計算物件中的元素佔用了多少空間。
  17. 因為執行sizeof運算能得到整個陣列的大小,所以可以用陣列的大小除以單個元素的大小得到陣列中元素的個數
  18. 因為sizeof的返回值是一個常量表達式,所以我們可以用sizeof的結果宣告陣列的維度。
  19. 當表示式中既有浮點型別也有整數型別時,整數值將轉換成相應的浮點型別。
  20. 整型提升負責把小整數型別轉換成較大的整數型別。對於bool、char、signed char、unsigned char、short和unsigned short等型別來說,只要它們所有可能的值都能存在int裡,他們就會提升成int型別(直接一步提升至int,沒有中間型別);否則,提升成unsigned int型別。
  21. 較大的char型別(wchar_t、char16_t、char32_t)提升成int、unsigned int、long、unsigned long、long long和unsigned long long中最小的一種型別,前提是轉換後的型別要能容納原型別所有可能的值。
  22. 如果一個運算物件是無符號型別、另外一個運算物件是帶符號型別,而且其中的無符號型別不小於帶符號型別,那麼帶符號的運算物件轉換成無符號的。如果帶符號型別大於無符號型別,此時轉換的結果依賴於機器。如果無符號型別的所有值都能存在該帶符號型別中,則無符號型別的運算物件轉換成帶符號型別。如果不能,那麼帶符號型別的運算物件轉換成無符號型別。
  23. 當陣列被用作decltype關鍵字的引數,或者作為取地址符(&)、sizeof及typeid等運算子的運算物件時,陣列轉換成指標的轉換不會發生。
  24. 指標的轉換:C++還規定了幾種其他的指標轉換方式,包括常量整數值0或者字面值nullptr能轉換成任意指標型別;指向任意非常量的指標能轉換成void;指向任意物件的指標能轉換成const void
  25. 允許將指向非常量型別的指標轉換成指向相應的常量型別的指標,對於引用也是這樣。(即非常量轉換成常量)
  26. 類型別定義的轉換:一處是在需要標準庫string型別的地方使用C風格字串string s = "a value";;另一處是在條件部分讀入istreamwhile (cin >> s)
  27. static_cast:任何具有明確定義的型別轉換,只要不包含底層const,都可以使用static_cast。
      double slope = static_cast<double>(j) / i; // 進行強制型別轉換以便執行浮點數除法
      void* p = &d; // 正確:任何非常量物件的地址都能存入void*
      double *dp = static_cast<double*>(p); // 正確:將void*轉換回初始的指標型別
    
  28. const_cast:const_cast只能改變運算物件的底層const。
      const char *pc;
      char *p = const_cast<char*>(pc); // 正確:但是通過p寫值是未定義的行為
    
      const char *cp;
      char *q = static_cast<char*>(cp); // 錯誤:static_cast不能轉換掉const性質
      static_cast<string>(cp); // 正確:字串字面值轉換成string型別
      const_cast<string>(cp); // 錯誤:const_cast只改變常量屬性
    
      // const_cast常常用於有函式過載的上下文中。
    
  29. reinterpret_cast:reinterpret_cast通常為運算物件的位模式提供較低層次上的重新解釋。reinterpret_cast本質上依賴於機器,想要安全地使用reinterpret_cast必須對涉及的型別和編譯器實現轉換的過程都非常瞭解。
      int *ip;
      char *pc = reinterpret_cast<char*>(ip);
      // 我們必須牢記pc所指的真實物件是一個int而非字元,如果把pc當成普通的字元指標使用就可能在執行時發生錯誤。例如:string str(pc);
    
  30. 舊式的強制型別轉換:type (expr);(type) expr;
  31. ++運算子:前置遞增運算子(++i)得到一個左值,它給運算子加1並得到運算物件改變後的值。後置遞增運算子(i++)得到一個右值,它給運算子加1並得到運算物件原始的、未改變的值的副本