当前位置:嗨网首页>书籍在线阅读

14-一个统计单词的程序

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

7.4 一个统计单词的程序

现在,我们可以编写一个统计单词数量的程序(即,该程序读取并报告单词的数量)。该程序还可以计算字符数和行数。先来看看编写这样的程序要涉及那些内容。

首先,该程序要逐个字符读取输入,知道何时停止读取。然后,该程序能识别并计算这些内容:字符、行数和单词。据此我们编写的伪代码如下:

读取一个字符

当有更多输入时

递增字符计数

如果读完一行,递增行数计数

如果读完一个单词,递增单词计数

读取下一个字符

前面有一个输入循环的模型:

while ((ch = getchar()) != STOP)
{
     ...
}

这里, STOP 表示能标识输入末尾的某个值。以前我们用过换行符和句点标记输入的末尾,但是对于一个通用的统计单词程序,它们都不合适。我们暂时选用一个文本中不常用的字符(如,|)作为输入的末尾标记。第8章中会介绍更好的方法,以便程序既能处理文本文件,又能处理键盘输入。

现在,我们考虑循环体。因为该程序使用 getchar() 进行输入,所以每次迭代都要通过递增计数器来计数。为了统计行数,程序要能检查换行字符。如果输入的字符是一个换行符,该程序应该递增行数计数器。这里要注意 STOP 字符位于一行的中间的情况。是否递增行数计数?我们可以作为特殊行计数,即没有换行符的一行字符。可以通过记录之前读取的字符识别这种情况,即如果读取时发现 STOP 字符的上一个字符不是换行符,那么这行就是特殊行。

最棘手的部分是识别单词。首先,必须定义什么是该程序识别的单词。我们用一个相对简单的方法,把一个单词定义为一个不含空白(即,没有空格、制表符或换行符)的字符序列。因此,“ glymxck ”和“ r2d2 ”都算是一个单词。程序读取的第1个非空白字符即是一个单词的开始,当读到空白字符时结束。判断非空白字符最直接的测试表达式是:

c != ' ' && c != '\n' && c != '\t' /* 如果c不是空白字符,该表达式为真*/

检测空白字符最直接的测试表达式是:

c == ' ' || c == '\n' || c == '\t' /*如果c是空白字符,该表达式为真*/

然而,使用 ctype.h 头文件中的函数 isspace() 更简单,如果该函数的参数是空白字符,则返回真。所以,如果 c 是空白字符, isspace(c) 为真;如果 c 不是空白字符, !isspace(c) 为真。

要查找一个单词里是否有某个字符,可以在程序读入单词的首字符时把一个标记(名为 inword )设置为 1 。也可以在此时递增单词计数。然后,只要 inword1 (或 true ),后续的非空白字符都不记为单词的开始。下一个空白字符,必须重置标记为 0 (或 false ),然后程序就准备好读取下一个单词。我们把以上分析写成伪代码:

如果 c 不是空白字符,且 inword 为假

设置 inword 为真,并给单词计数

如果 c 是空白字符,且 inword 为真

设置 inword 为假

这种方法在读到每个单词的开头时把 inword 设置为 1 (真),在读到每个单词的末尾时把 inword 设置为 0 (假)。只有在标记从 0 设置为 1 时,递增单词计数。如果能使用 _Bool 类型,可以在程序中包含 stdbool.h 头文件,把 inword 的类型设置为 bool ,其值用 truefalse 表示。如果编译器不支持这种用法,就把 inword 的类型设置为 int ,其值用 10 表示。

如果使用布尔类型的变量,通常习惯把变量自身作为测试条件。如下所示:

if (inword) 代替 if (inword == true)

if (!inword) 代替 if (inword == false)

可以这样做的原因是,如果 inwordtrue ,则表达式 inword == truetrue ;如果 inwordfalse ,则表达式 inword == truefalse 。所以,还不如直接用 inword 作为测试条件。类似地, !inword 的值与表达式 inword == false 的值相同(非真即 false ,非假即 true )。

程序清单7.7把上述思路(识别行、识别不完整的行和识别单词)翻译了成C代码。

程序清单7.7  wordcnt.c 程序

// wordcnt.c -- 统计字符数、单词数、行数
#include <stdio.h>
#include <ctype.h>           // 为isspace()函数提供原型
#include <stdbool.h>         // 为bool、true、false提供定义
#define STOP '|'
int main(void)
{
     char c;                 // 读入字符
     char prev;              // 读入的前一个字符
     long n_chars = 0L;      // 字符数
     int n_lines = 0;        // 行数
     int n_words = 0;        // 单词数
     int p_lines = 0;        // 不完整的行数
     bool inword = false;    // 如果c在单词中,inword 等于 true
     printf("Enter text to be analyzed (| to terminate):\n");
     prev = '\n';            // 用于识别完整的行
     while ((c = getchar()) != STOP)
     {
          n_chars++;         // 统计字符
          if (c == '\n')
               n_lines++;    // 统计行
          if (!isspace(c) && !inword)
          {
               inword = true; // 开始一个新的单词
               n_words++;    // 统计单词
          }
          if (isspace(c) && inword)
               inword = false;    // 达到单词的末尾
          prev = c;            // 保存字符的值
     }
     if (prev != '\n')
          p_lines = 1;
     printf("characters = %ld, words = %d, lines = %d, ",
               n_chars, n_words, n_lines);
     printf("partial lines = %d\n", p_lines);
     return 0;
}

下面是运行该程序后的一个输出示例:

Enter text to be analyzed (| to terminate):
Reason is a
powerful servant but
an inadequate master.
|
characters = 55, words = 9, lines = 3, partial lines = 0

该程序使用逻辑运算符把伪代码翻译成C代码。例如,把下面的伪代码:

如果 c 不是空白字符,且 inword 为假

翻译成如下C代码:

if (!isspace(c) &&!inword)

再次提醒读者注意, !inwordinword == false 等价。上面的整个测试条件比单独判断每个空白字符的可读性高:

if (c != ' ' && c != '\n' && c != '\t' && !inword)

上面的两种形式都表示“如果 c 不是空白字符,且如果 c 不在单词里”。如果两个条件都满足,则一定是一个新单词的开头,所以要递增 n_words 。如果位于单词中,满足第1个条件,但是 inwordtrue ,就不递增 n_word 。当读到下一个空白字符时, inword 被再次设置为 false 。检查代码,查看一下如果单词之间有多个空格时,程序是否能正常运行。第8章讲解了如何修正这个程序,让该程序能统计文件中的单词量。