5 . 1 簡單語句
- C++語言中的大多數語句都以分號結束,一個表達式,比如ival + 5 , 末尾加上分號就變成了表達式語句(expression statement)。表達式語句的作用是執行表達式并丟棄掉
- 求值結果:ival + 5; // 一條沒什么實際用處的表達式語句
- cout << ival; // 一條有用的表達式語句
- 第一條語句沒什么用處,因為雖然執行了加法,但是相加的結果沒被使用。比較普遍的情況是,表達式語句中的表達式在求值時附帶有其他效果,比如給變量賦了新值或者輸出了結果。
空語句
- 最簡單的語句是空語句(nullstatement),空語句中只含有一個單獨的分號:? ? ?;? ?//空語句
- 如果在程序的某個地方,語法上需要一條語句但是邏輯上不需要,此時應該使用空語句。一種常見的情況是,當循環的全部工作在條件部分就可以完成時,我們通常會用到空語句。例如,我們想讀取輸入流的內容直到遇到一個特定的值為止,除此之外什么事情也不做:
- while循環的條件部分首先從標準輸入讀取一個值并且隱式地檢查cin,判斷讀取是否成功。假定讀取成功,條件的后半部分檢查讀進來的值是否等于sought的值。如果發現了想要的值,循環終止;否則,從cin中繼續讀取另一個值,再一次判斷循環的條件。
別漏寫分號,也別多寫分號
- 因為空語句是一條語句,所以可用在任何允許使用語句的地方。由于這個原因,某些看起來非法的分號往往只不過是一條空語句而已,從語法上說得過去。下面的片段包含兩條語句:表達式語句和空語句。
- ival = vl + v2;; / / 正確:第二個分號表示一條多余的空語句
- 多余的空語句一般來說是無害的,但是如果在if或者while的條件后面跟了一個額外的分號就可能完全改變程序員的初衷。例如,下面的代碼將無休止地循環下去:
- 無終止條件 分號使條件改變失效
- 雖然從形式上來看執行遞增運算的語句前面有縮進,但它并不是循環的一部分。循環條件后面跟著的分號構成了一條空語句,它才是真正的循環體。
復合語句(塊)
- 復合語句(compoundstatement)是指用花括號括起來的(司能為空的)語句和聲明的序列,復合語句也被稱作塊(block),一個塊就是一個作用域(參見2.2.4節,第43頁),在塊中引入的名字只能在塊內部以及嵌套在塊中的子塊里訪問。通常,名字在有限的區域內可見,該區域從名字定義處開始,到名字所在的(最內層)塊的結尾為止。
- 如果在程序的某個地方,語法上需要一條語句,但是邏輯上需要多條語句,則應該使用復合語句。例如,while或者for的循環體必須是一條語句,但是我們常常需要在循環體內做很多事情,此時就需要將多條語句用花括號括起來,從而把語句序列轉變成塊。
- 舉個例子,回憶1.4.1節(第10頁)的while循環:
- 程序從邏輯上來說要執行兩條語句,但是while循環只能容納一條。此時,把要執行的語句用花括號括起來,就將其轉換成了一條(復合)語句。
- 塊不可以使用分號進行結尾
5.2語句作用域
- 可以在if、switch、while和for語句的控制結構內定義變量。定義在控制結構當中的變量只在相應語句的內部可見,一旦語句結束,變量也就超出其作用范圍了:
5.3條件語句
- C++語言提供了兩種按條件執行的語句。一種是if語句,它根據條件決定控制流;另外一種是switch語句,它計算一個整型表達式的值,然后根據這個值從幾條執行路徑中選擇一條。
5.3.1 if語句
- if語句(ifstatement)的作用是:判斷一個指定的條件是否為真,根據判斷結果決定是否執行另外一條語句。if語句包括兩種形式:一種含有else分支,另外一種沒有。簡單if語句的語法形式是
- 在這兩個版本的if語句中,condition都必須用圓括號包圍起來。condition可以是一個表達式,也可以是一個初始化了的變量聲明(參見5.2節,第155頁)。不管是表達式還是變量,其類型都必須能轉換成(參見4.11節,第141頁)布爾類型。通常情況下,statement和statement2是塊語句。
- 如果condition為真,執行statemento當statement執行完成后,程序繼續執行if語句后面的其他語句。
- 如果condition為假,跳過statemento對于簡單if語句來說,程序繼續執行if語句后面的其他語句;對于ifelse語句來說,執行statement2
使用 if else語句
- 我們舉個例子來說明if語句的功能,程序的目的是把數字形式表示的成績轉換成字母形式。假設數字成績的范圍是從0到100(包括100在內),其中100分對應的字母形式是"A++”,低于60分的成績對應的字母形式是“F”。其他成績每10個劃分成一組;60到69(包括69在內)對應字母"D”、70到79對應字母"C”,以此類推。使用vector對象存放字母成績所有可能的取值:
- 判斷grade的值是否小于60,根據結果選擇執行if分支還是else分支。在else分支中,由成績計算得到一個下標,具體過程是:首先從grade中減去50,然后執行整數除法(參見4.2節,在125頁),去掉余數后所得的商就是數組scores對應的下標。
懸垂else
- 當一個if語句嵌套在另一個if語句內部時,很可能if分支會多于else分支。事實上,之前那個成績轉換的程序就有4個if分支,而只有2個else分支。這時候問題出現了:我們怎么知道某個給定的else是和哪個if匹配呢?
- 這個問題通常稱作懸垂else(danglingelse),在那些既有if語句又有ifelse語句的編程語言中是個普遍存在的問題。不同語言解決該問題的思路也不同,就C而言,它規定else與離它最近的尚未匹配的if匹配,從而消除了程序的二義性。當代碼中if分支多于else分支時,程序員有時會感覺比較麻煩。舉個例子來說明,對于添加加號減號的那個最內層的ifelse語句,我們用另外一組條件改寫它:
5.3.2switch語句
- switch語句(switchstatement)提供了一條便利的途徑使得我們能夠在若干固定選項中做出選擇。舉個例子,假如我們想統計五個元音字母在文本中出現的次數,程序邏輯應該如下所示:
- 從輸入的內容中讀取所有字符。令每一個字符都與元音字母的集合比較。如果字符與某個元音字母匹配,將該字母的數量加1。顯示結果。
- 要想實現這項功能,直接使用switch語句即可:
- switch語句首先對括號里的表達式求值,該表達式緊跟在關鍵字switch的后面,可以是一個初始化的變量聲明(參見5.2節,第155頁)。表達式的值轉換成整數類型,然后與每個case標簽的值比較。如果表達式和某個case標簽的值匹配成功,程序從該標簽之后的第一條語句開始執行,直到到達了switch的結尾或者是遇到一條break語句為止。我們將在5.5.1節(第170頁)詳細介紹break語句,簡言之,break語句的作用是中斷當前的控制流。此例中,break語句將控制權轉移到switch語句外面。因為switch是while循環體內唯一的語句,所以從switch語句中斷出來以后,程序的控制權將移到while語句的右花括號處。此時while語句內部沒有其他語句要執行,所以while會返回去再一次判斷條件是否滿足。
- 如果switch語句的表達式和所有case都沒有匹配上,將直接跳轉到switch結構之后的第一條語句。剛剛說過,在上面的例子中,退出switch后控制權回到while語句的條件部分。case關鍵字和它對應的值一起被稱為case標簽(caselabel)。case標簽必須是整型常量表達式(參見2.4.4節,第58頁):
switch內部的控制流
- 理解程序在case標簽之間的執行流程非常重要。如果某個case標簽匹配成功,將從該標簽開始往后順序執行所有case分支,除非程序顯式地中斷了這一過程,否則直到switch的結尾處才會停下來。要想避免執行后續case分支的代碼,我們必須顯式地告訴編譯器終止執行過程。大多數情況下,在下一個case標簽之前應該有一條break語句。
- 然而,也有一些時候默認的switch行為才是程序真正需要的。每個case標簽只能對應一個值,但是有時候我們希望兩個或更多個值共享同一組操作。此時,我們就故意省略掉break語句,使得程序能夠連續執行若干個case標簽。例如,也許我們想統計的是所有元音字母出現的總次數:
- 在上面的代碼中,幾個case標簽連寫在一起,中間沒有break語句。因此只要ch是元 音字母,不管到底是五個中的哪一個都執行相同的代碼。
- C++程序的形式比較自由,所以case標簽之后不一定非得換行。把幾個case標簽寫在一行里,強調這些case代表的是某個范圍內的值:
default標簽
- 如果沒有任何一個case標簽能匹配上switch表達式的值,程序將執行緊跟在default標簽(defaultlabel)后面的語句。例如,可以增加一個計數值來統計非元音字母的數量,只要在default分支內不斷遞增名為otherCnt的變量就可以了:
switch內部的變量定義
- 如前所述,switch的執行流程有可能會跨過某些case標簽。如果程序跳轉到了某個特定的case,則switch結構中該case標簽之前的部分會被忽略掉。這種忽略掉一部分代碼的行為引出了一個有趣的問題:如果被略過的代碼中含有變量的定義該怎么辦?
- 答案是:如果在某處一個帶有初值的變量位于作用域之外,在另一處該變量位于作用域之內,則從前一處跳轉到后一處的行為是非法行為。
- 假設上述代碼合法,則一旦控制流直接跳到false分支,也就同時略過了變量filename和ival的初始化過程。此時這兩個變量位于作用域之內,跟在false之后的代碼試圖在尚未初始化的情況下使用它們,這顯然是行不通的。因此C++語言規定,不允許跨過變量的初始化語句直接跳轉到該變量作用域內的另一個位置。
- 如果需要為某個case分支定義并初始化一個變量,我們應該把變量定義在塊內,從而確保后面的所有case標簽都在變量的作用域之外。
?
5 . 4 迭代語句
- 迭代語句通常稱為循環,它重復執行操作直到滿足某個條件才停下來。while和 for 語句在執行循環體之前檢查條件,do while語句先執行循環體,然后再檢查條件。
- 定義在while條件部分或者while循環體內的變量每次迭代都經歷從創建到銷毀的過程。
- while循環結束之后 循環控制變量仍然可以使用
- 第一個循環從標準輸入中讀取數據,我們一開始不清楚循環要執行多少次,當cin讀取到無效數據、遇到其他一些輸入錯誤或是到達文件末尾時循環條件失效。第二個循環重復執行直到遇到一個負值為止,循環終止后,beg或者等于v.end(),或者指向v中一個小于0的元素。可以在while循環外繼續使用beg的狀態以進行其他處理。
傳統for循環的執行流程
- 我們以3.2.3節 (第 85頁 )的 for循環為例:
- 求值的順序如下所示:
- 1循環開始時,首先執行一次init-statement此例中,定義index并初始化為0。
- 2.接下來判斷condition.如果index不等于s.size()而且在s[index]位置的字符不是空白,則執行for循環體的內容。否則,循環終止。如果第一次迭代時條件就為假,for循環體一次也不會執行。
- 3.如果條件為真,執行循環體。此例中,for循環體將s[index]位置的字符改寫成大寫形式。
- 4.最后執行express。此例中,將index的值加1。這4步說明了for循環第一次迭代的過程。其中第1步只在循環開始時執行一次,第2、3、4步重復執行直到條件為假時終止,也就是在s中遇到一個空白字符或者index大于s.size()時終止。
- 牢記for語句頭中定義的對象只在for循環體內可見。因此在上面的例子中,for循環結束后index就不可用了。
省略for語句頭的某些部分
- for語句頭能省略掉init-statement condition和expression中的任何一個(或者全部)。如果無須初始化,則我們可以使用一條空語句作為init-statement.例如,對于在vector對象中尋找第一個負數的程序,完全能用for循環改寫:
- 注意,分號必須保留以表明我們省略掉了init-statementc說得更準確一點,分號表示的是一個空的init-statement。在這個循環中,因為所有要做的工作都在for語句頭的條件和表達式部分完成了,所以for循環體也是空的。其中,條件部分決定何時停止查找,表達式部分遞增迭代器。
- 省略condition的效果等價于在條件部分寫了一個true。因為條件的值永遠是true,所以在循環體內必須有語句負責退出循環,否則循環就會無休止地執行下去:
- 我們也能省略掉for語句頭中的expresssion,但是在這樣的循環中就要求條件部分或者循環體必須改變迭代變量的值。舉個例子,之前有一個將整數讀入vector的while循環,我們使用for語句改寫它:
- 因為條件部分能改變i 的值,所以這個循環無須表達式部分。其中,條件部分不斷檢查輸入流的內容,只要讀取完所有的輸入或者遇到一個輸入錯誤就終止循環。
- 因為對于do-while來說先執行語句或者塊,后判斷條件,所以不允許在條件部分定義變量:
5.5跳轉語句
- 跳轉語句中斷當前的執行過程。C++語言提供了4種跳轉語句:break、continue,goto和returno本章介紹前三種跳轉語句,return語句將在6.3節(第199頁)進行介紹。
- 標記為#1的break語句負責終止連字符case標簽后面的for循環。它不但不會終止switch語句,甚至連當前的case分支也終止不了。接下來,程序繼續執行for循環之后的第一條語句,這條語句可能接著處理連字符的情況,也可能是另一條用于終止當前分支的break語句。標記為#2的break語句負責終止switch語句,但是不能終止while循環。執行完這個break后,程序繼續執行while的條件部分
5.6 try語句塊和異常處理
- 異常是指存在于運行時的反常行為,這些行為超出了函數正常功能的范圍。典型的異常包括失去數據庫連接以及遇到意外輸入等。處理反常行為可能是設計所有系統最難的一部分。
- 當程序的某部分檢測到一個它無法處理的問題時,需要用到異常處理。此時,檢測出問題的部分應該發出某種信號以表明程序遇到了故障,無法繼續下去了,而且信號的發出方無須知道故障將在何處得到解決。一旦發出異常信號,檢測出問題的部分也就完成了任務。
- 如果程序中含有可能引發異常的代碼,那么通常也會有專門的代碼處理問題。例如,如果程序的問題是輸入無效,則異常處理部分可能會要求用戶重新輸入正確的數據;如果丟失了數據庫連接,會發出報警信息。
- 異常處理機制為程序中異常檢測和異常處理這兩部分的協作提供支持。在C++語言中,異常處理包括:
5.6.1throw表達式
- 程序的異常檢測部分使用throw表達式引發一個異常。throw表達式包含關鍵字throw和緊隨其后的一個表達式,其中表達式的類型就是拋出的異常類型。throw表達式后面通常緊跟一個分號,從而構成一條表達式語句。
- 舉個簡單的例子,回憶1.5.2節(第20頁)把兩個Sales_item對象相加的程序。這個程序檢查它讀入的記錄是否是關于同一種書籍的,如果不是,輸出一條信息然后退出。
- try語句塊的一開始是關鍵字try,隨后緊跟著一個塊,這個塊就像大多數時候那樣是花括號括起來的語句序列。
- 跟在try塊之后的是一個或多個catch子句。catch子句包括三部分:關鍵字catch、括號內一個(可能未命名的)對象的聲明(稱作異常聲明,exceptiondeclaration)以及一個塊。當選中了某個catch子句處理異常之后,執行與之對應的塊。catch-旦完成,程序跳轉到try語句塊最后一個catch子句之后的那條語句繼續執行。try語句塊中的program-statements組成程序的正常邏輯,像其他任何塊一樣,program-statements可以有包括聲明在內的任意C++語句。一如往常,try語句塊內聲明的變量在塊外部無法訪問,特別是在catch子句內也無法訪問。
- 程序本來要執行的任務出現在try語句塊中,這是因為這段代碼可能會拋出一個runtime_error類型的異常。try語句塊對應catch子句,該子句負責處理類型為runtime_error的異常。
- 如果try語句塊的代碼拋出了runtime_error異常,接下來執行catch塊內的語句。在我們書寫的catch子句中,輸出一段提示信息要求用戶指定程序是否繼續。如果用戶輸入,n,,執行break語句并退出while循環;否則,直接執行while循環的右側花括號,意味著程序控制權跳回到while條件部分準備下一次迭代。給用戶的提示信息中輸出了err.what()的返回值。我們知道err的類型是runtime_error,因此能推斷what是runtime_error類的一個成員函數(參見1.5.2節,第20頁)。每個標準庫異常類都定義了名為what的成員函數,這些函數沒有參數,返回值是C風格字符串(即const char*),其中,runtime_error的what成員返回的是初始化一個具體對象時所用的string對象的副本。如果上一節編寫的代碼拋出異常,則本節的catch子句輸出
- throw 拋出錯誤信息和錯誤對象綁定;try捕捉錯誤信息;catch處理,輸出之前throw中 錯誤信息和錯誤對象綁定的結果
5.6.3標準異常
- C++標準庫定義了一組類,用于報告標準庫函數遇到的問題。這些異常類也可以在用戶編寫的程序中使用,它們分別定義在4個頭文件中:
- exception頭文件定義了最通用的異常類exception。它只報告異常的發生,不提供任何額外信息。
- stdexcept頭文件定義了幾種常用的異常類,詳細信息在表5.1中列出。
- new頭文件定義了bad_alloc異常類型,這種類型將在12.1.2節(第407頁)詳細介紹。
- type_info頭文件定義了bad_cast異常類型,這種類型將在19.2節(第731頁)詳細介紹
- what函數返回的C風格字符串的內容與異常對象的類型有關。如果異常類型有一個字符串初始值,則what返回該字符串。對于其他無初始值的異常類型來說,what返回的內容由編譯器決定。
小結
- C++語言僅提供了有限的語句類型,它們中的大多數會影響程序的控制流程:
- while、for和dowhile語句,執行迭代操作。
- if和switch語句,提供條件分支結構。
- continue語句,終止循環的當前一次迭代。
- break語句,退出循環或者switch語句。
- goto語句,將控制權轉移到一條帶標簽的語句。
- try和catch,將一段可能拋出異常的語句序列括在花括號里構成try語句塊。catch子句負責處理代碼拋出的異常。
- throw表達式語句,存在于代碼塊中,將控制權轉移到相關的catch子句。
- return語句,終止函數的執行。我們將在第6登介紹return語句。除此之外還有表達式語句和聲明語句。表達式語句用于求解表達式,關于變量的聲明和定義在第2章已經介紹過了。
詞匯
- 復合語句(compound statement)和塊是同義詞。
- 異常處理代碼(exception handler)程序某處引發異常后,用于處理該異常的另一處代碼。和 catch子句是同義詞。
- 異常安全(exception safe) 是一-個術語,表示的含義是當拋出異常后,程序能執行正確的行為。
- 表達式語句(expression statement)即一條表達式后面跟上一個分號,令表達式執行求值過程。
- 控制流(flow of control) 程序的執行路徑?
- 帶標簽語句(labeled statement)前面帶有標簽的語句。所謂標簽是指一個標識符以及緊跟著的一個冒號。對于同一個標識符來說,用作標簽的同時還能用于其他目的,互不干擾。
- 空 語 句 (null statement)只含有一個分號的語句
- 引 發 (raise)含義類似于throw。在 C++語言中既可以說拋出異常,也可以說引發異常。
- 范圍 for 語 句 (range for statement)在一個序列中進行迭代的語句。
- terminate是一個標準庫函數,當異常沒有 被捕捉到時調用。terminate終止當前程序的執行。