1. 繼續Hello World

第 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 章 編碼風格詳細介紹。

使用註釋需要注意兩點:

  1. 註釋不能嵌套(Nest)使用,就是說一個註釋的文字中不能再出現/*和*/了,例如/* text1 /* text2 */ text3 */是錯誤的,編譯器只把/* text1 /* text2 */看成註釋,後面的 text3 */無法解析,因而會報錯。

  2. 有的C代碼中有類似// comment的註釋,兩個/斜線(Slash)表示從這裡直到該行末尾的所有字元都屬於註釋,這種註釋不能跨行,也不能穿插在一行代碼中間。這是從C++借鑒的語法,在C99中被標準化。

C語言標準

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");

運行的結果是第一條語句單獨打到第一行,後兩條語句都打到第二行。為了節省篇幅突出重點,以後的例子通常省略#includeint main(void) { ... }這些Boilerplate,但讀者在練習時需要加上這些構成一個完整的程序才能編譯通過。C標準規定的轉義字元有以下幾種:

表 2.1. C標準規定的轉義字元

\'單引號'(Single Quote或Apostrophe)
\"雙引號"
\?問號?(Question Mark)
\\反斜線\(Backslash)
\a響鈴(Alert或Bell)
\b退格(Backspace)
\f分頁符(Form Feed)
\n換行(Line Feed)
\r回車(Carriage Return)
\t水平製表符(Horizontal Tab)
\v垂直製表符(Vertical Tab)

如果在字元串字面值中要表示單引號和問號,既可以使用轉義序列\'\?,也可以直接用字元'和?,而要表示\或"則必須使用轉義序列,因為\字元表示轉義而不表示它的字面含義,"表示字元串的界定符而不表示它的字面含義。可見轉義序列有兩個作用:一是把普通字元轉義成特殊字元,例如把字母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後面多幾個空格也沒影響,但是intmain之間至少要有一個空格分隔開:

int main    (void)
{
printf("Hello, world.\n");
return 0;
}

不僅空格和Tab是無關緊要的,換行也是如此,我甚至可以把整個程序寫成一行,但是include必須單獨占一行:

#include<stdio.h>
int main(void){printf("Hello, world.\n");return 0;}

這樣也行,但肯定不是好的代碼風格,去掉縮進已經很影響可讀性了,寫成現在這個樣子可讀性更差。如果編譯器說第2行有錯誤,也很難判斷是哪個語句有錯誤。所以,好的代碼風格要求縮進整齊,每個語句一行,適當留空行