C语言学习指南:从规范编程到专业级开发
上QQ阅读APP看书,第一时间看更新

2.2.2 了解分隔符的用法

分隔符(delimiter)是用来将程序分为多个比较小的部分时所使用的字符,这些部分称作标记(token)。在C语言里面,标记是最小的完整要素,它可以是某个单独的字符,也可以是由C语言预先定义的某个字符序列(如int或return),还可以是由我们自己所定义的某个字符序列或某个单词(笔者稍后会讲到)。如果某个标记是由C语言预先定义的,那它就只能依照预定的方式使用,这种标记称为关键字或关键词(keyword)[1]。我们前面已经遇到了三个这样的关键字[2]:include、int与return。大家在本章中还会见到其他几个关键字。

下面我们再把Hello, world!程序展示一次,以便参照:

我们要讲解下面三类分隔符:

单独出现的分隔符:;及<space>(空格)。

成组出现的对称分隔符:<>、()、{}及""。

不对称,然而需要配合着使用的分隔符:#跟与之相配合的<newline>符号,以及//跟与之相配合的<newline>符号。

每种分隔符都有特定的用途。大多数分隔符的用法是唯一的,不会产生歧义,也就是说,你只要看到代码里出现了这样的分隔符,就知道它在这里肯定是用来表达某个意思的,并且只会用来表达这一个意思。还有一些分隔符在不同的语境[3]里面意思可能稍微有点区别。

在刚才那段Hello, world!程序的代码里面,只有两个地方必须添加<space>(空格)分隔符,第一个地方是int与main()之间[4]

还有一个地方是return与它想要返回的那个值(即本例中的0)之间:

在这两个地方,我们必须采用<space>字符把某个关键字或标记与另一个隔开。关键字是编程语言预先定义的一些词汇,具有特殊的含义。这些词汇只能按照预留的方式使用,而不能用来做别的事情。int、main()与return就属于这样的关键字,它们本身也是标记。0与;虽然不是关键字,但同样是标记。前者是一个字面量或字面值(literal value),后者是一个分隔符。这些分隔符(当然也包括<space>)把C语言的代码划分成多个标记,让编译器能够顺利地将每一个标记转译成相应的机器语言。如果某个空格并不用来划分标记,也就是说,无论是否添加这个空格,程序都能正确地编译,那我们就把这样的空格(space)叫作空白(whitespace)。

在刚才说的那三类分隔符里面,第二类是成组出现的对称分隔符。这种分隔符总是成对地使用,例如,<>用来指定某文件的名字,让编译器能够在编译过程中寻找这份文件以完成编译(文件的名字写在<与>之间)。()用来指出它左边的那个标记是一个函数的名字,我们很快会讲到。{}用来表示代码块(block),我们把一条或多条语句放在{与}之间,让它们合起来形成一个单元(稍后会讲到语句这个概念)。还有"",我们把某些字符写在"与"之间,以表示这些字符合起来是一个字符序列,或者说字符串(string)。

最后我们还要说说范例代码的第一行,这一行以#号开头,并以<newline>(换行符)结尾。这两个符号虽然不对称,但必须配合使用。这行代码是一条预处理指令。预处理是编译环节的第一步,在这一步里面,编译器要根据这些预处理指令的要求执行相关的操作。具体到本例来说,它会寻找一个名为stdio.h的文件,并把该文件的内容插入有待编译的文本,换句话说,就是把stdio.h文件的内容包含(include)[5]进来,这就好比我们把该文件中的所有代码都手工录入Hello, world!程序里面。为了命令编译器执行这样的插入或包含操作,我们需要指出那份文件的名字,然后编译器会在预先定义好的一系列路径中寻找该文件。编译器找到并打开该文件,将其读入流中。如果找不到,则报告错误。

#include机制让同一个文件能够方便地为许多份源代码所使用,假如我们不采用该机制,那就得把这份文件的内容手工复制到需要使用该文件的其他源代码文件里面。这会产生一个问题:如果stdio.h的内容发生变化,那么凡是以前直接复制了该文件内容的那些程序代码,就全都要做出相应的修改。反之,如果我们当初是采用#include来包含这个文件的,那就不会有这个问题,因为编译器在编译时会自动引入修改之后的内容,让程序能够根据新版的stdio.h来编译。后面我们会编写一些更复杂也更有意义的范例程序,到那时,我们还会引入许多这样的.h文件,其中有一些来自标准库(Standard Library),还有一些来自我们自制的程序库。

了解分隔符的作用之后,我们试着把那些不充当分隔符的空格、制表符与换行符删掉,让程序代码仅保留必须要有的关键字、标记与分隔符。我们打算把删减之后的代码写在hello_nowhitespace.c文件里面,让这份文件具备如下内容:

你可以创建一个名叫hello_nowhitespace.c的新文件,并将刚才那段代码录入该文件,然后保存、编译并运行程序,最后验证程序输出的结果是否跟删减字符之前的版本相同。注意,不要删掉欢迎词里面的那个空格[6],因为那是展示给用户看的(我们删的是普通的程序代码里面的字符)。

这样编写代码是不是一种值得提倡的做法呢?当然不是。

有人可能觉得这样写能够节省硬盘空间,但实际上节省的幅度很小,而且与删减之前的版本相比,将来如果要修改这段代码,那么必须先花很长时间来理解代码的含义,然后才知道应该怎么改。

编程领域有这样一个基本的现象:阅读程序代码的次数通常是编写或修改代码的几十倍。实际上,每行代码从刚开始写出来到彻底不再使用会阅读至少20次。我们在考虑如何修改或重用某个程序之前,可能必须把这个程序的代码反复读好多遍。如果读代码的是别人,而不是你自己,那么此人可能并不知道你当时写这段代码时是怎么想的。因此,我们一方面固然要遵守编译器的编译规则,要写出完整(complete)且正确的(correct)程序,另一方面还必须保证程序代码是清晰的(clear),让其他人能够读懂。为此,我们不仅要撰写有效的注释,而且必须学会添加适当的空白(whitespace)。

[1] 也叫作保留字或保留词(reserved word)。——译者注

[2] 这里已经根据原书勘误表(https://github.com/PacktPublishing/Learn-C-Programming# errata)做了修改。——译者注

[3] ontext,译文灵活采用语境、情境、上下文等说法来表述这个词。——译者注

[4] 意思是说,不能把int与main()连写成intmain(),也不能在二者之间使用空格之外的其他分隔符。——译者注

[5] 除了译为“包含”,译文还会酌情采用“引入”等说法来对译include一词。——译者注

[6] 也就是“Hello, world!\n”中逗号与world之间的空格。——译者注