- 1.1 軟件,程序與計算機語言
軟件是為完成某些特定功能而編制的一個到多個程序文件的集合。
程序是計算機指令的序列,編制程序的工作就是為計算機編制指令序列。
人和計算機交流也要用人和計算機都容易接受和理解的語言,這就是計算機語言。
- 1.2 程序語言的發展
1.2.1 機器語言
計算機的機器語言都是由0和1組成,計算機的所有數據都是由0和1組成。
1.2.2 匯編語言
匯編語言是用符號來表示這些固定的二進制指令的語言。
1.2.3 高級語言
高級語言總是盡量接近"高級動物”的自然語言和思維方式。
- 1.3 軟件開發的基本方法與步驟
1.分析問題,建立數據模型。
2.確定數據結構和算法。
3.編織程序。
4.調試程序。
1.3.1 算法
通常計算機算法分為兩大類:數值運算算法和非數值運算算法。
數值運算是對問題求數值解,非數值運算包括非常廣泛的領域。數值運算有確定的數學模型,一般都有比較成熟的算法。非數值運算的種類繁多,要求不一。
- 算法的基本特征(有窮性,確定性,有效性,有零個或多個輸入,有一個或多個輸出。
- 算法的表示—流程圖(對給定算法的一種圖形解法,流程圖又稱為框圖,它用規定的一系列圖形,流程線及文字說明來表示算法中的基本操作和控制流程,其優點是形象直觀,簡單易懂,便於修改和交流。)
流程圖包括起止框,輸入/輸出框,處理框,判斷框,注釋框,流程框,連接點。
1.3.2 編碼實現
創建一個C語言程序的步驟:
1.編寫源代碼
2.將源代碼編譯成目標代碼。
3.鏈接目標代碼成為可執行程序
- 源代碼
將源程序的代碼指令存儲,計算機采用兩種不同格式存儲文件,分別是文本格式和二進制格式。
文本文件包括ASCII碼字符集的符號。ASCII字符集包括了字母表的大小寫字母。還包括了從0-9的數和一些標點符號。二進制文件是由二進制數組成。
當編寫程序的時候,我們將源代碼存儲到文本文件中,程序員通常會將多個源代碼的文本文件創建一個程序。通過很短時間的編譯和鏈接過程,文本文件中的源代碼被轉化成二進制指令存儲到二進制文件中。
- 編譯源代碼
計算機不能執行源代碼,我們要編寫編譯器或翻譯器,這樣,就可以將程序從高級語言程序轉化成二進制代碼,也就是機器語言。
- 鏈接
當鏈接起將程序鏈接成可執行的形式時,它用多個庫來鏈接目標程序。庫是一些預先編譯好的函數的集合。這些函數可能完成一項或多項任務。
1.3.3 調試程序
程序中最易出現的幾種不同類型錯誤是
- 語法錯誤
- 邏輯錯誤
- 開發錯誤
- 運行時錯誤
2.1編程語言概述
學習一門新語言的惟一途徑就是使用它來編程。對於初學者來說, 編寫的第一個程序幾乎都是相同的,即在屏幕上顯示“Hello, world”,專業說法為打印“Hello, world”。
【例1-1】打印 Hello, world。
- /* 第一個C語言程序
- 打印 Hello, world */
- #include<stdio.h> //包含頭文件stdio.h
- int main(){ // 主函數
- printf("Hello, word\n"); // 打印字符串
- return 0; // 返回0,表示程序正確運行
- }
盡管這個程序很簡單,但對初學者來說,它仍然可能成為一大障礙,因為要實現這個目的,首先要編寫代碼,然后編譯、鏈接並運行,最后看到輸出結果。掌握了這些操作細節以后,其它事情就比較容易了。
關於編譯和鏈接將在下一節說明,這里先對程序進行解釋:
- 第1行包含標准庫文件,include稱為文件包含命令,擴展名為.h的文件稱為頭文件。
- 第2行定義名為main的函數,它不接受參數值;main函數的語句都被括在花括號中;int為main函數返回值類型。
- 第3行打印“Hello, world”,main函數調用庫函數printf以顯示字符序列。
- 第4行表示main函數的返回值為0,return讓函數返回一個值。
- 第5行結束main函數,花括弧必須成對出現。
位於“/* */”中和“//”后面的內容為注釋,用來對程序進行說明;注釋在編譯時會被自動忽略。
一個C語言程序,無論其大小,都是由函數和變量組成的。
函數具有某些特定功能,能執行特定操作;函數中包含一些語句,以說明操作的過程。變量則用於存儲計算過程中使用的值。
在本例中,函數的名字為main。通常情況下,函數的命名沒有限制,但main是一 個特殊的函數名,每個程序都從main函數的起點開始執行,這意味着每個程序都必須在某 個位置包含一個main函數。
main函數通常會調用其它函數來幫助完成某些工作,被調用的函數可以是我們自己編寫的,也可以來自於函數庫。上述程序段中的第一行語句#include <stdio.h>用於告訴編譯器在本程序中包含標准輸入/輸出庫。許多C語言源程序的開始處都包含這一行語句。我們將在后續章節對標准庫進行詳細介紹。
函數之間進行數據交換的一種方法是調用函數向被調用函數提供一個值(稱為參數)列表。函數名后面的一對圓括號將參數列表括起來。在本例中,main函數不需要任何參數,因此用空參數表( )表示。
函數中的語句用一對花括號{}括起來。本例中的main函數包含下面兩條語句:
- printf("Hello, word\n");
- return 0;
調用函數時,只需要使用函數名加上用圓括號括起來的參數表即可。上面這條語句將"hello, world\n"作為參數調用printf函數。printf是一個用於打印輸出的庫函數,在此處, 它打印雙引號中間的字符串。
用雙引號括起來的字符序列稱為字符串或字符串常量,如"hello, world\n"就是一個字符串。目前我們僅使用字符串作為printf及其它函數的參數。
在C語言中,字符序列\n表示換行符,在打印中遇到它時,輸出打印將換行,從下一行的左端行首開始。如果去掉字符串中的\n(這是個值得一做的練習),即使輸出打印完成后也不會換行。在printf函數的參數中,只能用\n表示換行符。如果用程序的換行代替,例如:
printf("Hello, word
");
C編譯器將會產生一條錯誤信息。
printf函數永遠不會自動換行,這樣我們可以多次調用該函數以分階段得到一個長的輸出行。上面給出的第一個程序也可以改寫成下列形式:
-
#include<stdio.h> int main(){ printf("Hello, "); printf("word"); printf("\n"); return 0; }
這段程序與前面的程序的輸出相同。
2.2 基本數據類型
所謂數據類型是按被定義變量的性質,表示形式,占據存儲空間的多少,構造特點來划分的。在C語言中,數據類型可分為:基本數據類型,構造數據類型,指針類型,空類型四大類。
數據類型說明:
數據類型 | 說明 |
---|---|
基本數據類型 | 基本數據類型最主要的特點是,其值不可以再分解為其它類型。也就是說,基本數據類型是自我說明的。 |
構造數據類型 | 構造數據類型是根據已定義的一個或多個數據類型用構造的方法來定義的。也就是說,一個構造類型的值可以分解成若干個“成員”或“元素”。每個“成員”都是一個基本數據類型或又是一個構造類型。在C語言中,構造類型有以下幾種:數組類型、結構體類型、共用體(聯合)類型。 |
指針類型 | 指針是一種特殊的,同時又是具有重要作用的數據類型。其值用來表示某個變量在內存儲器中的地址。雖然指針變量的取值類似於整型量,但這是兩個類型完全不同的量,因此不能混為一談。 |
空類型 | 在調用函數值時,通常應向調用者返回一個函數值。這個返回的函數值是具有一定的數據類型的,應在函數定義及函數說明中給以說明,例如在例題中給出的max函數定義中,函數頭為:
int max(int a,int b);
其中“int ”類型說明符即表示該函數的返回值為整型量。又如在例題中,使用了庫函數sin,由於系統規定其函數返回值為雙精度浮點型,因此在賦值語句s=sin (x);中,s也必須是雙精度浮點型,以便與sin函數的返回值一致。所以在說明部分,把s說明為雙精度浮點型。但是,也有一類函數,調用后並不需要向調用者返回函數值,這種函數可以定義為“空類型”。其類型說明符為void。在后面函數中還要詳細介紹。 |
2.3 數據類型轉換
自動轉換
自動轉換發生在不同數據類型的量混合運算時,由編譯系統自動完成。自動轉換遵循以下規則:
- 若參與運算量的類型不同,則先轉換成同一類型,然后進行運算。
- 轉換按數據長度增加的方向進行,以保證精度不降低。如int型和long型運算時,先把int量轉成long型后再進行運算。
- 所有的浮點運算都是以雙精度進行的,即使僅含float單精度量運算的表達式,也要先轉換成double型,再作運算。
- char型和short型參與運算時,必須先轉換成int型。
- 在賦值運算中,賦值號兩邊量的數據類型不同時,賦值號右邊量的類型將轉換為左邊量的類型。如果右邊量的數據類型長度比左邊長時,將丟失一部分數據,這樣會降低精度,丟失的部分按四舍五入向前舍入。
下圖表示了類型自動轉換的規則。
【例2-1】自動數據類型轉換
-
#include<stdio.h> int main(){ float PI=3.14159; int s,r=5; s=r*r*PI; printf("s=%d\n",s); return 0; }
本例程序中,PI為實型;s,r為整型。在執行s=r*r*PI語句時,r和PI都轉換成double型計算,結果也為double型。但由於s為整型,故賦值結果仍為整型,舍去了小數部分。
強制類型轉換
強制類型轉換是通過類型轉換運算來實現的。其一般形式為:
(類型說明符) (表達式)
其功能是把表達式的運算結果強制轉換成類型說明符所表示的類型。
例如:
- (float) a; /* 把a轉換為實型 */
- (int)(x+y); /* 把x+y的結果轉換為整型 */
在使用強制轉換時應注意以下問題:
- 類型說明符和表達式都必須加括號(單個變量可以不加括號),如把(int)(x+y)寫成(int)x+y則成了把x轉換成int型之后再與y相加了。
- 無論是強制轉換或是自動轉換,都只是為了本次運算的需要而對變量的數據長度進行的臨時性轉換,而不改變數據說明時對該變量定義的類型。
【例2-2】強制數據類型轉換
-
#include<stdio.h> int main(void){ float f=5.75; printf("(int)f=%d,f=%f\n",(int)f,f); return 0; }
本例表明,f雖強制轉為int型,但只在運算中起作用,是臨時的,而f本身的類型並不改變。因此,(int)f的值為 5(刪去了小數)而f的值仍為5.75。
2.4 運算符的分類
運算符不僅具有不同的優先級,還有不同的結合性。在表達式中,各運算量參與運算的先后順序不僅要遵守運算符優先級別的規定,還要受運算符結合性的制約,以便確定是自左向右進行運算還是自右向左進行運算。
C語言的運算符可分為以下幾類:
運算符 | 說明 |
---|---|
算術運算符 | 用於各類數值運算。包括加(+)、減(-)、乘(*)、除(/)、求余(或稱模運算,%)、自增(++)、自減(--)共七種。 |
關系運算符 | 用於比較運算。包括大於(>)、小於(<)、等於(==)、 大於等於(>=)、小於等於(<=)和不等於(!=)六種。 |
邏輯運算符 | 用於邏輯運算。包括與(&&)、或(||)、非(!)三種。 |
位操作運算符 | 參與運算的量,按二進制位進行運算。包括位與(&)、位或(|)、位非(~)、位異或(^)、左移(<<)、右移(>>)六種。 |
賦值運算符 | 用於賦值運算,分為簡單賦值(=)、復合算術賦值(+=, -=, *=, /=, %=)和復合位運算賦值(&=, |=, ^=, >>=, <<=)三類共十一種。 |
條件運算符 | 這是一個三目運算符,用於條件求值(?:)。 |
逗號運算符 | 用於把若干表達式組合成一個表達式(,)。 |
指針運算符 | 用於取內容(*)和取地址(&)二種運算。 |
求字節數運算符 | 用於計算數據類型所占的字節數(sizeof)。 |
特殊運算符 | 有括號(),下標[],成員(->,.)等幾種。 |
表達式是由常量、變量、函數和運算符組合起來的式子。一個表達式有一個值及其類型, 它們等於計算表達式所得結果的值和類型。表達式求值按運算符的優先級和結合性規定的順序進行。單個的常量、變量、函數可以看作是表達式的特例。
3.1 順序結構的設計實例
例【3-1】輸入三角形的三邊長,求三角形面積。
已知三角形的三邊長a、b、c,則該三角形的面積公式為:
area=( s(s-a)(s-b)(s-c) )1/2
其中s = (a+b+c)/2。
源程序如下:
-
#include <stdio.h> #include <math.h> int main(void){ float a,b,c,s,area; scanf("%f,%f,%f",&a,&b,&c); s=1.0/2*(a+b+c); area=sqrt(s*(s-a)*(s-b)*(s-c)); printf("a=%7.2f,b=%7.2f,c=%7.2f,s=%7.2f\n",a,b,c,s); printf("area=%7.2f\n",area); return 0; }
【例4.15】求ax2+bx+c=0方程的根,a、b、c由鍵盤輸入,設b2-4ac>0。
源程序如下:
-
#include <stdio.h> #include <math.h> int main(void){ float a,b,c,disc,x1,x2,p,q; scanf("a=%f,b=%f,c=%f",&a,&b,&c); disc=b*b-4*a*c; p=-b/(2*a); q=sqrt(disc)/(2*a); x1=p+q;x2=p-q; printf("\nx1=%5.2f\nx2=%5.2f\n",x1,x2); return 0; }
3.2 if語句的使用
用if語句可以構成分支結構。它根據給定的條件進行判斷,以決定執行某個分支程序段。C語言的if語句有三種基本形式。
語句的三種形式
1) 第一種形式為基本形式:if
if(表達式) 語句
其語義是:如果表達式的值為真,則執行其后的語句,否則不執行該語句。
【例3-3】
-
#include <stdio.h> int main(void){ int a,b,max; printf("\n input two numbers: "); scanf("%d%d",&a,&b); max=a; if (max<b) max=b; printf("max=%d",max); return 0; }
本例程序中,輸入兩個數a、b。把a先賦予變量max,再用if語句判別max和b的大小,如max小於b,則把b賦予max。因此max中總是大數,最后輸出max的值。
2) 第二種形式為: if-else
if(表達式)
語句1;
else
語句2;
其語義是:如果表達式的值為真,則執行語句1,否則執行語句2 。
【例3-4】
-
#include <stdio.h> int main(void){ int a, b; printf("input two numbers: "); scanf("%d%d",&a,&b); if(a>b) printf("max=%d\n",a); else printf("max=%d\n",b); return 0; }
輸入兩個整數,輸出其中的大數。改用if-else語句判別a,b的大小,若a大,則輸出a,否則輸出b。
3) 第三種形式為if-else-if形式
前二種形式的if語句一般都用於兩個分支的情況。當有多個分支選擇時,可采用if-else-if語句,其一般形式為:
if(表達式1)
語句1;
else if(表達式2)
語句2;
else if(表達式3)
語句3;
…
else if(表達式m)
語句m;
else
語句n;
其語義是:依次判斷表達式的值,當出現某個值為真時,則執行其對應的語句。然后跳到整個if語句之外繼續執行程序。 如果所有的表達式均為假,則執行語句n。然后繼續執行后續程序。 if-else-if語句的執行過程如下圖所示。
【例3-5】
-
#include <stdio.h> int main(void){ char c; printf("input a character: "); c=getchar(); if(c<32) printf("This is a control character\n"); else if(c>='0'&&c<='9') printf("This is a digit\n"); else if(c>='A'&&c<='Z') printf("This is a capital letter\n"); else if(c>='a'&&c<='z') printf("This is a small letter\n"); else printf("This is an other character\n"); return 0; }
本例要求判別鍵盤輸入字符的類別。可以根據輸入字符的ASCII碼來判別類型。由ASCII碼表可知ASCII值小於32的為控制字符。在“0”和“9”之間的為數字,在“A”和“Z”之間為大寫字母, 在“a”和“z”之間為小寫字母,其余則為其它字符。這是一個多分支選擇的問題,用if-else-if語句編程,判斷輸入字符ASCII碼所在的范圍,分別給出不同的輸出。例如輸入為“g”,輸出顯示它為小寫字符。
if語句的嵌套
當if語句中的執行語句又是if語句時,則構成了if 語句嵌套的情形。其一般形式可表示如下:
if(表達式)
if語句;
或者為:
if(表達式)
if語句;
else
if語句;
在嵌套內的if語句可能又是if-else型的,這將會出現多個if和多個else重疊的情況,這時要特別注意if和else的配對問題。例如:
if(表達式1)
if(表達式2)
語句1;
else
語句2;
其中的else究竟是與哪一個if配對呢?應該理解為:
if(表達式1)
if(表達式2)
語句1;
else
語句2;
還是應理解為:
if(表達式1)
if(表達式2)
語句1;
else
語句2;
為了避免這種二義性,C語言規定,else 總是與它前面最近的if配對,因此對上述例子應按前一種情況理解。
【例3-6】
-
#include <stdio.h> int main(void){ int a,b; printf("please input A,B: "); scanf("%d%d",&a,&b); if(a!=b) if(a>b) printf("A>B\n"); else printf("A<B\n"); else printf("A=B\n"); return 0; }
比較兩個數的大小關系。本例中用了if語句的嵌套結構。采用嵌套結構實質上是為了進行多分支選擇,實際上有三種選擇即A>B、A<B或A=B。這種問題用if-else-if語句也可以完成。而且程序更加清晰。因此,在一般情況下較少使用if語句的嵌套結構。以使程序更便於閱讀理解。
3.3 switch語句的使用
C語言還提供了另一種用於多分支選擇的switch語句, 其一般形式為:
switch(表達式){
case 常量表達式1: 語句1;
case 常量表達式2: 語句2;
…
case 常量表達式n: 語句n;
default: 語句n+1;
}
其語義是:計算表達式的值。 並逐個與其后的常量表達式值相比較,當表達式的值與某個常量表達式的值相等時, 即執行其后的語句,然后不再進行判斷,繼續執行后面所有case后的語句。如表達式的值與所有case后的常量表達式均不相同時,則執行default后的語句。
【例3-7】
-
#include <stdio.h> int main(void){ int a; printf("input integer number: "); scanf("%d",&a); switch (a){ case 1:printf("Monday\n"); case 2:printf("Tuesday\n"); case 3:printf("Wednesday\n"); case 4:printf("Thursday\n"); case 5:printf("Friday\n"); case 6:printf("Saturday\n"); case 7:printf("Sunday\n"); default:printf("error\n"); } return 0; }
本程序是要求輸入一個數字,輸出一個英文單詞。但是當輸入3之后,卻執行了case3以及以后的所有語句,輸出了Wednesday 及以后的所有單詞。這當然是不希望的。為什么會出現這種情況呢?這恰恰反應了switch語句的一個特點。在switch語句中,“case 常量表達式”只相當於一個語句標號, 表達式的值和某標號相等則轉向該標號執行,但不能在執行完該標號的語句后自動跳出整個switch 語句,所以出現了繼續執行所有后面case語句的情況。 這是與前面介紹的if語句完全不同的,應特別注意。
為了避免上述情況,C語言還提供了一種break語句,專用於跳出switch語句,break 語句只有關鍵字break,沒有參數。修改例題的程序,在每一case語句之后增加break 語句, 使每一次執行之后均可跳出switch語句,從而避免輸出不應有的結果。
【例3-8】
-
#include <stdio.h> int main(void){ int a; printf("input integer number: "); scanf("%d",&a); switch (a){ case 1:printf("Monday\n"); break; case 2:printf("Tuesday\n"); break; case 3:printf("Wednesday\n"); break; case 4:printf("Thursday\n"); break; case 5:printf("Friday\n"); break; case 6:printf("Saturday\n"); break; case 7:printf("Sunday\n"); break; default:printf("error\n"); } return 0; }
在使用switch語句時還應注意以下幾點:
- 在case后的各常量表達式的值不能相同,否則會出現錯誤。
- 在case后,允許有多個語句,可以不用{}括起來。
- 各case和default子句的先后順序可以變動,而不會影響程序執行結果。
- default子句可以省略不用。
3.4 while語句的用法
while語句的一般形式為:
while(表達式) 語句
其中表達式是循環條件,語句為循環體。
while語句的語義是:計算表達式的值,當值為真(非0)時, 執行循環體語句。
【例3-9】用while語句計算從1加到100的值。
-
#include <stdio.h> int main(void){ int i,sum=0; i=1; while(i<=100){ sum=sum+i; i++; } printf("%d\n",sum); return 0; }
使用while語句應注意以下兩點。
1) while語句中的表達式一般是關系表達或邏輯表達式,只要表達式的值為真(非0)即可繼續循環。
2) 循環體如包括有一個以上的語句,則必須用{}括起來,組成復合語句。
3.5 do-while語句的用法
do-while語句的一般形式為:
do
語句
while(表達式);
這個循環與while循環的不同在於:它先執行循環中的語句,然后再判斷表達式是否為真,如果為真則繼續循環;如果為假,則終止循環。因此,do-while循環至少要執行一次循環語句。
【例3-10】用do-while語句計算從1加到100的值
-
#include <stdio.h> int main(void){ int i,sum=0; i=1; do{ sum=sum+i; i++; } while(i<=100); printf("%d\n",sum); return 0; }
3.6 for語句的用法
在C語言中,for語句使用最為靈活,它完全可以取代 while 語句。它的一般形式為:
for(表達式1; 表達式2; 表達式3) 語句
它的執行過程如下:
- 先求解表達式1。
- 求解表達式2,若其值為真(非0),則執行for語句中指定的內嵌語句,然后執行下面第3)步;若其值為假(0),則結束循環,轉到第5)步。
- 求解表達式3。
- 轉回上面第2)步繼續執行。
- 循環結束,執行for語句下面的一個語句。
for語句最簡單的應用形式也是最容易理解的形式如下:
for(循環變量賦初值; 循環條件; 循環變量增量) 語句
循環變量賦初值總是一個賦值語句,它用來給循環控制變量賦初值;循環條件是一個關系表達式,它決定什么時候退出循環;循環變量增量,定義循環控制變量每循環一次后按什么方式變化。這三個部分之間用分號(;)分開。例如:
-
for( i=1; i<=100; i++ ) sum=sum+i;
先給i賦初值1,判斷i是否小於等於100,若是則執行語句,之后值增加1。再重新判斷,直到條件為假,即i>100時,結束循環。相當於:
-
i=1; while(i<=100){ sum=sum+i; i++; }
對於for循環中語句的一般形式,就是如下的while循環形式:
表達式1;
while(表達式2){
語句
表達式3;
}
使用for語句應該注意:
1) for循環中的“表達式1(循環變量賦初值)”、“表達式2(循環條件)”和“表達式3(循環變量增量)”都是選擇項,即可以缺省,但分號(;)不能缺省。
2) 省略了“表達式1(循環變量賦初值)”,表示不對循環控制變量賦初值。
3) 省略了“表達式2(循環條件)”,則不做其它處理時便成為死循環。例如:
-
for( i=1; ; i++ ) sum=sum+i;
相當於:
-
i=1; while(1){ sum=sum+i; i++; }
4) 省略了“表達式3(循環變量增量)”,則不對循環控制變量進行操作,這時可在語句體中加入修改循環控制變量的語句。例如:
-
for( i=1; i<=100 ; ){ sum=sum+i; i++; }
5) 省略了“表達式1(循環變量賦初值)”和“表達式3(循環變量增量)”。例如:
for( ; i<=100 ; ){ sum=sum+i; i++; }
6) 3個表達式都可以省略。例如:
for( ; ; ) 語句
相當於:
while(1) 語句
7) 表達式1可以是設置循環變量的初值的賦值表達式,也可以是其他表達式。例如:
-
for( sum=0; i<=100; i++ ) sum=sum+i;
8) 表達式1和表達式3可以是一個簡單表達式也可以是逗號表達式。
-
for( sum=0,i=1; i<=100; i++ ) sum=sum+i;
9) 表達式2一般是關系表達式或邏輯表達式,但也可是數值表達式或字符表達式,只要其值非零,就執行循環體。例如:
-
for( i=0; (c=getchar())!=’\n’; i+=c );
循環的嵌套
【例3-11】循環嵌套的應用。
-
#include <stdio.h> int main(void){ int i, j, k; printf("i j k\n"); for (i=0; i<2; i++) for(j=0; j<2; j++) for(k=0; k<2; k++) printf("%d %d %d\n", i, j, k); return 0; }