跳转到内容

1. 继续 Hello World

第 4 节“第一个程序” 中,读者应该已经尝试对 Hello world 程序做各种改动看编译运行结果,其中有些改动会导致编译出错,有些改动会影响程序的输出,有些改动则没有任何影响,下面我们总结一下。首先,注释可以跨行,也可以穿插在程序之中,看下面的例子。

例 2.1. 带更多注释的 Hello World

c
#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 CStandard 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)。例如我们写三条打印语句:

c
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 之间至少要有一个空格分隔开:

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

不仅空格和 Tab 是无关紧要的,换行也是如此,我甚至可以把整个程序写成一行,但是 include 必须单独占一行:

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

这样也行,但肯定不是好的代码风格,去掉缩进已经很影响可读性了,写成现在这个样子可读性更差。如果编译器说第 2 行有错误,也很难判断是哪个语句有错误。所以,好的代码风格要求缩进整齐,每个语句一行,适当留空行