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行有错误,也很难判断是哪个语句有错误。所以,好的代码风格要求缩进整齐,每个语句一行,适当留空行