第6章 C控制语句:循环
學習筆記——《C Prime Plus》
第6章 C控制語句:循環
- 6.1 再探 while 循環
- 6.1.1 程序注釋
- 6.1.2 C風格讀取循環
- 6.2 while 語句
- 6.2.1 終止 while 循環
- 6.2.2 何時終止循環
- 6.2.3 while:入口循環條件
- 6.2.4 語法要點
- 6.3 _Bool 類型
- 6.4 不確定循環和計數循環
- 6.5 for 循環
- 6.5.1 利用 for 的靈活性
- 6.6 其他運算符:+=、-=、*=、/=、%=
- 6.7 逗號運算符
- 6.8 出口條件循環:do while
- 6.9 如何選擇循環
- 6.10 循環嵌套
- 6.10.1 程序分析
- 6.10.2 循環變式
- 6.11 數組簡介
- 6.11.1 在 for 循環中使用數組
- 6.12 使用函數返回值的循環實例
- 6.12.1 程序分析
對于計算機科學而言,一門語言應該提供以下 3 種形式的程序流:
- 執行語句序列;
- 如果滿足某些條件就重復執行語句序列(循環);
- 通過測試選擇執行哪一個語句序列(分支)。
6.1 再探 while 循環
#include <stdio.h> int main(void) {long num;long sum = 0L; //把 sum 初始化為0,用long類型儲存更大的數int status;printf("Please enter an integer to be summed");printf("(q to quit):");status = scanf("%ld", &num);while(status == 1){sum = sum + num;printf("Please enter next integer (q to quit):");status = scanf("%ld", &num);}printf("Those integers sum to %ld. \n", sum);return 0; }6.1.1 程序注釋
首先:
根據測試條件 status == 1,只要 status 等于 1 ,循環就會重復。每次循環,num 的當前值都被加到 sum 上,這樣 sum 的值始終是當前整數之和。當 status 的值不為 1 時,循環結束。
然后:
要讓程序正常運行,每次循環都要獲取 num 的一個新值,并重置 status。利用 scanf() 的兩個不同的特性來完成。
該程序利用 scanf() 的雙重特性避免了在循環中交互輸入時的這個棘手的問題。。例如,假設 scanf() 沒有返回值,那么每次循環只會改變 num 的值。
該程序的結構:
把 sum 初始化為0 提示用戶輸入數據 讀取用戶輸入的數據當輸入的數據為整數時 {輸入添加給 sum提示用戶進行輸入然后讀取下一個輸入 }輸入完成后,打印 sum 的值順便一提,這叫偽代碼(pseudocode),是一種用簡單的句子表示程序思路的方法,它與計算機語言的形式相對應。
偽代碼有助于設計程序的邏輯。確定程序的邏輯無誤之后,再把偽代碼翻譯成實際的編程代碼。使用偽代碼的好處之一是,可以把注意力集中在程序的組織和邏輯上,不用再設計程序時還要分心如何用編程語言來表達自己的想法。
6.1.2 C風格讀取循環
總之,因為 while 循環是入口條件循環,程序在進入循環體之前必須獲取輸入的數據并檢查 status 的值, 所以在 while 前面要有一個 scanf() 。要讓循環繼續執行,在循環內需要一個讀取數據的語句,這樣程序才能獲取下一個 status 的值,所以在 while 循環末尾還要有一個 scanf() ,它為下一次迭代做好了準備。
while 循環偽代碼標準格式:
根據偽代碼的設計思路,編寫代碼:
status = scanf("%ld", &num); while(status == 1) {//循環行為status = scanf("%ld", &num); //讓循環繼續執行,為下一次迭代 }也可寫為:
while(scanf("%ld", &num) == 1) {//循環行為 }6.2 while 語句
while 循環的通用形式如下:
while( expression ) {statement; }statement 部分可以是以分號結尾的簡單語句,也可以是用花括號括起來的復合語句。
到目前為止,程序示例中的 expression 部分都使用關系表達式。也就是說,expression 是值之間的比較,可以使用任何表達式。如expression 為真(或者更一般地說,非零),執行 statement 部分一次,然后再次判斷 expression。在 expression 為假(0)之前,循環的判斷和執行一直重復進行。每次循環都被稱為一次迭代(iteration)。
6.2.1 終止 while 循環
while 循環有一點非常重要:在構建 while 循環時,必須讓測試表達式的值有變化,表達式最終要為假。否則,循環就不會終止。(可以使用 break 和 if 語句來終止循環)。
index = 1; while (index < 5) {printf("Good morning!\n"); }上面程序段將打印無數次 Good morning!。因為循環中 index 的值一直都是原來的值 1,不曾改變。
6.2.2 何時終止循環
要明確一點:只有在對測試條件求值時,才決定是終止還是繼續循環。
#include <stdio.h> int main(void) {int n = 5;while(n < 7) //第7行{ printf("n = %d\n", n);n++; //第10行 printf("Now n = %d\n", n); //第11行 }printf("The loop has finished.\n"); return 0; }運行結果:
在第 2 次循環時,變量 n 在第 10 行首次獲得值 7 。但是,此時程序并未退出,它結束本次循環(第 11 行),并在對第 7 行的測試條件求值時才退出循環(變量 n 在第 1 次判斷時為 5 ,第 2 次判斷時為 6 )。
6.2.3 while:入口循環條件
while 循環是使用入口條件的有條件循環。所謂“有條件”指的是語句部分的執行取決于測試表達式描述的條件,如(index < 5)。該表達式是一個入口條件(entry condition),因為必須滿足條件才能進入循環體。
6.2.4 語法要點
//糟糕的代碼創建了一個無限循環 #include <stdio.h> int main(void) {int n = 0;while(n<3)printf("n is %d\n", n);n++;printf("That's all this program does\n'");return 0;}運行結果:
屏幕上回一直輸出以上內容,除非強行關閉這個程序。
雖然程序中縮進了 n++;這句話,但是并未把它和上一條語句括在花括號內。因此,只有直接跟在測試條件后面的一條語句是循環的一部分。變量 n 的值不會改變,條件 n < 3 一直為真。該循環體會一直打印 n is 0 ,這是一個無限循環(infinite loop),沒有外部干涉不會退出。
切記:
即使 while 語句本身使用復合語句,在語句構成上,它也是一條單獨的語句。該語句從while 開始執行,到第 1 個分號結束。在使用了復合語句的情況下,到右花括號結束。
6.3 _Bool 類型
在編程中,表示真或假的變量被稱為布爾變量(Boolean variable),所以_Bool 是 C 語言中布爾變量的類型名,_Bool 類型的變量只能儲存 1(真)或 0(假)。如果把其他非零數值賦值非 _Bool 類型的變量,該變量會被設置為 1 。這反映了 C 把所有的非零值都視為真。給布爾變量取一個能表示真或假值的變量名是一種常見的做法。
6.4 不確定循環和計數循環
一些 while 循環是不確定循環(indefinite loop)。所謂不確定循環,指在測試表達式為假之前,預先不知道要執行多少次循環。例如,開頭的程序,通過與用戶交互獲得數據來計算整數之和,我們事先并不知道用戶會輸入什么整數。
另外,還有一類是計數循環(counting loop)。這類循環在執行循環之前就知道要重復執行多少次。
在創建一個重復執行固定次數的循環中涉及了3個行為:
while 循環的測試條件執行比較,遞增運算符執行遞增,遞增發生在循環的末尾,這可以防止不小心漏掉遞增。但是計數器的初始化放在循環外,就有可能忘記初始化。實踐告訴我們可能發生的事情終究會發生,所以,我們可以用另一種控制語句,可以避免這些問題—— for 循環。
6.5 for 循環
for 循環把上述 3 個行為(初始化、測試和更新)組合在一處。
for 語句是一種入口條件循環,即在執行循環之前就決定了是否執行虛幻。因此,for 循環可能一次都不執行。
關鍵字 for 后面的圓括號中有 3 個表達式,分別用兩個分號隔開。
- 第 1 個表達式是初始化,只會在 for 循環開始時執行一次。
- 第 2 個表達式是測試條件,在執行循環之前對表達式求值。如果表達式為假,就結束循環。
- 第 3 個表達式執行更新,在每次循環結束時求值。
完整的 for 循環還包括后面的簡單語句或復合語句。for 圓括號中的表達式也叫做控制表達式。
打印整數 1 ~ 6 及其對應的立方。
運行結果:
for 循環的第 1 行包含了循環所需的所有信息:num 的初始值,num 的種植和每次循環 num 的增量。
注意:num 的終值不是 6 ,而是 7 。雖然最后一次循環打印的 num 的值是6, 但隨后 num++ 使num 的值為 7,然后 num<=6 為假,for 循環結束。
6.5.1 利用 for 的靈活性
for 的靈活性源于如何使用 for 循環中的 3 個表達式。第 1 個表達式給計數器賦初值,第 2 個表達式表示計數器的范圍,第 3 個表達式遞增計數器。
此外,for 循環還有其他 9 種用法。
(1)可以使用遞減運算符來遞減計數器:
#include <stdio.h> int main(void) {int secs;for(secs = 5; secs > 0; secs--){printf("%d seconds!\n", secs);}printf("We have ignition!\n");return 0; }(2)可以讓計數器遞增 2、10 等:
#include <stdio.h> int main(void) {int n; for(n=2; n < 60; n = n + 13) //從2開始,每次遞增13,在60內 printf("%d \n",n);return 0; }(3)可以用字符代替數字計數:
#include <stdio.h> int main(void) {char ch;for(ch = 'a'; ch <= 'z'; ch++)printf("The ASCII value for %c is %d.\n", ch, ch);return 0; }(4)處理測試迭代次數外,還可以測試其他條件:
for (num = 1; num <= 6; num++)替換成:
for (num = 1; num*num*num <= 216; num++)如果與控制次數相比,你更關心限制立方的大小,就可以使用這樣的測試條件。
(5)可以讓遞增的量幾何增長,而不是算術增長。也就是說,每次都乘上一個固定量:
#include <stdio.h> int main(void) {double debt;for (debt = 100.0; debt < 150; debt = debt * 1.1)printf("Your debt si now %.2lf.\n", debt);return 0; }(6)第 3 個表達式可以使用任何合法的表達式。無論是什么表達式,每次迭代都會更新該表達式的值:
#include <stdio.h> int main(void) {int x;int y = 55;for (x = 1; y <= 75; y= (++x * 5) + 50)printf("%10d %10d\n", x, y);return 0; }運行結果:
該循環打印 x 的值和表達式 ++x * 5 + 50 的值。
注意:測試涉及 y,而不是 x。for 循環中的 3 個表達式可以是不同的變量;雖然該例可以正常運行,但是編程風格不太好。
(7)可以省略一個或多個表達式(但是不能省略分號),只要在循環中包含能結束循環的語句即可:
#include <stdio.h> int main(void) {int ans, n;ans = 2;for (n = 3; ans <= 25;)ans = ans * n;printf("n = %d; ans = %d.\n", n, ans);return 0; }該循環保持 n 的值為 3 。變量 anx 開始的值為 2 ,然后遞增到 6 和 18,最終是 54。
注意:省略第 2 個表達式會被視為真,程序會一直運行。
(8)第 1 個表達式不一定是給變量賦初值,也可以使用 printf() 。記住,在執行循環的其他部分之前,只對第 1 個表達式求值一次或執行一次。
#include <stdio.h> int main(void) {int num = 0;for (printf("Keep entering numbers!\n"); num != 6; )scanf("%d", &num);printf("That's the one I want!\n'");return 0; }程序打印第 1 行的句子一次,在用戶輸入 6 之前不斷接受數字。
(9)循環體的行為可以改變循環頭中的表達式:
for (n = 1; n < 10000; n = n + data)如果程序經過幾次迭代后發現 delta 太大或太小,循環中的 if 語句可以改變 delta 的大小。
6.6 其他運算符:+=、-=、*=、/=、%=
//一下兩兩一組寫法相互等價: socore += 20; socore = socore + 20;dimes -= 2; dimes = dimes - 2;bunnies *= 2; bunnies = bunnies * 2;time /= 2.73; time = time / 2.73;reduce %= 3; reduce = reduce % 3;6.7 逗號運算符
逗號運算符擴展了 for 循環的靈活性,以便在循環頭中包含更多的表達式。
例如,打印一類郵件資費,郵資為首重 40 元/千克,續重20 元/千克:
該程序在初始化表達式和更新表達式中使用了逗號運算符。
初始化表達式中的逗號使 ounces 和 cost 都進行了初始化,更新表達式中的逗號每次都迭代 ounces 遞增1、cost 遞增 20(NEXT_Z 的值是20)。
逗號運算符并不局限于 for 循環中使用,但這是它最常使用的地方。
逗號運算符有兩個其他性質:
x = ( y = 3, ( z = ++y + 2 ) + 5 );
先把 3 賦給 y,遞增 y 為 4,然后把 4 加 2 之和 6 賦給 z,接著加上 5,最后把結果 11 賦給 x。
6.8 出口條件循環:do while
while 循環和 for 循環都是入口條件循環,即在循環的每次迭代之前檢查測試條件,所以有可能根本不執行循環體中的內容。
C 語言還有出口條件循環(exit-condition loop),即在循環的每次迭代之后檢查測試條件,這保證了至少執行循環體中的內容一次。
在用戶輸入 13 之前不斷提示用戶輸入數字。
也可使用 while 循環語句(入口條件):
#include <stdio.h> int main(void) {const int secret_code = 13;int code_entered;printf("To enter the triskaidekaphobia therapy club,\n");printf("please enter the secret code number:");scanf("%d", &code_entered);while (code_entered != secret_code){printf("To enter the triskaidekaphobia therapy club,\n");printf("please enter the secret code number:");scanf("%d", &code_entered);}printf("Congratulations! You are cured!\n");return 0; }需滿足循環條件,才會進入循環;所有在 while 循環前面先實現一遍循環體的內容。
do while 循環的通用形式:
do {statement; }while( expression );statement 可以是一條簡單語句或復合語句。do while 循環以分號結尾。
do while 循環在執行完循環體后才執行測試條件,所以至少執行循環體一次;而 for 循環或 while 循環都是在執行循環體之前先執行測試條件。
do while 循環使用于那些至少迭代一次的循環。例如,下面是一個包含 do while 循環的密碼程序偽代碼:
6.9 如何選擇循環
首先,確定是入口條件循環還是出口條件循環。
通常,入口條件循環用得比較多,原因是:
一般而言,當循環涉及初始化和更新變量時,用 for 循環比較合適,而在其他情況下用 while 循環更好。
對于下面這種條件,用 while 循環就很好:
while (scanf("%ld", &num) == 1)對于涉及索引計數的循環,用 for 循環更合適:
for (count = 1; count <= 100; count++)6.10 循環嵌套
循環嵌套(nested loop)指在一個循環內包含另一個循環。嵌套循環常用于按行和列顯示數據,也就是說,一個循環處理一行中的所有列,另一個循環處理所有的行。
#include <stdio.h> #define ROWS 6 #define CHARS 10int main(void) {int row;char ch;//內層循環一行打印 10 個字符,外層循環創建 6 行for(row = 0; row < ROWS; row++) //第10行 { for(ch = 'A'; ch < ('A'+ CHARS); ch++) //第12行{printf("%c", ch);}printf("\n");}return 0; }運行結果:
6.10.1 程序分析
(1)代碼第 10 行開始的 for 循環被稱為外層循環(outer loop),第 12 行開始的 for 循環被稱為內層循環(inner loop)。
(2)外層循環從 row 為 0 開始循環, 到 row 為 6 時結束;因此,外層循環要執行 6 次,row 的值從 0 變為 5。
(3)每次迭代要執行的第 1 條語句是內層的 for 循環,該循環要執行 10 次,在同一行打印字符 A~J;第二句是外層循環的 printf("\n");
(4)嵌套循環中的內層循環在每次外層循環迭代是都要執行完所有的循環;比如:此代碼中的內層循環每次都要執行 10 次才結束,然后執行 printf("\n"); 然后再進行外層循環的下一次循環。
6.10.2 循環變式
可以通過外層循環控制內層循環,在每次外層循環迭代時內層循環完成不同的任務。比如:內層循環開始打印的字符取決于外層循環的迭代次數。
#include <stdio.h> int main(void) {const int ROWS = 6;const int CHARS = 6; //用const關鍵字代替#define int row;char ch;//依賴外部循環的嵌套循環 for(row = 0; row < ROWS; row++){for(ch = ('A' + row); ch <('A'+ CHARS); ch++){printf("%c", ch);}printf("\n");}return 0; }運行結果:
6.11 數組簡介
數組(array)是按順序儲存的一系列類型相同的值,如 10 個 char 類型的字符或 15 個 int 類型的值,整個數組有一個數組名,通過整數下標訪問數組中單獨的項或元素(element)。
float debts[20]; debts[5] = 32.54; debts[6] = 1.2e+21;聲明 debts 是一個內含 20 個元素的數組,每個元素都可以儲存 float 類型的值。數組的第 1 個元素是 debts[0],第 2 個元素是 debts[1],以此類推,直到 debts[19]。
注意,數組元素的編號從 0 開始,而不是從 1 開始。
把值讀入指定的元素中:
scanf("%f", &debts[4]); //把一個值讀入數組的第5個元素用于識別數組元素的數字被稱為下標(subscript)、索引(indice)、或偏移量(offset)。下標必須是整數,而且要從 0 開始計數。數組的元素被依次儲存在內存中相鄰的位置。
6.11.1 在 for 循環中使用數組
該程序讀取 10 個高爾夫分數,用 for 循環來讀取數據。
程序打印總分、平均分、差點(handicap,是平均分與標準分的差值)
運行結果:
(1)首先,注意程序實例雖然打印了 11 個數字,但是只讀入了 10 個數字,因為循環只讀了 10 個值。
(2)由于scanf() 會跳過空白字符,所以可以在一行輸入 10 個數字,也可以每行只輸入一個數字,或者像本例這樣混合使用空格和換行符隔開每個數字(因為輸入時緩沖的,只有當用戶鍵入 Enter 鍵后數字才會被發送給程序)。
要讀取 int 類型變量 fue,應該寫成:
scanf("%d", &fue);要讀取 int 類型的元素 score[index],應該寫成:
scanf("%d",&score[index]);較好的編程風格:
(1)使用 #define 指令創建的明示常量(SIZE)來指定數組的大小。如果以后要擴展程序處理 20 個分數,只需簡單地把 SIZE 重新定義為 20 即可,不用逐一修改程序中使用了數組大小的每一處。
(2)使用 3 個獨立的 for 循環,遵循了模塊化(modularity)的原則。模塊化的思想是:應該把程序劃分為一些獨立的單元,每個單元執行一個任務。這樣做提高了程序的可讀性,也方便后續更新或修改程序。
6.12 使用函數返回值的循環實例
編寫一個有返回值的函數,要完成以下內容:
例如:
double power(double n, int p) //返回一個 double 類型的值 {double pow = 1;int i;for (i = 1; i<=p; i++)pow *= n;return pow; //返回 pow 的值 }要聲明函數的返回類型,在函數名前寫出類型即可,就像聲明一個變量那樣。
關鍵字 return 表明該函數將把它后面的值返回給主調函數。
返回值也可以是表達式的值:
運行結果
6.12.1 程序分析
main()
實例中的 main() 是一個驅動程序(driver),即被設計用來測試函數的小程序。
while 循環
(1)輸入1.2 12,scanf() 成功讀取兩值,并返回2,循環繼續。因為 scanf() 跳過空白,多頁可以多行輸入。
(2)輸入 q 會使 scanf() 的返回值為 0,因為 q 與 scanf() 中的轉換說明 %lf 不匹配。scanf() 將返回 0,循環結束。類似地,輸入 2.8 q 會使 scanf() 的返回值為 1,循環也會結束。
power() 函數
(1)第一次出現:double power(double n, int p); //ANSI函數原型
這是 power() 函數的原型,它聲明程序將使用一個名為 power() 的函數。開頭的關鍵字 double 表明 power() 函數返回一個 double 類型的值。編譯器要知道 power() 函數返回值的類型,才能知道有多少字節的數據,以及如何解釋它們,這就是為什么必須聲明函數的原因。
圓括號中的 double n, int p 表示power() 函數的兩個參數。第 1 個參數應該是 double 類型的值,第 2 個參數應該是 int 類型的值。
----------------------------------------------------------------------------------------------------------------------------
(2)第二次出現:xpow = power(x, exp); //函數調用
程序調用 power() 函數,把兩個值傳遞給它。該函數計算 x 的 exp 次冪,并把計算結果返回給主調函數。在主調函數中,返回值將被賦給變量 xpow。
----------------------------------------------------------------------------------------------------------------------------
(3)第三次出現:double power(double n, int p) //函數定義
power() 函數有兩個形參,一個是 double 類型,一個是 int 類型,分別由變量 n 和變量 p 表示。
注意:函數定義的末尾沒有分號,而函數原型的末尾有分號。
power() 函數用 for 循環計算 n 的 p 次冪,并把計算結果賦給 pow,然后返回 pow 的值,return pow;
總結
以上是生活随笔為你收集整理的第6章 C控制语句:循环的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第4章 字符串和格式化输入/输出
- 下一篇: 第7章 C控制语句:分支和跳转