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 章 编码风格 详细介绍。
使用注释需要注意两点:
注释不能嵌套(Nest)使用,就是说一个注释的文字中不能再出现
/*和*/了,例如/* text1 /* text2 */ text3 */是错误的,编译器只把/* text1 /* text2 */看成注释,后面的text3 */无法解析,因而会报错。有的 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");运行的结果是第一条语句单独打到第一行,后两条语句都打到第二行。为了节省篇幅突出重点,以后的例子通常省略 #include 和 int 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 后面多几个空格也没影响,但是 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 行有错误,也很难判断是哪个语句有错误。所以,好的代码风格要求缩进整齐,每个语句一行,适当留空行。