第5章 語句
C++提供了條件執行語句、循環語句和用于中斷目前控制流的跳轉語句,本章将具體介紹這些語句。
5.1 簡單語句
空語句
最簡單的語句是空語句,隻有一個單獨的分号。如在程式的某個地方文法上需要一條語句但是邏輯上不需要,此時應該使用空語句。
// 讀取輸入流的内容直到遇到一個特定值
while (cin >> s && s != sought)
; // 空語句,循環的全部工作在條件部分就可以完成
複合語句
用花括号括起來的語句和聲明的序列,也稱作塊,一個塊就是一個作用域,在塊中引入的名字隻能在塊内部以及嵌套在塊中的子塊裡通路。
5.2 語句作用域
可在if、switch、while和for語句的控制結構内定義變量,定義在控制結構中的變量隻在語句相應的塊内部可見,一旦語句結束,變量就超出其作用範圍了。
for (int i = 0; i <= n; ++i) // 在for語句内部定義i,隻能在for對應的塊中使用
cout << i << endl;
i = 0; // 錯誤:在循環外部不能使用
5.3 條件語句
5.3.1 if 語句
if (condition1) // condition1類型必須能轉換成布爾類型
statement1;
// condition1為真:執行statement1,執行完後執行後面的其他語句,跳過else if和else語句
else if (condition2)
statement2;
// condition1為假,condition2為真:跳過statement1,執行statement2,執行完後跳過else語句
else
statement3;
// condition1和condition2為假:跳過statement1和statement2,執行statement3
當statement中有多條語句想要一起執行時,要使用花括号{}将其包覆。
當一個if語句嵌套在另一個if語句内部時,很可能if分支會多于else分支,C++規定else與離它最近的尚未比對的if比對。
5.3.2 switch 語句
switch (expression) { // 對表達式求值,然後與每個case标簽比較
case val1: // 比對成功則執行statement1,不成功則比對val2
statement1;
break; // 遇到break後跳出switch語句
case val2:
statement2;
break;
case val3:
statement3;
break;
default: // 該語句可省略
statement4;
break;
// 若所有case标簽都未比對成功則執行緊跟在default标簽後的語句
}
如果某個case标簽比對成功,将從該标簽開始往後順序執行所有case、default分支(不管後面的case标簽有沒有比對上),除非中斷這一過程(如break)。
switch的執行過程可能會跨過某些case标簽,如果程式跳轉到了某個特定的case,則switch結構中該case标簽之前的部分會被忽略掉。如果在某處一個帶有初值的變量位于作用域之外,在另一處該變量位于作用域之内,則從前一處跳轉到後一處是非法行為。
5.4 疊代語句
5.4.1 while 語句
當不确定到底要疊代多少次,或者在循環結束後想通路循環控制變量的可以用while循環。
while (condition)
statement
while語句的執行過程是交替地檢測condition和執行statement,直至condition為假為止。condition可以是一個表達式或者一個帶初始化的變量聲明。應該由條件本身或者是循環體設法改變condition的值,否則循環可能無法終止。
定義在while條件部分或者while循環體内的變量每次疊代都經曆從建立到銷毀的過程。
5.4.2 傳統的 for 語句
for (init-statement; condition; expression)
statement
for循環的執行過程如下:
init-statement必須是以下三種形式中的一種:聲明語句、表達式語句或者空語句,可以定義多個對象,但是隻能有一條聲明語句,是以所有變量的基礎類型必須相同。
for 語句頭能省略掉init-statement、condition和expression中的任何一個(或者全部)
5.4.3 範圍 for 語句
for (declaration: expression)
statement
expression表示的必須是一個序列,如用花括号括起來的初始值清單、數組或者vector或string等類型的對象,這些類型的共同特點是擁有能傳回疊代器的begin和end成員。
declaration定義一個變量,序列中每個元素都能轉換成該變量的類型。為確定類型相容,可使用auto類型說明符。如果需要對序列中的元素執行寫操作,循環變量必須聲明成引用類型。
每次疊代都會重新定義循環控制變量,并将其初始化成序列中的下一個值,之後才會執行statement。
vector<int> v = {0, 1, 2, 3, 4, 5};
// 範圍變量必須是引用類型,這樣才能執行寫操作
for (auto &r : v)
r *= 2;
以上範圍for語句等價于:
for (auto beg = v. begin(), end = v.end(); beg != end; ++beg) {
auto &r = *beg;
r *= 2;
}
5.4.4 do while 語句
do
statement
while (condition);
do while語句與while語句的差別是:do while語句先執行循環體statement,再檢查條件condition,如果condition為假,循環終止,否則,重複循環過程。
condition使用的變量必須定義在循環體之外。
5.5 跳轉語句
5.5.1 break 語句
break語句負責終止離它最近的while、do while、for或switch語句,并從這些語句之後的第一條語句開始繼續執行。break語句隻能出現在疊代語句或者switch語句内部(包括嵌套在此類循環裡的語句或塊的内部)。
5.5.2 continue 語句
continue語句負責終止離它最近的while、do while、for語句的目前疊代,并立即開始下一次疊代。continue語句隻能出現在疊代語句while、do while、for内部(包括嵌套在此類循環裡的語句或塊的内部)。
對while或do while語句:繼續判斷條件的值;對于傳統for循環:繼續執行for語句頭的expression;對于範圍for語句:用序列中的下一個元素初始化循環控制變量。
5.5.3 goto 語句
goto 語句的作用是從goto語句無條件跳轉到同一函數内的另一條語句。其形式為:
用辨別符+冒号的形式寫帶标簽的語句:
goto語句和控制權轉向的那條帶标簽的語句必須位于同一個函數内,goto語句不能将程式的控制權從變量的作用域之外轉移到作用域之内。
不要使用goto語句,因為它令程式很難了解且難修改。
5.6 try 語句塊和異常處理
5.6.1 throw 表達式
int x, y;
cin >> x >> y;
if (y != 0) {
cout << x / y << endl;
return 0;
}
else {
cerr << "y must be not equal to zero" << endl;
return -1;
}
上述程式計算兩個整數的除法運算,但在真實程式中,應把對象相除的代碼和使用者互動的代碼分離開來。是以改寫程式使得檢查完成後抛出一個異常:
if (y == 0)
throw runtime_error("y must be not equal to zero")
cout << x / y << endl;
如果y等于0,就抛出一個異常,該異常是類型runtime_error的對象。抛出異常将終止目前的函數,并把控制權轉移給能處理該異常的代碼。
5.6.2 try 語句塊
try {
program-statement
// 包括聲明在内的任意語句,在語句塊内部聲明的變量無法在外部通路
}
catch (expression-declaration) {
handler-statements
}
catch (expression-declaration) {
handler-statements
} // ...
5.6.1中的代碼用try語句塊編寫:
while (cin >> x >> y) {
try { // try語句塊是程式本來要執行的任務
if (y == 0)
throw runtime_error("y must be not equal to zero")
cout << x / y << endl;
}
catch (runtime_error err) { // catch子句負責處理類型為runtime_error的異常
cout << err.what() // what是runtime_error類的一個成員函數
<< "\n Try Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
break;
}
}
在複雜系統中,程式遇到抛出異常代碼前,其執行路徑已經經過了多個try語句塊,如目前try語句塊被另一個try語句塊調用,而當異常被抛出時,首先搜尋抛出該異常的函數,若沒找到比對的catch子句,終止該函數,并在調用該函數的函數中繼續尋找,以此類推。如一段程式沒有try語句塊且發生了異常,系統會調用terminate函數并終止目前程式的執行。
5.6.3 标準異常
C++标準庫定義了一組類(分别在4個頭檔案中)用于報告标準庫函數遇到的問題:
- exception:最通用的異常類exception,隻報告異常的發生,不提供任何額外的資訊。
- stdexcept:幾種常用的異常類。包括之前提到的runtime_error等。
- new:bad_alloc異常類型。
- type_info:bad_cast異常類型。
标準庫異常類隻定義了幾種運算,包括建立或拷貝異常類型的對象,以及為異常類型的對象指派。隻能以預設初始化的方式初始化exception、bad_alloc和bad_cast對象,不允許為這些對象提供初始值。相反:應當使用string對象或者C風格字元串初始化這些類型的對象,但不允許使用預設初始化的方式。
異常類型隻定義了一個名為what的成員函數,該函數沒有任何參數,傳回值是一個指向C風格字元串的const char*,該字元串的目的是提供關于異常的一些文本資訊。