通過上次課題的講述,相信大家一定對什么是數據及數據的定義和使用方法有一定的了解了,在看本次課題之前希望大家能趁熱打鐵,再到網上下本C語言的教程,最好是買本數來鞏固下變量相關的知識。
大家明白了怎么定義變量,怎么給變量賦值了,自然的就應該進一步了解一下一些詳細的代碼怎么編寫了。
今天我們的任務比較簡單,就講一下如何編寫代碼及實現代碼的流程控制。應該說這個是非常簡單的東西了,本來我沒打算講它,可是本著一步一個腳印的原則,我還是簡要的說一下。在本次課題之后,我會緊跟著出一個函數的專題,來作為代碼篇的完善,至此大家應該能獨立的寫自己的程序了。
本次課題知識點不是很多(也不少,但是都很簡單。),主要是在于多寫,多練,知道自己能把一些現實的問題轉換成代碼來解決問題。
不多廢話,先說下本次課題要寫的主要內容:
- 程序的運算和邏輯判斷
- 三種程序流程控制結構詳解。
- 養成良好的代碼編寫風格。
- 結束語
下面開始進入正題。
一、 程序的運算和邏輯判斷。
說計算是電腦最基本的功能,相必沒有人會反對的。做程序,自然最基礎的就是這些運算了。
我知道,看這個破爛文章的朋友對數據應該都是不感冒的。不過大家放心,這里牽扯的運算都很簡單,就是小學的加、減、成、除、取余數,是、非、與或和位移,沒別的了。先讓我們來了解下算術運算。
1. 算術運算。
加減乘除相比大家都應該很了解的。這里我不想費太多的篇幅講述這些大家都明白的知識,我把要說的知識列出來,大家自己了解就好。
用于算術運算的運算符有:

我想不用我說,加、減、成、除這些運算符大家都接觸過的,關于“%”運算符,其實就是取余數;用這些運算符可以將一些數字,變量等連接起來,進行運算,這樣的式子叫做表達式。例如:
int i =5;
i +7; // 這里就是一個表達式。
“=”這個運算符其實不屬于算術運算,它也并非是我們數學課上講的“等于”,而是一個“賦值”運算符,它用來將一個常量(數字、字母等)賦值給一個變量的賦值運算符,由于我們進行算術運算以后,通常都會將運算結果保存到一個變量中,所以,我把這個賦值運算符歸類到這里。而我們日常的“等于”運算符是:“= =”,它屬于邏輯運算符,我們將在下一節中講到它。
關于“++ 、 — —”這兩個運算符,上圖中已經說明了,它是對變量的對自己的自加或者自減運算,等同于:變量 = 變量+1; 或者 變量 = 變量 — 1;
現在讓我們來舉幾個例子,來說明下這些算術運算符的用法。
void main()
{
int x = 1;
int y = 2;
int z = 20;
x ++; // 等同于x = x + 1;
y --; // 等同于y = y - 1;
z /= x; // 等同于z = z / 1;
printf("%d, %d, %d\n", x, y, z); // 打印 x,y,z 的結果。
z %= x; // 計算得出 z / x 以后的余數
x = x+y;
z -= x;
printf("%d, %d, %d\n", x, y, z); // 打印 x,y,z 的結果。
}
請先不要看下面的截圖,先看下這個程序,分析一下,這兩次的輸出結果會是多少,然后對比下圖:
看下就算的是不是正確,如果正確了,那恭喜你,基礎的算術運算就算通過了,接下來,我們需要調試這段代碼,來熟悉一些相關的匯編命令,具體操作如下圖所示:

在代碼上右擊鼠標,選擇如下命令:

我們來詳細分析一下我們現在看到的代碼:
1: #include "stdio.h"
2:
3: void main()
4: {
00401010 push ebp
00401011 mov ebp,esp ;// 將現有的堆棧給了EBP寄存器。
00401013 sub esp,4Ch ;// 分配x4C大小的堆棧空間用來運算。
00401016 push ebx
00401017 push esi
00401018 push edi ;// 保存寄存器環境。
00401019 lea edi,[ebp-4Ch] ;// 這里用ebp寄存器減去一個偏移來定位我們定義的變量,
0040101C mov ecx,13h ;// 這里-4Ch是用來定位到堆棧頭,把堆棧內容改成int3中斷以免內存泄露。
00401021 mov eax,0CCCCCCCCh ;// 以上操作是保存堆棧環境,分配堆棧空間。
00401026 rep stos dword ptr [edi] ;// 在下一次課題講述函數時,我們會講到,這里大家可以略過。
5: int x = 1;
00401028 mov dword ptr [ebp-4],1;// [ebp-4]是我們的變量x,dword ptr是用來修飾這個變量是DWORD類型(也就是整型)。
6: int y = 2;
0040102F mov dword ptr [ebp-8],2;// MOV就是匯編指令,相當于我們C語言中"=" 賦值運算符,它的具體用法自己百度。
7: int z = 20;
00401036 mov dword ptr [ebp-0Ch],14h
8:
9: x ++; // 等同于x = x + 1;
0040103D mov eax,dword ptr [ebp-4]
00401040 add eax,1; // add指令就是我們C語言的"+"運算符,還有一個INC指令相當于我們的"++"運算符
00401043 mov dword ptr [ebp-4],eax
10: y --; // 等同于y = y - 1;
00401046 mov ecx,dword ptr [ebp-8]
00401049 sub ecx,1
0040104C mov dword ptr [ebp-8],ecx
11:
12: z /= x; // 等同于z = z / 1;
0040104F mov eax,dword ptr [ebp-0Ch]
00401052 cdq
00401053 idiv eax,dword ptr [ebp-4]
00401056 mov dword ptr [ebp-0Ch],eax
13: printf("%d, %d, %d\n", x, y, z);
00401059 mov edx,dword ptr [ebp-0Ch]
0040105C push edx
0040105D mov eax,dword ptr [ebp-8]
00401060 push eax
00401061 mov ecx,dword ptr [ebp-4]
00401064 push ecx
00401065 push offset string "%d, %d, %d\n" (0042001c) // 傳遞參數,具體規則將在下次課題“函數”中講述。
0040106A call printf (004010f0) ; // 調用printf函數打印結果
0040106F add esp,10h ; // 這里是C條用的對戰平衡方式。(具體將在下次課題“函數”中講述)
14:
15: z %= x;
00401072 mov eax,dword ptr [ebp-0Ch]
00401075 cdq
00401076 idiv eax,dword ptr [ebp-4]
00401079 mov dword ptr [ebp-0Ch],edx
16:
17: x = x+y;
0040107C mov edx,dword ptr [ebp-4]
0040107F add edx,dword ptr [ebp-8]
00401082 mov dword ptr [ebp-4],edx
18: z -= x;
00401085 mov eax,dword ptr [ebp-0Ch]
00401088 sub eax,dword ptr [ebp-4]
0040108B mov dword ptr [ebp-0Ch],eax
19: printf("%d, %d, %d\n", x, y, z);
0040108E mov ecx,dword ptr [ebp-0Ch]
00401091 push ecx
00401092 mov edx,dword ptr [ebp-8]
00401095 push edx
00401096 mov eax,dword ptr [ebp-4]
00401099 push eax
0040109A push offset string "%d, %d, %d\n" (0042001c)
0040109F call printf (004010f0)
004010A4 add esp,10h
20: }
004010A7 pop edi ; // 恢復寄存器環境
004010A8 pop esi
004010A9 pop ebx
004010AA add esp,4Ch ; // 平衡堆棧
004010AD cmp ebp,esp
004010AF call __chkesp (00401170) ; // DEBUG 模式程序專用的堆棧檢查函數。
004010B4 mov esp,ebp
004010B6 pop ebp
004010B7 ret
相信你根據上述代碼中的提示,應該能將這個匯編代碼看的差不多,當然,看不明白也沒有關系,我們需要掌握的匯編指令及其用法很少,就下面幾個:
mov/lea: 賦值/取地址。
add: 加法指令。
sub: 減法指令。
div/idiv: 除法指令。
mul/imul: 乘法指令。
這些匯編指令的具體用法大家自己百度或者參考相關資料,這里不做詳細說明, 下面開始我們的邏輯、關系運算。
2. 邏輯、關系運算。
提起什么邏輯運算,或者什么關系運算,看名字貌似很復雜的。不過這里可能讓大家放心的是,這些運算我們日常生活中經常用到,無非就是 真的,假的,是,不是,并且,或者之類的操作。
用于邏輯運算的運算符有如下幾個:
運算符
|
含義
|
&&
|
與(并且)
|
||
|
或(或者)
|
!
|
非(不是)
|
用于關系運算的運算符有如下幾個:

這些運算無非就是為了判斷一個表達式成立不成立,在C語言中,只要表達式的值不為零并且符合關系運算符的要求,那這個表達式就成立的,就可以用上述的兩種運算符進行比較運算,一般情況下,這些運算符會配合下節我們要講述的流程控制語句來使用,所以這里我就不給出具體用法的例子了,有情趣的朋友,可以繼續看下面章節中的例子
到現在我想主要的運算我都講完了,雖然不是很詳細,但是我想,只要大家堅持努力,多多百度,這些知識一定會掌握好的。
小學的時候,我們學過四則運算,在運算的時候,遵循先乘除,后加減,有括弧的先算括弧里面的,這個規則在這里一樣使用,只不過在編程環境中,運算符很多,所以需要有個更為詳細的運算符優先級表。這里我把它貼出來,但是還希望大家能夠盡量的使用括弧來讓人看的容易,以免出錯,具體優先級表轉載如下:

更詳細的用法可以參考如下鏈接:http://www.xxlinux.com/linux/article/development/soft/20060909/4128.html
二、 三種程序流程控制結構詳解。
有了上述的運算,我想大家都可以寫出一些很簡單的代碼了,但是我們在寫代碼的時候,肯定會遇到類似這樣的問題:
Ø 有時候,我們寫的代碼必須要在達到某種條件之后才可以執行,否則不讓運行。
Ø 有時候,我們寫的代碼很龐大,很羅嗦,因為它有幾種可能需要我們來寫出幾個程序。
Ø 有時候,我們寫的代碼需要一直重復執行直到某種條件不成立了才不執行。
只要你遇到過上述的問題,那我們這節課的內容就正是你所需要的。不多廢話,進入正題:
1. 順序結構
到現在為止,我們寫的所有的代碼都是順序結構的,所謂順序結構,就是代碼從第一條指令開始執行,直到執行完最后一條。中間不會落下任何一條指令。
想必大家現在應該能理解什么是順序結構了,所以我不想再這里浪費太多的篇幅,直接進入下一小節。
2. 分支結構
所謂分支結構,就是代碼在達到某種條件的時候,執行某些指令,否則就執行別的指令。
分支結構是改變代碼執行順序最簡單的方式,所以大家一定可以很容易的掌握它的,下面讓我們一個一個的來看。
a) if … else結構
這個結構算是編程中最基礎的結構了,它有三種格式,我在這里列出來,大家可以根據實際情況選擇使用哪個:
第一種格式:
if (條件表達式)
{
//條件成立時執行這里的語句
…
}
第二種格式:
if (條件表達式)
{
//條件成立時執行這里的語句
…
}
else
{
//條件不成立時執行這里的語句
…
}
第三種格式:
if (條件表達式1)
{
//條件表達式1成立時執行這里的語句
…
}
else if (條件表達式2) // 這里的else if可以有無限多個(如果有很多個時可以參考使用switch語句)。
{
//條件表達式2成立時執行這里的語句
…
}
else
{
//條件表達式都不成立時執行這里的語句
…
}
為了更好的說明這個語句的用法,我舉個例子:
int MaxNum(int num001, int num002, int num003)
{
if (num001 >= num002)
{
if (num001 >= num003)
{
return num001; // 將 num001 作為函數的結果返回出來。
}
else
{
return num003;
}
}
else
{
if (num002 >= num003)
{
return num002;
}
else
{
return num003;
}
}
}
說明:上述代碼中的功能是從提供的三個數:num001、num002、num003中選出最大的數來。
至于與這些if有關的匯編指令就是跳轉,像sub,cmp,test,之類的比較指令來影響相應的標志寄存器還有JE,JNE,JB,JNB之類的跳轉指令來跳轉到指定的代碼中執行,大家可以像我們分析算術運算的方式一樣,去調試它,去分析它,去掌握這些比較和跳轉指令的用法。
這里我就省下篇幅,繼續我們的switch結構。
b) switch … 結構
上小節中講述的if語句,是用于少數分支時的處理語句,它寫起來方便,代碼簡潔明了,但是如果一些表達式的結構可能有5種甚至更多種結果,需要我們分別作出不同的處理時,最好的選擇就是用wsitch語句。
先說明一下switch結構的語法格式:
switch(表達式結果或者存放結果的變量)
{
case 結果1:
// 當switch后的括弧中的值是結果時,就執行這里的語句
...;
break; // break是用來跳出分支結構的關鍵字,如果這里沒有它,只要結果是結果1,那從結果1開始向下的所有指令都會被執行(包括結果2,結果3……)。
case 結果2:
// 當switch后的括弧中的值是結果時,就執行這里的語句
...;
break; // 如果這里沒有這個關鍵字時,只要上面的結果是結果2,那從結果2開始向下的所有指令都會被執行(包括結果3,結果4……)。
case 結果3:
// 當switch后的括弧中的值是結果時,就執行這里的語句
...;
break; // 同上
case 結果N:
// 當switch后的括弧中的值是結果N時,就執行這里的語句
...;
break;
default:
// 當switch后的括弧中的值不是上面列出的任何一個值時,就執行這里的語句
...;
}
這里寫的可能有點模糊,我給出一個代碼片段,我說明一下switch語句的用法:
PGAME_CHAR_INFO pGCI = GetCharInfoPoint();
switch (pGCI->dwZhiYe)
{
case 1:
lstrcpyW(szTemp, L"靈劍\0");
break;
case 2:
lstrcpyW(szTemp, L"日羽\0");
break;
case 3:
lstrcpyW(szTemp, L"槍俠\0");
break;
case 4:
lstrcpyW(szTemp, L"薩滿\0");
break;
case 5:
lstrcpyW(szTemp, L"法皇\0");
break;
case 6:
lstrcpyW(szTemp, L"藥王\0");
break;
default:
lstrcpyW(szTemp, L"未知\0");
break;
}
其實,這個switch的匯編形式跟if結構很像,唯一的區別就是每個分支后面都會有一個break跳轉(JMP)指令,大家可以自己試著去調試這段代碼,分析一下盡量掌握這些代碼的匯編形式。
3. 循環結構
所謂循環結構,就是一直重復執行某段指定的語句,知道條件不滿足了為止。
a) for循環結構
好,按照我們的習慣,我先寫出這個語句的基本語法結構:
for (初始值; 滿足條件; 增量)
{
要循環的語句;
}
例如下面的代碼:
#include "stdio.h"
void main()
{
int i = 10;
// 循環打印輸出0到之間的所有自然數。
for (int x = 0; x < i; x++)
{
printf("%d\n", x);
}
}
打印結果如下:

通過這個結果和上面的語法結構,我們可以猜測出for語句的執行流程如下圖所示:

左圖中描述了for語句的執行流程,先從上面的語句中執行到for關鍵字,然后開始初始化操作,在判斷一下循環的條件是否滿足:
如果條件滿足,則繼續按照綠色的箭頭執行,開始執行要循環的語句,執行完以后,會來到增量(或叫 步長)這個語句對計數器(記錄循環次數的變量)進行增加,然后在去判斷是否滿足循環條件,如果滿足繼續執行要循環的語句,如此循環。
直到條件不滿足了,按照紫色箭頭所示,跳出循環繼續執行后面的語句。
現在讓我們來調試跟蹤一下上面的那段程序, 確切的體會下電腦是如何執行循環語句的:
1: #include "stdio.h"
2:
3: void main()
4: {
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,48h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-48h]
0040101C mov ecx,12h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
5: int i = 10;
00401028 mov dword ptr [ebp-4],0Ah ; // 初始化變量i
6:
7: // 循環打印輸出0到之間的所有自然數。
8: for (int x = 0; x < i; x++)
0040102F mov dword ptr [ebp-8],0 ; // 初始化x變量
00401036 jmp main+31h (00401041) ; // 強制跳到1041 這個位置
00401038 mov eax,dword ptr [ebp-8]
0040103B add eax,1 ; // 自變量加一,繼續判斷比較
0040103E mov dword ptr [ebp-8],eax
00401041 mov ecx,dword ptr [ebp-8] ; // 開始比較i和x兩個變量
00401044 cmp ecx,dword ptr [ebp-4]
00401047 jge main+4Ch (0040105c); // 如果ebp-8得值(也就是x的值)大于等于i的值則跳出循環
9: {
10: printf("%d\n", x);
00401049 mov edx,dword ptr [ebp-8]
0040104C push edx
0040104D push offset string "%d\n" (0042001c)
00401052 call printf (00401090) ; // 打印變量
00401057 add esp,8
11: }
0040105A jmp main+28h (00401038) ; // 調回去,累加器加一,然后繼續循環
12: }
0040105C pop edi ; // 退出循環,恢復上下文環境。
0040105D pop esi
0040105E pop ebx
0040105F add esp,48h ; // 平衡堆棧。
00401062 cmp ebp,esp
00401064 call __chkesp (00401110)
00401069 mov esp,ebp
0040106B pop ebp
0040106C ret
好,到這里相信大家對for循環已經明白了,其實那個語法結構中的什么初始化,什么增量,無所謂的,只要明白了它的執行順序,然后寫上相應的代碼就可以了,接下來就是多多使用它,熟練它就好。
b) while循環結構
與for循環相比,while有很多的優點,當然它也簡單的一塌糊涂。我直截了當的說下這個while語句的兩種寫法和它們之間的區別:
按照 for 語句中圖的分析方法,可以看出while的兩種寫法都比較容易,它們的區別也很明顯,一個先判斷下是否應該循環,另一個就不管三七二十一,先執行一遍循環體再說……
這個while語句比較容易,而且與for可以兼容,所以這里我就不再浪費篇幅去分析它了。大家如果有想去可以自己分析一下,看下它們的匯編模樣。
到這里,我們這一小節的所有知識點都已經說完了,希望大家能靈活運用它們,根據實際情況,它們之間是可以嵌套的,比如if語句中使用for語句,for語句中使用一個switch語句,等等。
三、 養成良好的代碼編寫風格。
引用一下高質量C++/C編程指南:http://man.chinaunix.net/develop/c&c++/c/c.htm(只看能看懂的部分)
四、 結束語
通過本次專題的學習,我相信大家可以寫一個很基礎的代碼了,再配合我們下節課要講述的內容,我們的基礎部分就要結束了。
多寫程序,多看人家的程序,多作一些C語言的作業,靈活一下。