C語言中的符號常量
在結束討論溫度轉換程序前,我們再來看一下符號常量。在程序中使用 300、20 等類似的“幻數”并不是一個好習慣,它們幾乎無法向以后閱讀該程序的人提供什么信息,而且使程序的修改變得更加困難。處理這種幻數的一種方法是賦予它們有意義的名字。#define 指令可以把符號名(或稱為符號常量)定義為一個特定的字符串:
#define 名字 替換文本
在該定義之后,程序中出現的所有在 #define 中定義的名字(既沒有用引號引起來,也不是其它名字的一部分)都將用相應的替換文本替換。其中,名字與普通變量名的形式相同:它們都是以字母打頭的字母和數字序列;替換文本可以是任何字符序列,而不僅限于數字。
在該定義之后,程序中出現的所有在 #define 中定義的名字(既沒有用引號引起來,也不是其它名字的一部分)都將用相應的替換文本替換。其中,名字與普通變量名的形式相同:它們都是以字母打頭的字母和數字序列;替換文本可以是任何字符序列,而不僅限于數字。
#include <stdio.h>#define LOWER 0 /* lower limit of table */#define UPPER 300 /* upper limit */#define STEP 20 /* step size *//* print Fahrenheit-Celsius table */main(){ int fahr; for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) printf("%3d %6.1f/n", fahr, (5.0/9.0)*(fahr-32));}
其中,LOWER、UPPER 與 STEP 都是符號常量,而非變量,因此不需要出現在聲明中。符號常量名通常用大寫字母拼寫,這樣可以很容易與用小寫字母拼寫的變量名相區別。注意,#define 指令行的末尾沒有分號。
變量與算術表達式
我們來看下一個程序,使用公式℃=(5/9)(℉-32)打印下列華氏溫度與攝氏溫度對照表:
0 -1720 -640 460 1580 26100 37120 48140 60160 71180 82200 93220 104240 115260 126280 137300 148
此程序中仍然只包括一個名為 main 的函數定義。它比前面打印“hello, world”的程序長一些,但并不復雜。這個程序中引入了一些新的概念,包括注釋、聲明、變量、算術表達式、循環以及格式化輸出。該程序如下所示:
#include <stdio.h>/* 當 fahr=0,20,… ,300 時,分別打印華氏溫度與攝氏溫度對照表 */main(){ int i; int fahr, celsius; int lower, upper, step; lower = 0; /* 溫度表的下限 */ upper = 300; /* 溫度表的上限 */ step = 20; /* 步長 */ fahr = lower; while (fahr <= upper) { celsius = 5 * (fahr-32) / 9; printf("%d/t%d/n", fahr, celsius); fahr = fahr + step; } scanf("%s", &i);}
其中的一行:
/*當 fahr=0,20,… ,300 時,分別打印華氏溫度與攝氏溫度對照表 */
稱為注釋,此處,它簡單地解釋,該程序是做什么用的。包含在/*與*/之間的字符序列將被編譯器忽略。注釋可以自由地運用在程序中,使得程序更易于理解。程序中允許出現空格、制表符或換行符之處,都可以使用注釋。
在 C 語言中,所有變量都必須先聲明后使用。聲明通常放在函數起始處,在任何可執行語句之前。聲明用于說明變量的屬性,它由一個類型名和一個變量表組成,例如:
int fahr, celsius;int lower, upper, step;
其中,類型 int 表示其后所列變量為整數,與之相對應的,float 表示所列變量為浮點數(即可以帶有小數部分的數)。int 與 float 類型的取值范圍取決于具體的機器。對于 int 類型,通常為 16 位,其取值范圍在-32768~32767 之間,也有用 32 位表示的 int 類型。float 類型通常是 32 位,它至少有 6 位有效數字,取值范圍一般在 10-38~1038 之間。
除 int 與 float 類型之外,C 語高還提供了其它一些基本數據類型,例如:
- char:字符,一個字節
- short:短整型
- long:長整型
- double:雙精度浮點型
這些數據類型對象的大小也取決于具體的機器。另外,還存在這些基本數據類型的數組、結構、聯合,指向這些類型的指針以及返回這些類型值的函教。
在上面的溫度轉換程序中,最開始執行的計算是下列 4 個賦值語句:
lower = 0;upper = 300;step = 20;fahr = lower;
它們為變量設置初值。各條語句均以分號結束。
溫度轉換表中的各行計算方式相同,因此可以用循環語句重復輸出各行。這是 while 循環語句的用途:
while (fahr <= upper) { ...}
while循環語句的執行方式是這樣的:首先測試圓括號中的條件;如果條件為真(fahr<=upper),則執行循環體(括在花括號中的 3 條語句);然后再重新測試圓括號中的條件,如果為真,則再次執行循環體;當圓括號中的條件測試結果為假(fahr>upper)時,循環結束,并繼續執行跟在 while 循環語句之后的下一條語句。在本程序中,循環語句后沒有其它語句,因此整個程序的執行終止。
while 語句的循環體可以是用花括號括起來的一條或多條語句(如上面的溫度轉換程序),也可以是不用花括號包括的單條語句,例如:
while (i < j) i = 2 * i;
在這兩種情況下,我們總是把由 while 控制的語句縮進一個制表位,這樣就可以很容易地看出循環語句中包含哪些語句。這種縮進方式突出了程序的邏輯結構。盡管 C 編譯器并不關心程序的外觀形式,但正確的縮進以及保留適當空格的程序設計風格對程序的易讀性非常重要。我們建議每行只書寫一條語句,并在運算符兩邊各加上一個空格字符,這樣可以使得運算的結合關系更清楚明了。相比而言,花括號的位置就不那么重要了。我們從比較流行的一些風格中選擇了一種,讀者可以選擇適合自己的一種風格,并養成一直使用這種風格的好習慣。
在該程序中,絕大部分工作都是在循環體中完成的。循環體中的賦值語句:
printf(" %d/t%d/n", fahr, celsius);
用于打印兩個整數 fahr 與 celsius 的值,并在兩者之間留一個制表符的空間(/t)。
printf 函數的第一個參數中的各個%分別對應于第二個、第三個、……參數,它們在數目和類型上都必須匹配,否則將出現錯誤的結果。
順便指出,printf 函數并不是 C 語言本身的一部分,C 語言本身并沒有定義輸入/輸出功能。printf 僅僅是標準庫函數中一個有用的函數而已,這些標準序函數在 C 語言程序中通常都可以使用。但是,ANSI 標準定義了 printf 函數的行為,因此,對每個符合該標準的編譯器和庫來說,該函數的屬性都是相同的。
上述的溫度轉換程序存在兩個問題。比較簡單的問題是,由于輸出的數不是右對齊的,所以輸出的結果不是很美觀。這個問題比較容易解決:如果在 printf 語句的第一個參數的%d 中指明打印寬度,則打印的數字會在打印區域內右對齊。例如,可以用語句
printf(" %3d %6d/n", fahr, celsius);
打印 fahr 與 celsius 的值,這樣,fahr 的值占 3 個數字寬,celsius 的值占 6 個數字寬,輸出的結果如下所示:
0 -17 20 -6 40 4 60 15 80 26100 37...
另一個較為嚴重的問題是,由于我們使用的是整型算術運算,因此經計算得到的攝氏溫度值不太精確,例如,與 0℉對應的精確的攝氏溫度應該為-17.8℃,而不是-17℃。為了得到更精確的結果,應該用浮點算術運算代替上面的整型算術運算。這就需要對程序做適當修改。下面是該程序的又一種版本
#include <stdio.h>/* print Fahrenheit-Celsius tablefor fahr = 0, 20, ..., 300; floating-point version */main(){ float fahr, celsius; float lower, upper, step; lower = 0; upper = 300; step = 20; /* lower limit of temperatuire scale */ /* upper limit */ /* step size */ fahr = lower; while (fahr <= upper) { celsius = (5.0/9.0) * (fahr-32.0); printf("%3.0f %6.1f/n", fahr, celsius); fahr = fahr + step; }}
這個程序與前一個程序基本相同,不同的是,它把 fahr 與 celsius 聲明為 float 類型,轉換公式的表述方式也更自然一些。在前一個程序中,之所以不能使用 5 / 9 的形式,是因為按整型除法的計算規則,它們相除并舍位后得到的結果為 0。但是,常數中的小數點表明該常數是一個浮點數,因此,5.0 / 9.0 是兩個浮點數相除,結果將不被舍位。
如果某個算術運算符的所有操作數均為整型,則執行整型運算。但是,如果某個算術運算符有一個浮點型操作數和一個整型操作數,則在開始運算之前整型操作數將會被轉換為浮點型。例如,在表達式 fahr – 32 中,32 在運算過程中將被自動轉換為浮點數再參與運算。不過,即使浮點常量取的是整型值,在書寫時最好還是為它加上一個顯式的小數點,這樣可以強調其浮點性質,便于閱讀。
在這里需要注意,賦值語句 fahr = lower; 與條件測試語句 while (fahr <= upper) 也都是按照這種方式執行的,即在運算之前先把 int 類型的操作數轉換為 float 類型的操作數。
printf 中的轉換說明%3.0f 表明待打印的浮點數(即 fahr)至少占 3 個字符寬,且不帶小數點和小數部分;%6.1f 表明另一個待打印的數(celsius)至少占 6 個字符寬,且小數點后面有 1 位數字。其輸出如下所示:
0 -17.820 -6.740 4.4...
格式說明可以省略寬度與精度,例如,%6f 表示待打印的浮點數至少有 6 個字符寬;%.2f指定待打印的浮點數的小數點后有兩位小數,但寬度沒有限制;%f 則僅僅要求按照浮點數打印該數。
- %d, 按照十進制整型數打印
- %6d, 按照十進制整型數打印,至少 6 個字符寬
- %f, 按照浮點數打印
- %6f, 按照浮點數打印,至少 6 個字符寬
- %.2f, 按照浮點數打印,至少 6 個字符寬
- %6.2f, 按照浮點數打印,至少 6 個字符寬,小數點后有兩位小數
此外,printf 函數還支持下列格式說明:%o 表示八進制數;%x 表示十六進制數;%c表示字符;%s 表示字符串;%%表示百分號(%)本身。