這是一個C語言系列文章,如果是初學者的話,建議先行閱讀之前的文章。筆者也會按照章節順序發佈。
本篇詳細講解循環結構與控制結構。對於每一種編程語言來說,這都是必不可少的結構。
循環結構
循環結構是為了簡化重複工作而給出一種語句。
前面 一文中曾提及,函數是對一種功能的封裝,以便日後直接使用這種功能。看似都是簡化重複工作,但它們之間並非替代關係。
例如有一個需求:向文件中寫入一千萬行相同數據。
未學習循環時,只有將寫數據函數在代碼文件中寫一千萬行來完成。
這樣的代碼...
編輯代碼極慢,且生成的二進制文件相較於同功能使用循環的代碼來說要大很多(一千萬個call指令)。
然而利用循環,上述的需求三行代碼即可搞定。
來簡單看看循環結構的執行流程:
通常,程序執行到循環結構時會先判斷循環條件是否滿足,滿足循環條件,則進入循環內部執行相應語句(上例的調用寫文件函數),然後繼續判斷循環條件(是否不足一千萬次)是否滿足,如此往復,直至循環條件不滿足時,跳出循環。
在C語言中有三種循環結構:
- while
- do-while
- for
while
while語句的一般形式如下:
<code>while (循環條件) { ...//循環內的語句}//如果循環內只有一條語句,也可省略大括號,其實{}及其內部語句這個整體也可以看作為一個語句,叫塊語句while (循環條件) ...;//某一條語句/<code>
關於大括號{}形成的塊語句,我將在後續作用域 相關的文章中提及。
例如:
<code>int i = 0;while (i < 10) { ++i;}//也可寫成while (i < 10) ++i;/<code>
do-while
我們先看下do-while的一般形式:
<code>do { ...//循環內語句} while (循環條件);/<code>
do-while與while的差別在於,循環條件的檢查是前置還是後置。
while的執行流程與上面的流程圖一致,即先驗證循環條件是否滿足,滿足則進入循環。
而do-while則是先執行循環內語句,然後檢查循環條件,滿足循環條件則繼續執行循環內語句,如此往復,直至循環條件不滿足時退出循環。
for
先看一下for循環結構的一般形式:
<code>for (表達式1;表達式2;表達式3) { ...//循環內語句}//如果循環內只有一條語句,可省略大括號for (表達式1;表達式2;表達式3) ...;//某一條語句/<code>
for循環中三個表達式均可以根據需求給出或者省寫。三個表達式的含義如下:
- 表達式1——循環的前置處理,即在檢查循環條件前被執行,一般都是用來初始化循環條件相關的變量
- 表達式2——循環條件,每一輪執行循環內語句前都要驗證表達式2的值是否為真,為真則執行循環內語句
- 表達式3——循環後置處理,每一輪循環內語句執行後,在檢測循環條件(表達式2)前被執行,一般用於修改循環條件相關的變量值
看個例子:
<code>int i;for (i = 0; i < 10; ++i) { printf("%d\\n", i);}/<code>
這個例子中,利用for循環讓程序執行10次printf函數的調用,而printf將會在終端打印每一次循環時i的值。再來看幾個例子:
<code>int i = 0;for (; i < 10; ) { printf("%d\\n", i); ++i;}int i;for (i = 0; i < 10; ++i) printf("%d\\n", i);/<code>
這兩個例子的功能與上一個代碼區的代碼功能完全一樣,只是for循環結構寫法略有不同而已。
控制結構
控制結構是用來對程序執行流程進行控制的,例如滿足什麼條件執行哪些語句。
看一下控制結構的一般流程:
程序進入控制結構後一般先進行條件判斷,如果滿足條件則執行一段語句,如果不滿足條件則不執行語句或執行另一段語句。這類流程一般稱為
分支結構。除卻分支結構外,流程控制還包含一些其他功能。在C語言中,流程控制包含如下內容:
- if-else
- switch
- break
- continue
- goto
if-else
這是最典型的分支結構,其形式非常直觀。
假設我們定義瞭如下兩個變量:
<code>int a = 90, b = 80;/<code>
我希望a>b時向終端輸出a的值,那麼代碼可以寫成:
<code>if (a > b) { printf("%d\\n", a);}/<code>
如果同時我希望a<=b時,輸出b的值呢?
<code>if (a > b) { printf("%d\\n", a);} else { printf("%d\\n", b);}/<code>
if-else語句的一般形式:
<code>if (判斷條件) { ...//條件成立時的一段語句} else { ...//條件不成立時的一段語句}/<code>
其中,如果{}中只有一條語句,那麼大括號可以省寫。
if-else也可以嵌套,看下面這個例子:
<code>if (a > b) { if (a-b > 10) { //潛入if判斷,a-b的值大於10則調用printf printf("%d\\n", a-b);//打印a-b的值 }} else { if (a%b == 1) { //此處大括號不可省寫,因為內部包含多於1條語句 a = 100; printf("%d\\n", a%b); } else //此處大括號可以省寫,因為只有一條函數調用語句 printf("%d\\n", b);}/<code>
如果我對a - b的結果有多種處理時,除了上述的嵌套,還可以怎麼寫呢?
<code>if (a-b == 10) { printf("%d\\n", a);} else if (a-b == 9) { printf("%d\\n", b);} else if (a-b == 8) { printf("%d\\n", a);} else { printf("%d\\n", b);}/<code>
這裡其實也是嵌套,因為else後跟的是一條if語句,因此大括號省略。將if直接寫在else後,代碼讀起來更容易理解。
switch
如果上例中的分支條件有很多的話,則會寫出一長串if-else,這樣的代碼會很蠢笨,有沒有更優雅的寫法呢?來一起看看switch吧,重寫上面a-b的例子:
<code>switch (a-b) { case 10: printf("%d\\n", a); break; case 9: printf("%d\\n", b); break; case 8: printf("%d\\n", a); break; default: printf("%d\\n", b); break;}/<code>
這段代碼的含義與上面的if-else版本的完全一樣,但這樣看著是不是更簡潔一些?
來看下switch的一般形式:
<code>switch (表達式) { case 數值1: ...//一些語句 case 數值2: { ...//一些語句 } ... default: ...//一些語句}/<code>
表達式的值的數據類型必須為 ,且不能為指針類型。上例中,表達式a-b的值為整型。
switch中對每個分支做的都是等值判斷。
case關鍵字後跟的數值,這些數值的數據類型都必須是 ,且不能為指針類型。
上面的例子中用到了break,我們馬上就會說到。break本是用來跳出循環的,但也可用於switch結構中。如果a-b的例子(a=90, b=80)中不加入break,那麼終端輸出結果就會是:
<code>90809080/<code>
即,會從第一個滿足等值匹配的case處執行其中語句,並在下一個case處<strong>不進行等值判斷,直接執行其中語句,如此直至switch中的後續分支都被執行完。
此外,case後可以寫{}也可不寫,但這兩者的差別並不是在於case中語句數量,而是是否可以定義新的變量。
<code>switch (a - b) { case 10: int c = a - b; break; default: break;}/<code>
這樣的寫法是<strong>不符合語法的,case內如果不加{},則不允許定義變量。如果一定要定義,可以如下寫:
<code>switch (a - b) { case 10: { int c = a - b; break; } default: break;}/<code>
break
在switch中提到過,break是用來跳出循環的,我們舉個例子:
<code>int i;for (i = 0; i < 10; ++i) { if (i % 10 == 3) break;}/<code>
這段代碼利用for循環結構做10次循環,但是我希望在第4次循環(i=3)時退出循環。我們可以利用if語句做判斷,然後利用break關鍵字配以分號所組成的語句跳出循環。
continue
除了跳出循環,有時我們發現循環中有一些變量的內容未達到預期時,希望暫時不執行循環內的一些語句處理。例如:
<code>int i = 0, j = 0;for (i = 0; i < 10; ++i, ++j) {//for中的是表達式,逗號表達式也可以被應用在此 if (j < 5) continue; j *= 10;}/<code>
這個例子中,我期望j在小於5時不要乘10,此時,我可以用if語句來判斷j的內容,如果小於5,則用continue關鍵詞配以分號所組成的語句,讓循環中的執行流程不繼續往下執行,而是直接走到for的第三個表達式處理,然後流程再進入for的第二個表達式判斷,滿足第二個表達式條件後繼續進入for中從頭執行語句,如此往復,直到j滿足條件後,才每輪循環都執行j *= 10;這條語句。
goto
有時,我們不在循環中時,也會有跳轉的需求,儘管這類需求極少。在日常工程中,也不推薦使用goto關鍵詞,因為濫用goto會讓代碼維護難度增加。
我們來看一個goto的例子:
<code>int main(void){ int a = 10;again: ++a; if (a < 12) goto again; return 0;}/<code>
這裡,again是一個標號(label),其命名規則與變量命名規則一致,其後必須跟隨冒號。標號在其所在作用域(即其所在函數)內唯一。
goto的必須配合label一同使用,因為編譯器需要知道流程跳轉到什麼位置。
上面的這段代碼的含義就是,定義了整型變量a,然後a自加,判斷a的值是否小於12,小於的話,跳轉到again的位置繼續執行其後的語句(也就是從++a;開始的部分)。如果a >= 12了,那麼正常返回。
一個綜合使用的例子
<code>#include <stdio.h>int main(void){ int i; char s[] = "Hello World"; for (i = 0; i < sizeof(s); ++i) { if (s[i] == '\\0') break; switch (s[i]) { case 'H': case 'W': printf("up-case\\n"); break; case ' ': printf("blank\\n"); break; default: printf("low-case\\n"); break; } } return 0;}/<stdio.h>/<code>
其中,case部分如果不寫任何語句,那麼流程在匹配後會繼續向下一個case前進,但不會對下一個case的值進行等值驗證,直接進入其語句部分執行。
今天所提及的這些循環結構與控制結構都是可組合使用的。學習語言要將知識點融會貫通,這需要一個過程,需要多進行嘗試,嘗試出錯不要怕,想清原因並嘗試如何修正將會大大提升編程水平與信心。
喜歡的小夥伴可以關注碼哥,也可以給碼哥留言評論,如有建議或者意見也歡迎私信碼哥,我會第一時間回覆。
閱讀更多 碼哥比特 的文章