語句

  • 一般情況下,語句(statement)是按順序執行的。

  • C++ 提供一組控制流程語句來處理更複雜的程式執行邏輯。如條件語句、循環語句與中斷當下控制流程的跳轉語句等。

基本語句

  • 當表達式末尾加上分號;)就成為語句。如:ival + 5;

    ival + 5;                // 沒有實際作用的語句。
    std::cout << ival;       // 有意義的表達式語句。   
  • 空語句只有一個分號(;),為最簡單的語句。

    ;                       // 空語句。
    • 用途:若程式在語法上需要一條語句,但邏輯實際上並不需要時,則可使用空語句。

      // 持續讀入數據直至到達文件末尾或某次輸入的值等於sought
      while(std::cin >> s && s != sought)
        ;                                   // 空語句。
  • 注意分號的使用:不要漏寫,也不要多寫,故多餘的語句不代表無害。

    ival = v1 + v2;;                         // 正確,第二個分號表達一條多餘的語句。 
    while(iter != svec.end());              // while循環體為『空語句』。
      ++iter;                               // 遞增運算子並不屬於while循環的一部分。

複合語句

  • 複合語句(compound statement)是指用『大括弧』括({})起來的(也可以為空)語句。又稱為『語句塊(block)』

  • 語句塊可視為一個『作用域(scope)』

  • 在語句塊內宣告的變數只能在該語句塊以及嵌套在該語句塊內被存取(可視 (visible))

  • 若在程式某處,語法需要一條語句,但實際邏輯上需要多條語句,此時可使用『複合語句』。例如,while 語句或 for 語句:

    while (val <= 10)
    {
      sum += val;
      ++val;
    }
  • 可在 ifswitchwhilefor 語句內的控制結構內定義變數。而在控制結構內定義的變數只能在對應的語句內可視。若要在外部程式需要使用控制變數,則變數必須定義在外部。

    while (int i = get_num())
      std::cout << i << std::endl;
    
    i = 0;                                          // 錯誤,在while外部無法存取i。
    auto beg = v.begin();                          // 變數定義在外部。
    while (beg != v.end() && *beg >= 0)
      ++beg;
    if (beg == v.end())
    {
    
    }

條件語句

if 語句

  • 根據條件來決定控制流程。

  • if 語句形式:

    if (condition)                    // condition為真,則執行statement
      statement
    
    if (condition)                   // condition為真,則執行statement1,否則執行statement2。
      statement1
    else
      statement2
    • condition 都必須被 () 包起來。

    • condition 可以是表達式,也可以初始化一個變數。無論是哪一種,都必須能轉換為『布林值』

    • statement、statement1 與 statement2 都必須為語句塊。

    • 當單一條語句無法滿足邏輯需求時,可加入『大括弧』形成語句快。

  • 當 if 語句嵌套在另一個 if 語句時,請留意 else 與哪一個 if 匹配為一組:

    • C++ 規定 else 與離他最近且尚未匹配的 if 作為同組。
// 請注意實際程式執行狀況與程式縮排格式的差別:
if (grade % 10 >= 3)
  if (grade % 10 > 7)
    lettergrade += '+';               // 末尾是8或9的成績添加一個加號
else
  lettergrade += '-';                 // 末尾是3、4、5、6或是7的成績添加減號
// 縮排格式與執行過程相符,但並非程式設計師意圖。
if (grade % 10 >= 3)
  if (grade % 10 > 7)
    lettergrade += '+';               // 末尾是8或9的成績添加一個加號
  else
    lettergrade += '-';                 // 末尾是3、4、5、6或是7的成績添加減號
if (grade % 10 >= 3) 
{
  if (grade % 10 > 7)
    lettergrade += '+';               // 末尾是8或9的成績添加一個加號
}  
else                                  // 大括弧強迫else與外層if匹配
  lettergrade += '-';                 // 末尾是3、4、5、6或是7的成績添加減號

switch 語句

  • switch 後面的表達式可以是整數表達式,字元表達式,也可以是 enum 型態,或可轉換為整數型態,並根據此整數回傳值決定執行動作。

  • 每個 case 後面的常數表達式彼此間必須不同,否則會出現編譯錯誤。

  • switch 語句形式:

    switch (expr)
    {
    case 常數1:
      語句1;
      break;
    case 常數2:
      語句2;
      break;
    
    .....
    
    case 常數n:
      語句n;
      break;
    default:
      default語句;
    
    }
  • switch 語句由以下4部分組成:

    • switch 表達式。

    • 一組 case 條件。

    • 與一個或一組 case 條件相關聯的語句或語句塊。

    • default 標籤。也可以不使用,但當所有 case 條件都不滿足時,則整個 switch 就不被執行。

  • 若某個 case 條件匹配成功,將從該條件開始往後依序執行所有 case 分支。除非程式顯示中斷該過程,否則直到 switch 的結尾才會停止。

  • 若要避免執行後續 case 分支,可在下一個 case 標籤之前加上 break 語句。

  • 一般不要省略 case 分支最後的 break 語句。若不寫,請加上註釋說明之。

    unsigned vowelCnt = 0;
    
    switch (ch)
    {
      //  出現a、e、i、o、u中的任一個都會將vowelCnt的值加1。
    case 'a'":
    case 'e':
    case 'i':
    case 'o':
    case 'u':
      ++vowelCnt;
      break;
    }

循環語句

goto 語句

  • 又稱『無條件轉向語句』。

  • 也被視為『跳轉語句』。

  • 用來跳轉到程式某個位置進行執行。

  • 語法:

    • goto 語句標號;

      • 語句標號:為標識符(只能由字母、數字,_ 三種字元組成,且第一個字元必須為字母或 _ ,另外不可為系統保留字)。
    • if 語句一起構成循環結構。

    • 從循環體內部跳轉到循環體外部(但會破壞結構化程式設計原則,建議不使用)。

    • 在大多數情況下,可以使用其他循環語句來代替 goto 語句。

    • goto 語句無法用來跨函數跳轉。

      int i = 1, sum = 0;
      loop:                         // 語句標號。
        if (i <= 100)
        {
          sum = sum + i;
          i++;
          goto loop;
        }
      printf("1 + 2 + 3 + ... + 100 和值為 %d。\n", sum);
      • loop: 為語句標號。後面接:

      • 當程式執行到 goto loop; 語句時可以直接跳轉至 loop 標號所在行並重新繼續往下執行,如此反覆。

      void func1()
      {
        lbl1:
        int k;
        k = 1;
        goto lbl1;
      }
      
      
      void func2()
      {
        lbl2:
        int a;
        a = 1;
      }
      • 無法將 func1 函數中的 goto lbl2; 修改為 goto lbl2;,這樣做會發生語法錯誤:因為 lbl2 位於 func2 函數內,goto 無法跨函數跳轉。

while 語句

  • 語法:

    • while (表達式) 欲執行的語句;

      • 先判斷表達式的值,若表達式值為真(非 0),則執行 欲執行的語句(循環體),然後再次循環回去,重新判斷表達式值,再決定是否再次欲執行的語句(循環體)部分,如此反覆。

      • 若要執行多條語句,則使用 {} 括起來。

    int i = 1, sum = 0;
    while (i <= 100)
    {
      sum = sum + i;
      i++;
    }
    printf("1 + 2 + 3 + ... + 100 和值為 %d。\n", sum);

do … while 語句

  • 語法:

    • do 要執行的語句 while (表達式);

      • 先執行一次要執行的語句,然後判斷表達式的值,若表達式之值為 true(非0)時,繼續執行循環體語句,然後繼續判斷表達式的值,如此反覆,直到表達式的值為 false(0),跳出整個do … while 語句,接著繼續往後面語句執行。
    • 至少會執行一次循環,然後才會進行判斷表達式是否為 true

      int i = 1, sum = 0;
      do{
        sum = sum + i;
        ++i;
      } while (i <= 100);
      printf("1 + 2 + 3 + ... + 100 和值為 %d。\n", sum);
  • do … while 語句使用情境較少,因為大多數情況下,do … while 語句可以被 while 語句來替代。

for 語句

  • 語法:for (表達式1; 表達式2; 表達式3) 內嵌語句;

    • for 語句執行步驟:

      1. 表達式1求值。

      2. 表達式2求值。

      3. 表達式2之值為 true(非0),則執行 for 語句中的內嵌語句,接著執行表達式3,反覆循環步驟2,直到表達式2的值為 false(0),則循環結束,跳到整個 for 語句後面的語句來執行。

    • 表達式1只會被執行一次。

    • 循環執行幾次,表達式3就執行幾次。

    int i, sum = 0;
    for (i = 1; i <= 100; ++i)                      
    {
      sum = sum + i;
    }
    std::cout << "sum = " << sum << std::endl;             // sum = 5050。
  • 表達式1可以省略,但其後的分號(;)不能省略。

    int i, sum = 0;
    i = 1;
    for ( ; i <= 100; ++i)                      
    {
      sum = sum + i;
    }
    std::cout << "sum = " << sum << std::endl;             // sum = 5050。
  • 表達式2可以省略,也就是不加入循環結束的條件,但分號(;)仍然不能省略。

    int i, sum = 0;
    for (i = 1; ; ++i)
    {
      sum = sum + i;
      if (i >= 100)
        break;
    }
    std::cout << "sum = " << sum << std::endl;             // sum = 5050。
  • 表達式3可以省略,但必須保證循環能正常結束,否則循環會無止盡執行。

    int i, sum;
    for (sum = 0, i = 1; i <= 100; )
    {
      sum = sum + i;
      ++i;
    }
    std::cout << "sum = " << sum << std::endl;             // sum = 5050。

三個表達式都省略:不設定初值,不判斷條件,循環變數不變動。如下所示:

while (1)
{
  
}


for(;;)
{
  
}
int i = 1, sum = 0;
for(;;)
{
  sum = sum +i;
  if (i >= 100)
    break;
  ++i;
}
std::cout << "sum = " << sum << std::endl;             // sum = 5050。
  • 表達式1可以用來設定循環變數的初值,也可以設定與循環變數無關的其他表達式。

    int i = 1, sum;
    for (sum = 0; i <= 100; ++i)
    {
      sum = sum + i;
    }
    std::cout << "sum = " << sum << std::endl;             // sum = 5050。

嵌套式循環語句

int k = 0;
for (size_t i = 2; i <= 9; i++)
{
  for (size_t j = 1; j <= 9; j++)
  {
    k = i * j;
    std::cout << i << " * " << j << " = " << k << std::endl;
  }
  std::cout << std::endl;
}

跳轉語句

  • 跳轉語句會中斷當下的執行過程。

break 語句

  • break 語句會中斷離它最近的 whiledo .. whileforswitch 語句,並從這些語句後的第一條語句開始繼續執行。

  • break 語句只能出現在迭代語句或 switch 語句的內部。

  • break 語句通常與一個 if 語句配合使用。

    for (size_t i = 0; i < 5; ++i)
    {
      if (i == 2)
        break;
      std::cout << i << std::endl;
    }
    int i, sum = 0;
    for (i = 0; i < 100; i++)
    {
      sum = sum + i;
      if (sum >= 4000)
        break;
    }
    std::cout << "sum = " << sum << std::endl;
    std::cout << "i = " << i << std::endl;
    int i, j, k;
    for (i = 1; i < 9; i++)
    {
      for (j = 1; j < i; j++)
      {
        k = i * j;
        std::cout << i << " * " << j << " = " << k << std::endl;
        break;html 
      }
      printf("\n");
      break;
    }
    std::cout << "流程結束!" << std::endl;

continue 語句

  • continue 語句終止最近循環中當下的迭代並立即開始下一次的迭代。

  • continue 語句只能用在 forwhiledo … while 循環的內部,或是嵌套在此類循環裡的語句或語句塊內部。

  • break 類似,當出現在嵌套循環中的 continue 語句僅作用在離它最近的循環。

  • break 不同,只有當 switch 嵌套在循環內,才能在 switch 內使用 continue 語句。

    int i;
    for (i = 1; i <= 100; ++i)
    {
      if (i % 3 == 0)
        continue;
      std::cout << i << std::endl;
    }

常見的錯誤

  • for 迴圈語句分號:

    #include <iostream>
    
    int main() {
        double x = 2.0;
        for (int i = 0; i < 5; i++);        // 分號不應該出現在這個位置
        {
            x *= 2.0;
        }
        std::cout << "x = " << x << std::endl;
        return 0;
    }
    • for迴圈結束的地方有一個分號;,這使得 for 迴圈的主體變成了一個空語句,這意味著迴圈內實際上什麼都沒做。

    • for 迴圈後面的大括號內的語句是一個獨立的語句塊,它只執行一次,不會被 for 迴圈重複執行。

  • if 語句比較兩個浮點數:

    • 在C++中,整數的相等性比較相對簡單,但對於雙精度浮點數進行相等性比較時需要考慮浮點數運算中的四捨五入誤差。下面展示了如何比較兩個整數和如何正確地比較兩個雙精度浮點數。

      • 整數比較

        • 假設我們有兩個整數變數ij,我們希望將另一個整數變數k設為0,如果這兩個變數的值相等,那麼我們可以使用以下程式碼:

          int i = 5; 
          int j = 5; 
          int k;
          
          if (i == j) 
          { 
            k = 0; 
          } 
          else 
          { 
            k = 1; 
          }
          • 在這段程式碼中,如果ij相等,則k被設為0;否則,k被設為1。
      • 浮點數比較

        • 對於雙精度浮點數變數pq,由於浮點數運算可能會產生四捨五入誤差,直接比較兩個浮點數是否相等可能會得不到預期的結果。相反,我們應該檢查這兩個數的差值是否小於某個非常小的數值(如epsilon)。

          #include <iostream>
          #include <cmath> // 包含cmath頭文件以使用fabs函數
          
          using namespace std;
          
          int main() 
          {
            double p = 5.0;
            double q = 5.000000000000186;
            int k;
            double epsilon = 1e-9; // 定義一個非常小的數值
          
            if (fabs(p - q) < epsilon) 
            {
              k = 0;
            } 
            else 
            {
              k = 1;
            }
          
            cout << "k = " << k << endl;
          
            return 0;
          }
          • 在浮點數運算中,由於電腦的有限精度,計算結果可能會包含一些非常小的誤差。直接比較兩個浮點數是否相等是不可靠的,故使用一個非常小的數值epsilon來比較它們的差值是否足夠小,以判斷它們是否相等。

表達式補充

邏輯與關係運算子

結合律 運算子 功能 語法
! 邏輯 非 !expr

<

<=

>

>=

(關係運算子)

小於

小於等於

大於

大於等於

expr < expr

expr <= expr

expr > expr

expr >= expr

==

!=

相等

不相等

expr == expr

expr != expr

&& 邏輯且 expr && expr
|| 邏輯 或 expr || expr
  • 『邏輯且』運算子(&&):當左右兩側運算元都為真時,結果為真。

  • 『邏輯或』運算子(||):只要當兩側運算元中一個為真時,結果為真。

  • &&||運算子都是由左側運算元先運算,再求右側運算元之值。

  • 短路求值(short-circuit evalution)只有當左側運算元無法確認表達式最後結果時,才會對右側運算元進行求值。

    • &&:左側為真(true),才會對右側運算元求值。

    • ||:左側為假(false),才會對右側運算元求值。

  • 『邏輯非』(!)運算子會將運算元之值取相反後並將其回傳之。

  • 關係運算子:比較兩側運算元大小關係並回傳布林值。滿足『左結合律』。注意下例:

    if (i < j < k)                          // 注意:拿 i < j 的布林值結果與k比較
    {                                       // 若k大於1則一定為真
    
    }
    
    if (i < j && j < k)                     // 應該寫成這樣才正確。  
    {                                       
    
    }
    • 上述問題在於布林值的比較操作。 在 C++ 中使用連續比較運算子(如 i < j < k)時,表達式的行為可能不是你所期望的。這是因為 C++ 的比較運算子是從左到右依次求值的,而布林值(truefalse)會被隱式轉換為整數 10

      • 例如,表達式 i < j < k 將會被解析為 (i < j) < k,這樣 i < j 的結果是一個布林值(truefalse),然後這個布林值會被轉換為整數 10,再與 k 進行比較。這可能會導致意想不到的結果。

      • 正確作法應為使用邏輯運算子 && 來明確地進行兩次比較:

  • 相等性測試與布林字面值

    if (val) {/*......*/ }              // 若val是任意非0值,則條件為真
    if (!val) {/*.....*/}               // 若val為0,則條件為真。
    if (val == true) {/*.....*/}        // 只有當val等於1時,條件才為真。
    • 若 val 不是布林值,則進行比較前會先將 true 轉換為 val 的型態,意指:

      if (val == 1) {/*.....*/}
    • 進行比較運算時,除非比較的對象為布林值,建議不要使用布林字面值 truefalse 作為運算元。

賦值運算子

  • 賦值運算子(=)左側的運算元一定是一個可被修改的『左值(變數)』,並將『等號右側』的賦值給『等號左側』的變數。

  • 可理解成:給變數一個值,或是更改變數內容為某一個值。

  • char a;此為變數宣告(未初始化)。系統會將變數 a 分配一個 byte的記憶體空間(未定義值)。

  • char a = 90;定義時初始化(非賦值語句)。系統會給變數 a 分配記憶體空間,接著在這個內存上放入值 90。

    int i = 0, j = 0, k = 0              // 初始化而非賦值。
    const int ci = i;                    // 初始化而非賦值。
    • 下列賦值語句皆為錯誤:

      1024 = k;                        // 錯誤:字面值為右值。
      i + k = k;                       // 錯誤:算術表達式為右值。
      ci = k;                          // 錯誤:ci為 const 常數的左值。
  • 賦值表達式的結果是『左側運算元』,且其為『左值』,而結果的型態就是左側運算元的型態。

  • 若賦值運算子的左右兩側運算元型態不同,則右側運算元將轉換成左側運算元的型態

    k = 0;                             // 型態為int,值為0。
    k = 3.14;                          // 型態為int,值為3。
  • C++ 11 標準:允許使用大括弧({})的『初始化列表』作為賦值運算子右側的運算元:

    k = {3.14};                        // 錯誤:發生型態窄化轉換。
    std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 
  • 賦值運算子滿足『右結合律』,此與二元運算子不同。

    int ival, jval;
    ival = jval = 0;                  // 正確:都被賦值為0。
    // ival = (jval = 0);             // 滿足右結合律。
    int ival, *pval;
    ival = pval = 0;                  // 無法將指標值(int*)賦值給int
  • 賦值運算子(=優先級較低

    int i;
    while ( (i = get_value() ) != 42)
    {
      // 其他的處理
    }
    • 上述程式碼 while 的條件內若不加上 (),則比較運算子 != 的運算元將是 get_value 函數回傳值與 42,比較結果顯然不是我們所預期的答案。

    • 故在條件語句中,賦值部分應該加上()

  • 複合賦值運算子。

    • a = a operator ba += 1等價 a = a + 1

      • +=

      • -=

      • *=

      • /=

      • %=

  • 優先級順序:賦值運算子 < 關係運算子 < 算術運算子

遞增與遞減運算子

  • 共兩種:

    • 遞增運算子(++):為物件加一,又分『前置』,如 ++i 與『後置』,如 i++

    • 遞減運算子(--):為物件減一,又分『前置』,如 --i 與『後置』,如 i--

  • 前置遞增(遞減)運算子:對『左值運算元』加一(減一),再將『改變之後的物件』作為執行結果(作為回傳值)。

  • 後置遞增(遞減)運算子:也是對『左值運算元』加一(減一),但執行結果是運算元被『改變之前的物件』版本的拷貝。

    int i = 0, j;
    j = ++i; 
    std::cout << "j = ++i" << std::endl;
    std::cout << "i = "<< i << std::endl;
    std::cout << "j = "<< j << std::endl;
    int i = 0, j;
    j = i++; 
    std::cout << "ex: i++" << std::endl;
    std::cout << "j = i++" << std::endl;
    std::cout << "i = "<< i << std::endl;
    std::cout << "j = "<< j << std::endl;
  • 前置版本:將物件本身作為『左值』回傳。

  • 後置版本:將物件的原始值的副本(拷貝)作為『右值』回傳。

  • 注意:除非必要,建議『不要』使用遞增遞減運算子的『後置版本』,原因如下:

    • 前置版本避免了不必要的工作:它將運算元加一(減一)後,直接回傳改變後的運算元。

    • 後置版本需要將原始值進行儲存,以便於之後回傳未修改的內容。

  • 若不需要修改前的原始值,則建議使用前置版本。

  • 範例——語句中混用『解參考運算子』與『遞增運算子』:

    auto pbeg = v.begin();
    // 輸出元素直到遇到第一個負值為止
    while (pbeg != v.end() && *beg >= 0)
    {
      std::cout << *pbeg++ << std::endl;   // 輸出當前值,並將pbeg向前移動一個元素
    }
    
    
    // std::cout << *iter++ << std::endl;  // 更簡潔~~
    // 上述語句可以取代下列兩行語句:
    // std::cout << *iter << std::endl;
    // ++iter;
    • *pbeg++ 等於 *(pbeg++):因為『遞增運算子』的優先級高於『解參考運算子』,故 () 可以省略。

    • 此時解參考運算子的運算元是『pbeg未增加之前的值

    • 此語句輸出 pbeg 開始時指向的那個元素,並將指標向前移動一個位置。

  • 注意:大部分運算子沒有規定運算元的求值順序。故當一條子表達式改變運算元的值,而存在另外一條使用該運算元值的子表達式時,求值順序就非常重要了。

    for (auto it = s.begin(); it != s.end() && !issapce(*it); ++i)
    {
      *it = toupper(*it);                      // 將當前字元改為大寫形式。
    }
    • 若改寫成:
    while (beg != s.end() && !isspace(*beg))
    {
      *beg = toupper(*beg++);                 // 錯誤:該賦值語句無法定義
    }
    • 上述範例將產生未定義的原因為:

      • 賦值運算子左右兩側的運算元都用到 beg,且右側的運算元還改變了 beg 的值,故該語句是未定義的。

      • 編譯器可能按照下列任意一種方式處理該表達式:

        1. *beg = toupper(*beg); // 若先求左側的值。

        2. *(beg + 1) = toupper(*beg); // 若先求右側的值。

  • 前置遞增遞減運算子(++i–-i)為『左值表達式』(回傳左值):

    • ++i:直接給 i 變數加 1,然後回傳 i 本身。因為 i 為變數,所以可以被賦值,故為左值表達式。

      int i = 5;
      (++i) = 20;                            // i被賦值為20。
  • 後置遞增遞減運算子(i++i--)為『右值表達式』(回傳右值):

    • i++:先產生一個『臨時變數』來保存 i 的值用於回傳,接著再對 i 加 1,之後系統在釋放上述的『臨時變數』。被釋放掉的臨時變數將無法被賦值。故為『右值表達式』。

      int i = 5;
      // (i++) = 20;                        // 錯誤:表達式必須是可被修改的左值。
int i = 1;
int &&r1 = i++;                      // 正確:右值參考可以綁定右值,但與r1與i沒有關係。
// int &r2 = i++;                    // 錯誤:左值參考無法綁定右值表達式。
int  &r3 = ++i;                      // 正確:左值參考可以綁定左值。
// int &&r4 = ++i;                   // 錯誤:右值參考無法綁定左值。

std::cout << "i = " << i << std::endl;
std::cout << "r1 = " << r1 << std::endl;
std::cout << "r3 = " << r3 << std::endl;
  • 補充說明:

    • &&r1綁定右值,但 r1 本身為左值。

    • 所有變數都為左值。

    • 任何函數的形式參數都為左值,如 void f(int &&w),形式參數 w 的型態為右值參考(意指其可以綁定右值),但 w 本身為左值。

成員存取運算子

  • 點運算子(.):可取得物件的成員(member)。

  • 箭頭運算子(->):等價於 (*ptr).mem

    std::string s1 = "a string", *p = &s1;
    auto n = s1.size();                     // 執行string物件s1的size成員。
    n = (*p).size();                        // 執行p所指向的物件的size成員。
    n = p->size();                          // 等價於 (*p).size()。
  • 因為『解參考運算子』的優先級「低」於『點運算子』,故執行解參考運算的子表達式必須加上 ()

    // *p.size();                              // 錯誤:p為一個指標,它並沒有名為size的成員。
  • 箭頭運算子作用於一個指標型態的運算元,其結果為『左值』

  • 點運算子有兩種情況:

    • 若成員所屬的物件為左值,則結果為左值。

    • 若成員所屬的物件為右值,則結果為右值。

條件運算子

  • 條件運算子(? :):允許將簡單的 if-else 的邏輯嵌入到單個表達式中:

  • 語法:cond ? expr1 : expr2;

    • 其中,cond 是判斷條件的表達式,而 expr1expr2 是兩個型態相同或可能轉換為某個型態的表達式。

    • 先執行 cond 的值,若條件為真(true)則對 expr1 求值並回傳該值,否則對 expr2 求值並回傳結果。

    max = (a > b)? a:b;

逗點運算子

  • 逗點運算子( , comma operator)是優先級『最低』的運算子

  • 逗點運算子含兩個運算元,按照『由左至右』的順序依序求值:

    表達式1, 表達式2, 表達式3, ……., 表達式N

  • 逗點運算子先對左側表達式求值,接著將求值結果丟棄。其真正的回傳值為『最右側表達式』之值表達式N)。

    std::vector<int>::size_type cnt = ivec.size();
    
    for(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)
      ivec[ix] = cnt;
    • 上述循環 for 語句中的表達式進行:遞增 ix,遞減 cnt,每次循環迭代 ixcnt 都會改變。

    • 只要 ix 滿足條件,則將當前元素設定為 cnt 當下之值。

    int a;
    a = (4, 5);                      // a = 5,因為逗點運子優先級太低,故必須加()。
    a = (3 + 5, 6 + 8);              // a = 14。
    a = 3 * 5, a * 4;                // 表達式值為60。因為先計算a = 3*5,再計算a*4。故a的值為15,整個逗點表達式為60。
    int x, a;
    x = (a = 3, 6*3);                  // a = 3, x = 18。
    x = a = 3, 6*a;                    // a = 3, x = 3,逗點表達式的結果為18,但並未被使用。
    int result = (x = a = 3, 6 * a);   // result = 18。

強制型態轉換

  • 使用強制型態轉換時,則會抑制編譯器的報錯行為,故強制型態轉換一般不建議使用

  • 建議使用 C++ 風格。

  • 相較於之前討論的『隱式型態轉換』,亦存在『顯式型態轉換』(又稱『強制型態轉換』)。如:

    // int k = 5 % 3.2;                // 錯誤: % 運算子兩側必須為整數型態。
    int k = 5 % (int)3.2;
    // 也可以寫成下列形式
    int k2 = 5 % int(3.2);
    • 上例屬於 C 語言風格的『強制型態轉換』-使用 () :沒有形態的檢查,型態轉換的對錯由程式設計師負責。
  • C++ 提供更安全的『強制型態轉換』:

    • static_cast<>

    • dynamic_cast<>

    • const_cast<>

    • reinterpret_cast<>

  • 語法:強制型態轉換名稱<type>(expression);

  • static_cast<>

    • 『靜態轉換』,可視為一般轉換,與 C 語言的強制型態類似。

    • 編譯期就會進行型態轉換的檢查。

      double f = 100.123f;
      int i = (int)f;                                  // C語言風格。
      int i2 = static_cast<int>(f);                    // 100, C++語言風格。
    • 子類別轉換為父類別時,也可以使用 static_cast

      class A                             // 父類別
      {
      
      };
      
      class B:public A                    // 子類別
      {
      
      };
      
      int main()
      {
        B b;
        A a = static_cast<A>(b);          // 允許將子類別轉換為父類別。反之,則錯誤。
        return 0;
      }
    • void* 指標與其他型態指標之間的轉換:

      • void* 為『無型態指標』,也就是可以指向任何型態的指標。
      int i = 10;
      int *p = &i;
      void *q = static_cast<void*>(p);
      int *db = static_cast<int*>(q);
    • 不可用於指標型態之間的轉換,如 int*double* 等。

      double f = 100.0;
      double *pf = &f;
      // int *i = static_cast<int*>(pf);                      // 錯誤。
      // float *fd = static_cast<float*>(pf);                 // 錯誤。
  • dynamic_cast<>

    • 顧名思義,該轉換用於『執行期』進行型態檢查。

    • 主要用於『父類別轉換為子類別』

      • 它主要用於將父類別指標或參考轉換為子類別型態。

      • 若轉換失敗,dynamic_cast<> 則回傳 nullptr(如果轉換的是指標)或拋出 std::bad_cast 異常(如果轉換的是參考)。

    • 在類型安全性方面提供了保證,但相較於其他類型的轉換有較高的成本。

  • const_cast<>

    • 移除指標變數參考const 的屬性。故功能較為單純。

    • 編譯期進行此型態轉換檢查。

    • 若原來變數為 const,當強制使用 const_cast 移除 const 屬性並修改其值,此為未定義行為,請避免之。

      const int ai = 90;
      //int ai2 = const_cast<int>(ai);            // 錯誤:因ai不是指標,也不是參考,故無法轉換。 
      const int *pai = &ai;
      int *pai2 = const_cast<int*>(pai);          // 正確。
      *pai2 = 120;                                // 建議不要這樣做!
      std::cout << ai << std::endl;
      std::cout << *pai << std::endl;
  • reinterpret_cast

    • 可用來處理兩個型態無關連型態轉換。

    • 編譯期進行此型態轉換檢查。

    • 這種轉換是非常低層次的,並且通常與底層機器的內存表示相關,因此被認為是危險的。

    • 因編譯器不會提出任何警告與錯誤訊息,故具危險性。建議避免使用

    • 轉換指標類型reinterpret_cast 可用於將一個指標類型轉換為另一個完全不相關的指標類型。

    • 轉換為整數類型reinterpret_cast 可用於將指標轉換為整數類型,這在某些底層編程中可能是必要的。