预计阅读本页时间:-
现在我们已经有工具来编写一个统计字数的程序了。统计字数的程序读取输入的字符并报告其中的单词个数。处理时也可以统计字符个数和行数。我们来看看这样一个程序包含哪些步骤。
首先,这个程序应该逐个读取字符,并且应该有些方法判断何时停止;第二,它应该能够识别并统计下列单位:字符、行和单词。下面是伪代码描述:
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
前面已经有输入循环的模型了:
这里,STOP代表通知输入结束的ch取值。前面的示例程序已经使用了换行符和句号来用于该目的,但对于一个通用的单词统计程序这两个都不合适。现在我们暂且选择一个在文本中不常见的字符()。在第8章“字符输入/输出和输入确认”中,我们将会介绍一个更好的解决方案,以使程序既能处理文本文件又能处理键盘输入。
现在来考虑一下循环体。因为程序使用getchar()来输入字符,所以可以在每个循环周期通过递增一个计数器的值来统计字符。为了统计行数,程序可以检查换行符。如果字符是换行符,程序就递增行数计数器的值。有个问题是如果STOP字符出现在一行的中间该怎么办。行数计数器应不应该增加呢?一种做法是将它作为一个不完整行统计,也就是说,该行有字符而没有换行符。可以通过追踪前一个字符来识别这种情况。如果STOP之前所读入的最后一个字符不是换行符,就计为一个不完整行。
最棘手的部分是识别单词。首先,必须明确定义一个单词意味着什么。让我们以一个相对简单的方法将一个单词定义为不包含空白字符(也就是说,没有空格、制表符或换行符)的一系列字符。因此,“glymxck”和“r2d2”是单词。一个单词以程序首次遇到非空白字符开始,在下一个空白字符出现时结束。检测非空白字符最简单明了的判断表达式是这样的:
检测空白字符最简单明了的判断是:
但是,使用ctype.h中的isspace()函数会更简单。如果该函数的参数是空白字符,它就返回真。因此如果c是空白字符,isspace(c)为真;而如果c不是空白字符,!isspace(c)为真。
为了知道一个字符是不是在某单词里,可以在读入一个单词的首字符时把一个标志(命名为inword)设置为1。也可以在此处递增单词计数。
然后,只要inword保持为1(或真),后续的非空白字符就不标记为一个单词的开始。到出现下一个空白字符时,必须将此标志重置为0(或假),并且程序准备搜索下一个单词。转换为伪代码是这样的:
这种方法在每个单词开始时将inword设为1(真),而在每个单词结束时将其设为0(假)。仅在该标志从0变为1时对单词计数。如果在您的系统上可以使用_Bool型变量,可以包含stdbool.h头文件并用bool作为inword的类型,取值分别为true和false。否则,就使用int类型,取值为0和1。
如果使用布尔型变量,通常的习惯是用变量自身的值作为判断条件。也就是说,用:
来代替:
并且用:
来代替:
依据是,如果inword为true,则表达式inword = =true结果为true;而如果inword为false,则该表达式也为false。因此倒不如只用inword作为判断条件。与之类似,!inword与表达式inword==false值相同(非真即false,非假即true)。
程序清单7.7将这些思想(识别行,识别不完整行以及识别单词)翻译为C代码。
程序清单7.7 wordcnt.c程序
下面是一个运行示例:
程序用逻辑运算符把伪代码翻译为C代码。例如:
被翻译为如下代码:
注意,!inword等价于inword= =false。整个判断条件当然比单独判断每个空白字符要更可读:
上面的任何一种形式都表示:“如果c不是空白字符,并且如果c不处于一个单词里”。假如两个条件都满足,那么一定是新单词的开始,所以递增n_words。如果处于一个单词的中间,那么第一个条件是满足的,但是inword为true,所以n_words不递增。
当遇到下一个空白字符时,将inword再次设为false。检查一下代码,看看当两个单词之间有数个空格时,程序是否正确。第8章说明了怎样修改这个程序以统计一个文件中的单词。