跳轉到內容

C++ 程式設計

來自 Wikibooks,為開放世界提供開放書籍

控制流語句

[編輯 | 編輯原始碼]

通常程式不是線性指令序列。它可能會重複程式碼或為給定的路徑目標關係做出決定。大多數程式語言都有控制流語句(結構),這些語句提供一些控制結構,用於指定執行程式必須執行的順序,從而允許此順序的順序變化。

  • 語句可能只在特定條件下(條件語句)執行,
  • 語句可能在特定條件下(迴圈)重複執行,
  • 一組遠端語句可能執行(子例程)。
邏輯表示式作為條件
邏輯表示式可以在迴圈和條件語句中使用邏輯運算子,作為要滿足的條件的一部分。

異常和非結構化控制流

[編輯 | 編輯原始碼]

有些指令沒有特定的結構,但將在塑造其他控制流語句的結構方面具有非凡的用處,必須特別注意防止非結構化和混亂的程式設計。

break 將強制當前迴圈迭代退出到迴圈外部的下一條語句。除了switch 控制語句之外,它在迴圈結構之外沒有用處。

continue 指令用於迴圈內部,它將停止當前迴圈迭代,並啟動下一個迭代。

goto 關鍵字不鼓勵使用,因為它使得難以跟蹤程式邏輯,從而導致錯誤。goto 語句使當前執行執行緒跳轉到指定的標籤。

語法
label:
  statement(s);

goto label;

在一些罕見的情況下,goto 語句允許編寫簡潔的程式碼,例如,在處理導致函式退出時清理程式碼的多個退出點時(並且異常處理或物件解構函式不是更好的選擇)。除了這些罕見情況外,無條件跳轉的使用通常是設計複雜的徵兆,因為存在許多巢狀語句層級。

在特殊情況下,例如重度最佳化,程式設計師可能需要對程式碼行為進行更多控制;goto 允許程式設計師指定執行流程直接且無條件地跳轉到所需的標籤。標籤是函式中其他地方的標籤語句的名稱。

注意
在軟體工程領域,有一篇經典的論文是由W. A. Wulf撰寫的,名為"反對 GOTO 的論據",它是在 1972 年 10 月第 25 屆ACM 全國大會上發表的,當時關於goto 語句的爭論正達到頂峰。在這篇論文中,Wulf 認為goto 語句應該被視為危險的。Wulf 還以他對效率的評論而聞名:“在效率的名義下(不一定能實現它),犯下的計算錯誤比任何其他單一原因(包括盲目的愚蠢)都要多。”

例如,goto 可以用於退出兩個巢狀迴圈。此示例在用零替換遇到的第一個非零元素後退出。

for (int i = 0; i < 30; ++i) {
  for (int j = 0; j < 30; ++j) {
    if (a[i][j] != 0) {
       a[i][j] = 0;
       goto done;
     }
  }
}
done:
/* rest of program */

雖然簡單,但它們很快就會導致難以閱讀和難以維護的程式碼。

// snarled mess of gotos

int i = 0;
  goto test_it;
body:
  a[i++] = 0;
test_it:
  if (a[i]) 
    goto body;
/* rest of program */

比等效的

for (int i = 0; a[i]; ++i) {
  a[i] = 0;
}
/* rest of program */

Goto 通常用於效能至關重要的函式或機器生成的程式碼的輸出(如由yacc 生成的解析器)。

goto 語句幾乎應該總是避免,但有些情況下它會提高程式碼的可讀性。一種情況是“錯誤部分”。

示例

#include <new>
#include <iostream>

...

int *my_allocated_1 = NULL;
char *my_allocated_2 = NULL, *my_allocated_3 = NULL;
my_allocated_1 = new (std::nothrow) int[500];

if (my_allocated_1 == NULL)
{  
  std::cerr << "error in allocated_1" << std::endl;
  goto error;
}

my_allocated_2 = new (std::nothrow) char[1000];

if (my_allocated_2 == NULL)
{  
  std::cerr << "error in allocated_2" << std::endl;
  goto error;
}
    
my_allocated_3 = new (std::nothrow) char[1000];

if (my_allocated_3 == NULL)
{  
  std::cerr << "error in allocated_3" <<std::endl;
  goto error;
}
return 0;
    
error:
  delete [] my_allocated_1;
  delete [] my_allocated_2;
  delete [] my_allocated_3;
  return 1;

此結構避免了處理錯誤來源的麻煩,並且比使用控制結構的等效結構更簡潔。因此,它不易出錯。

注意
雖然上面的示例展示了goto 的合理用法,但在實踐中並不常見。異常以更清晰、更有效和更有組織的方式處理這種情況。這將在“異常處理”中詳細討論。使用 RAII 管理記憶體等資源也可以避免對大多數上面所示的顯式清理程式碼的需求。


abort(), exit() 和 atexit()

[編輯 | 編輯原始碼]

正如我們將在後面看到的,C++ 中包含的標準 C 庫 還提供了一些有用的函式,可以改變控制流。有些函式允許你終止程式的執行,使你能夠設定返回值或在終止請求時啟動特殊任務。你將不得不跳到abort() - exit() - atexit() 部分以獲取更多資訊。

條件語句

[編輯 | 編輯原始碼]

很可能在沒有任何編寫過的有意義的程式中,計算機都不會根據某些特定條件展示基本的決策能力。實際上,可以認為沒有人類活動是不涉及決策、本能或其他方面的。例如,在駕駛汽車並靠近紅綠燈時,一個人不會想,“我將繼續駛過十字路口。”相反,一個人會想,“如果燈是紅色,我會停車,如果燈是綠色,我會走,如果燈是黃色,如果我以一定速度行駛並且距離十字路口一定距離,我會走。”這些過程可以使用條件語句進行模擬。

條件語句是指示計算機僅在滿足特定條件時才執行某些程式碼塊或更改某些資料的語句。

最常見的條件語句是 if-else 語句,條件表示式和 switch-case 語句通常用作更簡潔的方法。

if (分支)

[編輯 | 編輯原始碼]

if 語句允許根據指定的條件選擇一條可能的路徑。

語法

if (condition)
{
  statement;
}

語義

首先,評估條件

  • 如果條件為真,則語句在繼續執行主體之前執行。
  • 如果條件為假,則程式跳過語句並繼續執行程式的其餘部分。

注意
if 語句中的條件可以是任何解析為布林值或空/非空值的表示式;你可以在其中宣告變數、巢狀語句等。這同樣適用於其他流程控制條件語句(例如:while),但通常被認為是不好的風格,因為它唯一的好處是透過使程式碼更難讀來簡化輸入。

這種特性很容易導致簡單的錯誤,比如將 a=b(賦值)誤寫成 a==b(條件)。因此,人們採用了這樣一種編碼實踐:透過反轉表示式(或使用常量變數)自動將錯誤暴露出來,編譯器會生成錯誤。

最近的編譯器支援檢測此類事件並生成編譯警告。

示例

if(condition)
{
  int x; // Valid code
  for(x = 0; x < 10; ++x) // Also valid.
    {
      statement;
    }
}
flowchart from the example
來自示例的流程圖

注意
如果你想避免一直輸入 std::cout、std::cin 或 std::endl;你可以在程式的開頭包含 using namespace std,因為 cout、cin 和 endl 是 std 名稱空間的成員。

有時程式需要根據條件選擇兩個可能的路徑之一。為此,我們可以使用 if-else 語句。

if (user_age < 18)
{
    std::cout << "People under the age of 18 are not allowed." << std::endl;
}
else
{
    std::cout << "Welcome to Caesar's Casino!" << std::endl;
}

在這裡,如果使用者未滿 18 歲,我們將顯示一條訊息。否則,我們允許使用者進入。只有當 'user_age' 小於 18 時才會執行 if 部分。在其他情況下(當 'user_age' 大於或等於 18 時),將執行 else 部分。

if 條件語句可以串聯在一起,以實現更復雜的條件分支。在這個例子中,我們擴充套件了之前的例子,還檢查了使用者是否超過 64 歲,並在滿足條件時顯示另一條訊息。

if (user_age < 18)
{
  std::cout << "People under the age of 18 are not allowed." << std::endl;
}
else if (user_age > 64)
{
  std::cout << "Welcome to Caesar's Casino! Senior Citizens get 50% off." << std::endl;
}
else
{
  std::cout << "Welcome to Caesar's Casino!" << std::endl;
}
flowchart from the example
來自示例的流程圖

注意

  • breakcontinueifelse 無關。
  • 雖然你可以使用多個 else if 語句,但在處理許多相關條件時,建議使用 switch 語句,我們將在下一節中討論。

switch (多重分支)

[edit | edit source]

switch 語句根據特定的整數值進行分支。

switch (integer expression) {
    case label1:
         statement(s)
         break;
    case label2:
         statement(s)
         break;
    /* ... */
    default:
         statement(s)
}

正如你在上面的方案中看到的,case 和 default 在程式碼塊的末尾都有一個 "break;" 語句。這個表示式將導致程式退出 switch,如果沒有新增 break,程式將繼續執行其他 case 中的程式碼,即使整數表示式不等於該 case。這可以在某些情況下被利用,如下一個例子所示。

我們要將輸入從數字分離到其他字元。

 char ch = cin.get(); //get the character
 switch (ch) {
     case '0': 
          // do nothing fall into case 1
     case '1': 
         // do nothing fall into case 2
     case '2': 
        // do nothing fall into case 3
     /* ... */
     case '8': 
        // do nothing fall into case 9
     case '9':  
          std::cout << "Digit" << endl; //print into stream out
          break;
     default:
          std::cout << "Non digit" << endl; //print into stream out
 }

在這段小程式碼中,對於每個小於 '9' 的數字,它將繼續傳播到 case,直到它到達 case '9' 並列印 "digit"。

如果不是這樣,它將直接進入預設的 case,在那裡它將列印 "Non digit"。

注意

  • 請務必使用 break 命令,除非你希望多個條件具有相同的操作。否則,它將“穿透”到下一組命令。
  • break 只能退出最內層。例如,如果你在 switch 中,需要退出包圍的 for 迴圈,你可能需要考慮新增一個布林值作為標誌,並在 switch 程式碼塊之後檢查該標誌,而不是使用其他可用的方法。(儘管如此,根據情況,將程式碼重構到一個單獨的函式中並從該函式中返回可能更乾淨,並且使用行內函數和/或智慧編譯器,這樣做不會產生任何執行時開銷。)
  • continueswitch 程式碼塊無關。在 switch 程式碼塊中呼叫 continue 將導致包圍 switch 程式碼塊的迴圈 "continue"。

迴圈(迭代)

[edit | edit source]

迴圈(也稱為迭代或重複)是一系列語句,這些語句只指定一次,但可以連續執行多次。迴圈“內部”的程式碼(迴圈的主體)會按照指定的次數執行,或者針對每個專案執行一次,或者直到滿足某個條件為止。

迭代是重複一個過程,通常是在計算機程式中。令人困惑的是,它可以用作通用的術語,與重複同義,也可以用來描述具有可變狀態的特定形式的重複。

當以第一種意義使用時,遞迴是迭代的一個例子。

但是,當以第二種(更嚴格)意義使用時,迭代描述了在指令式程式設計語言中使用的程式設計風格。這與遞迴形成對比,遞迴採用更宣告性的方法。

由於 C++ 的性質,在區分單詞的使用時可能會導致更大的問題,因此為了簡化問題,使用“迴圈”來指代本節中描述的簡單遞迴,使用迭代迭代器(執行迭代的“一個”)來指代 STL 中使用的類迭代器(或與物件/類相關)。

無限迴圈

有時程式需要永遠迴圈,或者直到出現錯誤等異常情況。例如,事件驅動的程式可能打算永遠迴圈處理發生的事件,只有在操作員終止程序時才停止。

更常見的是,無限迴圈是由於條件控制迴圈中的程式設計錯誤造成的,在這種情況下,迴圈條件在迴圈內從未改變。

// as we will see, these are infinite loops...
while (1) { }

// or

for (;;) { }


注意
當編譯器最佳化原始碼時,所有在檢測到的無限迴圈(永遠不會執行)之後的語句將被忽略。編譯器通常會對檢測到此類情況發出警告。

條件控制迴圈

大多數程式語言都有用於重複迴圈直到某個條件改變的結構。

條件控制迴圈分為兩類:預條件或入口條件,將測試放在迴圈的開頭;後條件或退出條件迭代,將測試放在迴圈的末尾。在前一種情況下,主體可能會完全跳過,而在後一種情況下,主體始終至少執行一次。

在條件控制迴圈中,breakcontinue 關鍵字變得重要。break 關鍵字導致退出迴圈,繼續執行程式的其餘部分。continue 關鍵字終止迴圈的當前迭代,迴圈繼續進行下一輪迭代。

while (預條件迴圈)

[edit | edit source]

語法

while (''condition'') ''statement''; ''statement2'';

語義 首先,評估條件。

  1. 如果條件為真,則執行語句並再次評估條件
  2. 如果條件為假,則繼續執行語句2

備註語句可以是一個包含多個指令的程式碼塊 { ... }。

'while' 語句與 'if' 語句的不同之處在於,一旦執行了主體(在上面稱為語句),它將返回到 'while' 並再次檢查條件。如果為真,它將再次執行。實際上,它將一直執行,直到表示式為假。

示例 1

#include <iostream>
using namespace std;
 
int main() 
{
  int i=0;
  while (i<10) {
    cout << "The value of i is " << i << endl;
    i++;
  }
  cout << "The final value of i is : " << i << endl;
  return 0;
}

執行

 The value of i is 0
 The value of i is 1
 The value of i is 2
 The value of i is 3
 The value of i is 4
 The value of i is 5
 The value of i is 6
 The value of i is 7
 The value of i is 8
 The value of i is 9
 The final value of i is 10

示例 2

// validation of an input
#include <iostream>
using namespace std;
 
int main() 
{
  int a;
  bool ok=false;
  while (!ok) {
    cout << "Type an integer from 0 to 20 : ";
    cin >> a;
    ok = ((a>=0) && (a<=20));
    if (!ok) cout << "ERROR - ";
  }
  return 0;
}

執行

 Type an integer from 0 to 20 : 30
 ERROR - Type an integer from 0 to 20 : 40
 ERROR - Type an integer from 0 to 20 : -6
 ERROR - Type an integer from 0 to 20 : 14

do-while (後條件迴圈)

[edit | edit source]

語法

do {
  statement(s)
} while (condition);
 
statement2;

語義

  1. 執行語句(s)
  2. 評估條件
  3. 如果條件為真,則轉到 1)。
  4. 如果條件為假,則繼續執行語句2

do - while 迴圈在語法和目的上與 while 迴圈類似。該結構將迴圈繼續條件的測試移動到程式碼塊的末尾,以便在任何評估之前至少執行一次程式碼塊。

示例

#include <iostream>

using namespace std;
 
int main() 
{
  int i=0;

  do {
    cout << "The value of i is " << i << endl;
    i++;
  } while (i<10);

  cout << "The final value of i is : " << i << endl;
  return 0;
}

執行

The value of i is 0
The value of i is 1
The value of i is 2
The value of i is 3
The value of i is 4
The value of i is 5
The value of i is 6
The value of i is 7
The value of i is 8
The value of i is 9
The final value of i is 10

for (預條件和計數控制迴圈)

[edit | edit source]

for 關鍵字用作預條件迴圈的特例,它支援用於以步進表示式的形式僅重複迴圈一定次數的構造,可以測試該表示式並用於透過在每次迴圈中遞增或遞減來設定步進大小(變化率)。

語法
for (initialization ; condition; step-expression)
  statement(s);

for 結構是一個通用的迴圈機制,它包含 4 個部分。

  1. . 初始化,它包含 0 個或多個用逗號分隔的變數初始化語句。
  2. . 測試條件,它被評估以確定是否繼續執行 for 迴圈。
  3. . 遞增,它包含 0 個或多個用逗號分隔的語句,這些語句遞增變數。
  4. . 語句列表,它包含 0 個或多個語句,這些語句將在每次執行迴圈時執行。

注意
在迴圈初始化(或主體)中宣告和初始化的變數僅在迴圈本身的作用域中有效。

for 迴圈等效於下一個 while 迴圈

 initialization
 while( condition )
 {
   statement(s);
   step-expression;
 }


注意

迴圈的每個步驟(初始化、條件和步進表示式)都可以包含多個命令,用,(逗號運算子)分隔。初始化條件步進表示式都是可選引數。在 C++ 中,逗號很少用作運算子。它主要用作分隔符(例如:int x, y; ).

示例 1

// a unbounded loop structure
for (;;)
{
  statement(s);
  if( statement(s) )
    break;
}

示例 2

// calls doSomethingWith() for 0,1,2,..9
for (int i = 0; i != 10; ++i)
{                  
  doSomethingWith(i); 
}

可以改寫為

// calls doSomethingWith() for 0,1,2,..9
int i = 0;
while(i != 10)
{
  doSomethingWith(i);
  ++i;
}

for 迴圈是一個非常通用的結構,它可以執行無界迴圈(示例 1),並且不需要遵循許多更正式語言中類似命名結構強加的嚴格迭代模型。C++(就像現代 C)允許在 for 迴圈的初始化部分宣告變數(示例 2),並且通常被認為是一種良好的形式,只在可以初始化時宣告物件,並在儘可能小的作用域中進行初始化。本質上,for 和 while 迴圈是等效的。大多數 for 語句也可以改寫為 while 語句。

在 C++11 中,添加了 for 迴圈的另一種形式。這種迴圈遍歷範圍(通常是字串或容器)中的每個元素。

語法
for (variable-declaration : range-expression)
  statement(s);

示例 2

std::string s = "Hello, world";
for (char c : s) 
{
  std::cout << c << ' ';
}

將列印

H e l l o ,   w o r l d

.

華夏公益教科書