在第 4 節 “第一個程序”中,讀者應該已經嘗試對Hello world程序做各種改動看編譯運行結果,其中有些改動會導致編譯出錯,有些改動會影響程序的輸出,有些改動則沒有任何影響,下面我們總結一下。首先,註釋可以跨行,也可以穿插在程序之中,看下面的例子。
例 2.1. 帶更多註釋的Hello World
#include <stdio.h> /* * comment1 * main: generate some simple output */ int main(void) { printf(/* comment2 */"Hello, world.\n"); /* comment3 */ return 0; }
第一個註釋跨了四行,頭尾兩行是註釋的界定符(Delimiter)/*和*/,中間兩行開頭的*號(Asterisk)並沒有特殊含義,只是為了看起來整齊,這不是語法規則而是大家都遵守的C代碼風格(Coding Style)之一,代碼風格將在第 9 章 編碼風格詳細介紹。
使用註釋需要注意兩點:
C語言的發展歷史大致上分為三個階段:Old Style C、C89和C99。Ken Thompson和Dennis Ritchie最初發明C語言時有很多語法和現在最常用的寫法並不一樣,但為了向後兼容性(Backward Compatibility),這些語法仍然在C89和C99中保留下來了,本書不詳細講Old Style C,但在必要的地方會加以說明。C89是最早的C語言規範,于1989年提出,1990年首先由ANSI(美國國家標準委員會,American National Standards Institute)推出,後來被接納為ISO國際標準(ISO/IEC 9899:1990),因而有時也稱為C90,最經典的C語言教材[K&R]就是基于這個版本的,C89是目前最廣泛採用的C語言標準,大多數編譯器都完全支持C89。C99標準(ISO/IEC 9899:1999)是在1999年推出的,加入了許多新特性,但目前仍沒有得到廣泛支持,在C99推出之後相當長的一段時間裡,連gcc
也沒有完全實現C99的所有特性。C99標準詳見[C99]。本書講C的語法以C99為準,但示例代碼通常只使用C89語法,很少使用C99的新特性。
C標準的目的是為了精確定義C語言,而不是為了教別人怎麼編程,C標準在表達上追求準確和無歧義,卻十分不容易看懂,[Standard C]和[Standard C Library]是對C89及其修訂版本的闡釋(可惜作者沒有隨C99更新這兩本書),比C標準更容易看懂,另外,參考[C99 Rationale]也有助于加深對C標準的理解。
像"Hello, world.\n"
這種由雙引號(Double Quote)引起來的一串字元稱為字元串字面值(String Literal),或者簡稱字元串。注意,程序的運行結果並沒有雙引號,printf
打印出來的只是裡面的一串字元Hello, world.
,因此雙引號是字元串字面值的界定符,夾在雙引號中間的一串字元才是它的內容。注意,打印出來的結果也沒有\n
這兩個字元,這是為什麼呢?在第 2 節 “自然語言和形式語言”中提到過,C語言規定了一些轉義序列(Escape Sequence),這裡的\n
並不表示它的字面意思,也就是說並不表示\和n這兩個字元本身,而是合起來表示一個換行符(Line Feed)。例如我們寫三條打印語句:
printf("Hello, world.\n"); printf("Goodbye, "); printf("cruel world!\n");
運行的結果是第一條語句單獨打到第一行,後兩條語句都打到第二行。為了節省篇幅突出重點,以後的例子通常省略#include
和int main(void) { ... }
這些Boilerplate,但讀者在練習時需要加上這些構成一個完整的程序才能編譯通過。C標準規定的轉義字元有以下幾種:
如果在字元串字面值中要表示單引號和問號,既可以使用轉義序列\'
和\?
,也可以直接用字元'和?,而要表示\或"則必須使用轉義序列,因為\字元表示轉義而不表示它的字面含義,"表示字元串的界定符而不表示它的字面含義。可見轉義序列有兩個作用:一是把普通字元轉義成特殊字元,例如把字母n轉義成換行符;二是把特殊字元轉義成普通字元,例如\和"是特殊字元,轉義後取它的字面值。
C語言規定了幾個控制字元,不能用鍵盤直接輸入,因此採用\加字母的轉義序列表示。\a
是響鈴字元,在字元終端下顯示這個字元的效果是PC喇叭發出嘀的一聲,在圖形界面終端下的效果取決於終端的實現。在終端下顯示\b
和按下退格鍵的效果相同。\f
是分頁符,主要用於控制打印機在打印原始碼時提前分頁,這樣可以避免一個函數跨兩頁打印。\n
和\r
分別表示Line Feed和Carriage Return,這兩個詞來自老式的英文打字機,Line Feed是跳到下一行(進紙,喂紙,有個喂的動作所以是feed),Carriage Return是回到本行開頭(Carriage是卷着紙的軸,隨着打字慢慢左移,打完一行就一下子移回最右邊),如果你看過歐美的老電影應該能想起來這是什麼。用老式打字機打完一行之後需要這麼兩個動作,\r\n
,所以現在Windows上的文本檔案用\r\n
做行分隔符,許多應用層網絡協議(如HTTP)也用\r\n
做行分隔符,而Linux和各種UNIX上的文本檔案只用\n
做行分隔符。在終端下顯示\t
和按下Tab鍵的效果相同,用於在終端下定位表格的下一列,\v
用於在終端下定位表格的下一行。\v
比較少用,\t
比較常用,以後將“水平製表符”簡稱為“製表符”或Tab。請讀者用printf
語句試試這幾個控制字元的作用。
注意"Goodbye, "
末尾的空格,字元串字面值中的空格也算一個字元,也會出現在輸出結果中,而程序中別處的空格和Tab多一個少一個往往是無關緊要的,不會對編譯的結果產生任何影響,例如不縮進不會影響程序的結果,main
後面多幾個空格也沒影響,但是int
和main
之間至少要有一個空格分隔開:
int main (void) { printf("Hello, world.\n"); return 0; }
不僅空格和Tab是無關緊要的,換行也是如此,我甚至可以把整個程序寫成一行,但是include
必須單獨占一行:
#include<stdio.h> int main(void){printf("Hello, world.\n");return 0;}
這樣也行,但肯定不是好的代碼風格,去掉縮進已經很影響可讀性了,寫成現在這個樣子可讀性更差。如果編譯器說第2行有錯誤,也很難判斷是哪個語句有錯誤。所以,好的代碼風格要求縮進整齊,每個語句一行,適當留空行。