計算機語言和人類語言類似,人類語言是為了解決人與人之間交流的問題,而計算機語言是為了解決程序員與計算機之間交流的問題。程序員編寫的程序就是計算機的控制指令,控制計算機的運行。借助于編譯工具,可以將各種不同的編程語言的源程序轉換為計算機可以執行的機器語言。
?????? 計算機程序是計算機的控制指令,控制計算機完成特定的功能。我們編寫源程序的過程和建造房屋的過程類似,大致分為五個步驟。
?????? 第一步:設計程序的架構,類似于房屋設計。先設計好圖紙之后再進行下一步的建造工作。
?????? 第二步:準備程序需要的數據,相當于建造房屋的原材料。
?????? 第三步:分析程序功能實現的算法,相當于建造房屋的工藝流程。
?????? 第四步:編寫源程序——開始建造。
?????? 第五步:編寫完成后調試程序——建造房屋后的清理修補工作。
?????? 上述五個步驟中,最重要的就是程序的設計工作。如果設計出了問題,會導致后續工作的失敗。
針對功能較為復雜的程序,程序開發有一套標準的流程,我們將上述五個步驟進一步細化:
?????? 第一步:分析需求,設計程序結構框架;
?????? 第二步:數據定義,定義恰當的數據結構;
?????? 第三步:分析算法;
?????? 第四步:編寫偽代碼,即用我們自己的語言來編寫程序;
?????? 第五步:畫流程圖,使用Visio、Excel或者其他繪圖工具繪制算法流程和邏輯關系圖;
?????? 第六步:編寫源程序,其實就是將我們的偽代碼翻譯成計算機語言;
?????? 第七步:調試程序,修復程序中可能出現的BUG;
?????? 第八步:優化代碼,嘗試更好的設計方案,效率更高的算法,邏輯更為清晰簡潔明了。這一步可以使我們學到更多的東西,何樂而不為呢。
?????? 在今后的學習中,建議讀者嚴格遵循這樣的流程,養成良好的編程習慣,受益終身。
提示
請記住兩句話:
程序=數據結構+算法。
代碼量的多少是衡量程序員水平最客觀的標準。
數據結構是第二步要做的事情,算法是第三步要做的事情。
??????
數據結構就是將數據以什么樣的形式存儲到計算機內存。數據存儲需要考慮以下幾個問題:
1.以字節為單位,還是以字為單位,或者是其他大小的內存塊為單位存儲數據;
2.以連續的形式存儲(例如數組),還是不連續的形式存儲(例如鏈表)到計算機的內存;
3.數據是在剛加載程序時就存儲到內存(靜態分配內存),還是在程序的執行過程中存儲到內存(動態分配內存);
4.數據是以數值的方式存儲和引用,還是以地址的方式存儲和引用,還是兩種方式的組合。
5.數據是以什么樣的順序存儲,升序還是降序等。
選擇合適的數據結構非常重要,關系到程序執行的效率和功能的實現。好的數據結構,可以大大簡化程序功能實現的算法。
算法就是程序功能實現的具體流程。計算機程序中的算法和數學中的算法相似,但不完全相同,更多的時候,我們將程序中的算法稱為程序執行的流程和先后順序。
?????? 上述提示內容在剛開始學習時不要求理解,隨著代碼量的增加,會終有所悟。
在程序設計時需要使用三種基本的結構:順序結構、分支結構和循環結構。不論多么復雜的程序,都是由這三種結構組合而成。接下來,我們分別學習這三種程序設計結構的構建方法。
■順序結構:按照指令代碼排列的先后順序執行,稱之為順序結構。
■分支結構:指程序根據一定的條件有選擇的執行路徑。
■循環結構:指在程序中需要反復執行某個功能而設置的一種程序結構,可以看成是一個條件判斷語句和一個向回轉向語句的組合。
順序結構相對比較簡單,分支結構和循環結構中必定包含順序結構語句塊,本書不再贅述。接下來我們將在本章講述分支結構的程序設計方法,下一章講述循環結構的程序設計方法。
本章學習知識概要:
????? if句
????? ese語句
????? switch語句
6.1 if語句
本節必須掌握的知識點:
??? ????示例十八
??? ????代碼分析
??? ????匯編解析
??? ????示例十九
??? ????代碼分析
??? ????匯編解析
什么是語句?在C語言中,語句大部分是由分號結尾的。
舉例
int a = 0;
int b = 1; int c = 2; int d = 3;
語句包括:賦值表達式語句、空語句、復合語句、函數表達式語句、控制語句等。
本章我們將要學習if語句、else if句和switch語句。
6.1.1 示例十八
■if語句表達形式1
程序執行到if語句時,判斷表達式的值,如果結果為真(非0),則執行相應的語句。
if(表達式){
?????? statement
}
示例代碼十八
?????? ●第一步:分析需求,設計程序結構框架。
分析需求:判斷輸入的整數,是否能被2整除。
設計程序結構框架:分支結構(if語句形式1)if語句。
?????? ●第二步:數據定義,定義恰當的數據結構;
?????? int num;//定義一個int類型的整型局部變量。
?????? ●第三步:分析算法。
?????? 整數num除以2,如果余數為0,則可以被2整除。如果余數不為0,則不能被2整除。
?????? ●第四步:編寫偽代碼,即用我們自己的語言來編寫程序。
?????? int main(void) {
??? 定義一個int類型整型變量num;
??? 調用printf函數打印一個提示信息"請輸入一個整數:";
??? 調用scanf_s函數接收鍵盤輸入一個整數,并存入變量num;
??? if (num % 2) 如果條件為真(余數不為0)
??????? 調用printf函數輸出"您輸入的整數不能被2整除!";
??? system("pause");
??? return 0; ?????????????????????????????????????????????????????????????????????????????????????????????????? ??
}
圖6-1 示例十八流程圖
?????? ●第五步:畫流程圖,使用Visio、Excel或者其他繪圖工具繪制算法流程和邏輯關系圖;???? 【注】強烈建議讀者在編寫工程項目時,一定要寫偽代碼、畫流程圖,具體流程圖詳解見附錄D。 本書在后續章節中可能會由于節約篇幅的考慮,省略了部分流程。??????????
●第六步:編寫源程序,其實就是將我們的偽代碼翻譯成計算機語言;
/*
?? 判斷輸入的整數,是否能被2整除
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
??? int num;
??? printf("請輸入一個整數:");
??? scanf_s("%d", &num);
??? if (num % 2)
??????? printf("您輸入的整數不能被2整除!\n");//單語句可以省略大括號
??? system("pause");
??? return 0;
}
●輸出結果:
?????? 請輸入一個整數:3
您輸入的整數不能被2整除!
?????? ●第七步:調試程序,修復程序中可能出現的BUG;
參見反匯編代碼。
●第八步:優化代碼,嘗試更好的設計方案,效率更高的算法,邏輯更為清晰簡潔明了。
示例十九中我們將改用“if語句表達形式2(if/else結構)”。
6.1.2 代碼分析
示例十八由鍵盤輸入一個整數值存入變量num。然后采用num%2取模的算法作為if語句的條件,余數為0,條件為假,不會執行if語句塊內的printf語句。如果余數非零,條件為真,執行if語句塊內的printf語句。
?
總結
1.在C語言的語法中規定,大括號內的語句稱為語句塊,例如函數體、if語句塊、while語句塊,或者任意大括號內的語句塊。
2.如果語句塊內的語句為單語句,則可以省略大括號。如果大括號內的語句為復合語句,則不可以省略大括號。
3.C語言語句的縮進關系表示從屬關系,例如:
??? if (num % 2)
??????? printf("您輸入的整數不能被2整除!\n");//單語句可以省略大括號
printf語句的縮進表示該語句從屬于if語句塊。如果沒有省略大括號,則不存在任何疑義。
6.1.3 匯編解析
■匯編代碼
;C標準庫頭文件和導入庫
include vcIO.inc
.data??????
num sdword? ?
.const????
szMsg1 db "請輸入一個整數:",0
szMsg2 db "%d",0
szMsg3 db "您輸入的整數不能被2整除!",0dh,0ah,0
.code?????
start:
?????? ;輸入整數num
?????? invoke printf,offset szMsg1
?????? invoke scanf,offset szMsg2,ADDR num
?????? ;
?????? mov eax,num
?????? mov ebx,2
?????? cdq? ?;被除數擴展到EDX:EAX
?????? idiv ebx
?????? .if edx
????????????? invoke printf,offset szMsg3
?????? .endif
?????? ;?????
?????? invoke _getch
?????? ret???????????????????????
end start
●輸出結果:
請輸入一個整數:3
您輸入的整數不能被2整除!
上述匯編代碼中,取模運算采用的是有符號數除法指令idiv。因為num為int類型的有符號整數,作為被除數需要使用cdq指令將其擴展為64位EDX:EAX,除數2存入ebx寄存器,然后使用idiv指令除以ebx,商保存在eax寄存器中,余數保存在edx寄存器中。接下來使用高級匯編偽指令.if,edx余數作為其條件表達式,如果edx不為0,則條件為真,執行下面的printf語句。
■反匯編代碼
???int num;
??? printf("請輸入一個整數:");
002D1952? push??????? offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xa3\xba" (02D7B30h)?
002D1957? call??????? _printf (02D104Bh)?
002D195C? add???????? esp,4?
??? scanf_s("%d", &num);
002D195F? lea???????? eax,[num]?
002D1962? push??????? eax?
002D1963? push??????? offset string "%d" (02D7B44h)?
??? scanf_s("%d", &num);
002D1968? call??????? _scanf_s (02D1154h)?
002D196D? add???????? esp,8?
??? if (num % 2)
002D1970? mov???????? eax,dword ptr [num]?
002D1973? and???????? eax,80000001h?
002D1978? jns???????? main+5Fh (02D197Fh)? ;符號位為0,最高位為0,即正整數時則跳轉
002D197A? dec???????? eax? ;為負整數時,最高位為1,先減1
002D197B? or????????? eax,0FFFFFFFEh? ;除最高位和最低位,其余各位置1
002D197E? inc???????? eax? ;最低位加1,如果最低位為1,則奇數+1為0,否則為偶數
002D197F? test??????? eax,eax? ;與運算,不保存結果,測試eax值是否為0
002D1981? je????????? main+70h (02D1990h)? ;如果eax為0則跳轉02D1990h地址處,否則為真
??? printf("您輸入的整數不能被2整除!\n");//單語句可以省略大括號
002D1983? push??????? offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xd5\xfb\xca\xfd\xb2\xbb\xc4\xdc\xb1\xbb2\xd5\xfb\xb3\xfd!\n" (02D7B48h)?
002D1988? call??????? _printf (02D104Bh)?
002D198D? add???????? esp,4?
??? system("pause");
002D1990? mov???????? esi,esp?
以上是C代碼的反匯編代碼,對比我們自己寫的匯編代碼,編譯器翻譯if(num%2)時有顯著不同。請讀者仔細閱讀注釋。這段反匯編代碼是通過判斷num為正整數還是負整數,是奇數還是偶數的方法,最終判斷是否可以被2整除。其中使用了兩個掩碼80000001h和0FFFFFFFEh。如果num的二進制數據位的最后一位為1,則為奇數,最后一位為0,則為偶數。
【注】不同的VS版本的反匯編代碼存在差異,但是功能和結果肯定沒有問題。
6.1.4 示例十九
■if語句表達形式2
if語句是判斷表達式是否成立,當表達式成立時,則做相應的事情;當表達式不成立時,則做另一件事情。語法格式:
if (表達式)
? statement1
else
? statement2
解析:如果條件成立為真(不為0)時,則執行語句statement1;否則執行語句statsement2。
示例代碼十九
●第一步:分析需求,設計程序結構框架。
分析需求:判斷輸入的整數,是否能被2整除。
設計程序結構框架:分支結構(if語句形式2)if/else語句。
?????? ●第二步:數據定義,定義恰當的數據結構;
?????? int num;//定義一個int類型的整型局部變量。
?????? ●第三步:分析算法。
?????? 整數num除以2,如果余數為0,則可以被2整除。如果余數不為0,則不能被2整除。
?????? ●第四步:編寫偽代碼,即用我們自己的語言來編寫程序。
?????? int main(void) {
??? 定義一個int類型整型變量num;
??? 調用printf函數打印一個提示信息"請輸入一個整數:";
??? 調用scanf_s函數接收鍵盤輸入一個整數,并存入變量num;
??? if (num % 2) 如果條件為真(余數不為0)
??????? 調用printf函數輸出"您輸入的整數不能被2整除!";
??? else
??????? 調用printf函數輸出"您輸入的整數能被2整除!";
??? system("pause");
??? return 0;
}
?????? ●第五步:畫流程圖,使用Visio、Excel或者其他繪圖工具繪制算法流程和邏輯關系圖;?????
?????????????
????????????????????????????????????????圖6-2 示例十九流程圖
●第六步:編寫源程序,其實就是將我們的偽代碼翻譯成計算機語言;
/*
?? 判斷輸入的整數,是否能被2整除
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
??? int num;
??? printf("請輸入一個整數:");
??? scanf_s("%d", &num);
??? if (num % 2)
??????? printf("您輸入的整數不能被2整除!\n");//單語句可以省略大括號
??? ??? else
??????? printf("您輸入的整數能被2整除!\n");//C語言中縮進表示從屬關系
??? system("pause");
??? return 0;
}
●輸出結果:
測試1
請輸入一個整數:3
您輸入的整數不能被2整除!
測試2
請輸入一個整數:4
您輸入的整數能被2整除!
6.1.5 代碼分析
?????? 上述代碼,if語句的條件表達式(num % 2)如果為真,則執行if語句塊;如果條件為假,則執行else語句塊。同時輸出兩種情形,不存在任何遺留的情況。代碼邏輯更為清晰和完整,也更加人性化一些。
6.1.6 匯編解析
■匯編代碼
?????? ;C標準庫頭文件和導入庫
include vcIO.inc
.data??????
num sdword? ?
.const????
szMsg1 db "請輸入一個整數:",0
szMsg2 db "%d",0
szMsg3 db "您輸入的整數不能被2整除!",0dh,0ah,0
szMsg4 db "您輸入的整數能被2整除!",0dh,0ah,0
.code?????
start:
?????? ;輸入整數num
?????? invoke printf,offset szMsg1
?????? invoke scanf,offset szMsg2,ADDR num
?????? ;
?????? mov eax,num
?????? mov ebx,2
?????? cdq??????????????? ;被除數擴展到EDX:EAX
?????? idiv ebx
?????? .if edx
????????????? invoke printf,offset szMsg3
?????? .else
????????????? invoke printf,offset szMsg4?
?????? .endif
?????? ;?????
?????? invoke _getch
?????? ret???????????????????????
end start
●輸出結果:
測試1
請輸入一個整數:3
您輸入的整數不能被2整除!
測試2
請輸入一個整數:4
您輸入的整數能被2整除!
上述匯編代碼使用了高級匯編偽指令.if/.else語句,與C語言if/else語句完全相同。
■反匯編代碼
??? int num;
??? printf("請輸入一個整數:");
00211952? push??????? offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xca\xfd\xa3\xba" (0217B30h)?
??? int num;
??? printf("請輸入一個整數:");
00211957? call??????? _printf (021104Bh)?
0021195C? add???????? esp,4?
??? scanf_s("%d", &num);
0021195F? lea ????????eax,[num]?
00211962? push??????? eax?
00211963? push??????? offset string "%d" (0217B44h)?
00211968? call??????? _scanf_s (0211154h)?
0021196D? add???????? esp,8?
??? if (num % 2)
00211970? mov???????? eax,dword ptr [num]?
00211973? and???????? eax,80000001h?
00211978? jns???????? main+5Fh (021197Fh)?
0021197A? dec???????? eax?
0021197B? or????????? eax,0FFFFFFFEh?
0021197E? inc???????? eax?
0021197F? test??????? eax,eax?
00211981? je????????? main+72h (0211992h);如果條件為假,即eax=0則跳轉0211992h地址處
??????? printf("您輸入的整數不能被2整除!\n");//單語句可以省略大括號;if語句塊
00211983? push??????? offset string "\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xd5\xfb\xca\xfd\xb2\xbb\xc4\xdc\xb1\xbb2\xd5\xfb\xb3\xfd!\n" (0217B48h)?
00211988? call??????? _printf (021104Bh)?
0021198D? add???????? esp,4?
00211990? jmp???????? main+7Fh (021199Fh)?
??? else
??????? printf("您輸入的整數能被2整除!\n");//C語言中縮進表示從屬關系;else語句塊
00211992? push??????? offset string ;"\xc4\xfa\xca\xe4\xc8\xeb\xb5\xc4\xd5\xfb\xca\xfd\xc4\xdc\xb1\xbb2\xd5\xfb\xb3\xfd!\n" (0217B68h)?
00211997? call??????? _printf (021104Bh)?
0021199C? add? ???????esp,4?
仔細閱讀上述反匯編代碼的注釋:
0021197F? test??????? eax,eax?
00211981? je????????? main+72h (0211992h);如果條件為假,即eax=0則跳轉0211992h地址處
if (num % 2)
?????? 如果條件為真,執行if語句塊
printf("您輸入的整數不能被2整除!\n");//單語句可以省略大括號;if語句塊
else
如果條件為假,執行else語句塊
??? printf("您輸入的整數能被2整除!\n");//C語言中縮進表示從屬關系;else語句塊
00211992? push??????? offset
練習
?????? 1、輸入一個整數,判斷是否可以被5整除。
?????? 2、輸入一個整數,判斷是否為奇數。
?????? 3、輸入學生的成績,判斷是否及格(大于等于60分及格)。