考虑写个程序来计算每个 C 语言关键字的出现次数。我们需要一个字符串数组来保存关键字名称,还需要一个整数数组来保存数量。一种可能的方式是使用两个并行的数组,keyword 和 keycount :
char *keyword[NKEYS];
int keycount[NKEYS];
但两个数组是并行的这个事实,暗示了还有一种不同的组织方式,即结构体数组。每个关键字条目是一对:
char *word;
int count;
且有一个数组由这样的对组成。如下结构声明
struct key {
char *word;
int count;
} keytab[NKEYS];
声明了一个结构体类型 key,定义了这种类型的一个数组 keytab,并为它们分配了空间。数组的每个元素都是一个结构体。也可以写成:
struct key {
char *word;
int count;
};
struct key keytab[NKEYS];
由于 keytab 这个结构包含了名称的常量集合,最简单的办法是让它成为一个外部变量,并在定义时将它一劳永逸地初始化。结构体的初始化与之前的类似——在定义后面加上由大括号括起来的初始化列表:
struct key {
char *word;
int count;
} keytab[] = {
"auto", 0,
"break", 0,
"case", 0,
"char", 0
"const", 0,
"continue", 0,
"default", 0,
/* ... */
"unsigned", 0,
"void", 0,
"volatile", 0,
"while", 0
};
初始化表达式是成对出现的,对应结构体的两个成员。更精确的方式是把“每行”或者说每个结构体都用大括号括起来,例如
{"auto", 0},
{"break", 0},
{"case", 0}
...
但当初始化表达式是简单变量或字符串,而且都存在的情况下,内部的括号是不需要的。与简单类型的数组一样,如果存在初始化表达式,而且 [ ] 内为空时,结构体数组 keytab 中的条目个数将会由编译器自动计算。
关键字计算程序从 keytab 的定义开始。主例程通过反复调用 getword 函数来读取输入,getword 每次获取一个单词。每个单词都会在 keytab 中搜索,用的是我们在第三章写的二分搜索函数的字符串版本。表中的关键字必须要以升序存储。
#include
#include
#include
#define MAXWORD 100
int getword(char *, int);
int binserarch(char *, struct key *, int);
/* 计算C关键字数量 */
main()
{
int n;
char word[MAXWORD];
while (getword(word, MAXWORD) != EOF)
if (isalpha(word[0]))
if ((n = binsearch(word, keytab, NKEYS)) >= 0)
keytab[n].count++;
for (n = 0; n < NKEYS; n++)
if (keytab[n].count > 0)
printf("%4d %s\n",
keytab[n].count, keytab[n].count);
return 0;
}
/* binsearch: 在 tab[0]...tab[n-1]中查找word */
int binsearch(char *word, struct key tab[], int n)
{
int cond;
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low + high) / 2;
if (cond = strcmp(word, tab[mid].word) < 0){
low = mid + 1;
else if (cond > 0)
high = mid -1;
else
return mid;
}
return -1;
}
我们待会来展示 getword 函数;现在只要知道每次调用 getword 返回一个单词,该单词会被拷贝函数的第一个参数(字符数组)中,这就足够了。
NKEYS 是 keytab 中关键字的数量。尽管我们可以手动计算,但让机器来做会更简单而且更安全,特别是这个列表还可能会变化。一种可能的方法是用一个空指针来结束初始化列表,然后在 keytab 中循环,直到遇到结尾。
但没必要做那么多,因为数组的大小完全是在编译期间确定的。数组的大小是每个元素的大小乘以元素的个数,因此元素的个数就是
keytab 的大小 / struct key 的大小
C 语言提供了一个编译期的一元操作符 sizeof 用来计算任意对象的大小。表达式
sizeof 对象
或
sizeof 类型名
会得到一个整数,等于指定对象或类型所占空间的字节数。(严格地说,sizeof 产生的是无符号整数,其类型为 size_t,在
在我们的例子中,关键字的数量是数组的大小除以一个元素的大小。使用一个 #define 语句来计算并设置 NKEYS 的值。
#define NKEYS (sizeof keytab / sizeof (struct key))
另一种方法是用数组的大小除以某个特定元素的大小
#define NKEYS (sizeof keytab / sizeof keytab[0])
后者的优势在于,如果数组类型变化,这个定义也不用修改。
sizeof 不能用在 #if 行中,因为预处理器不会解析类型名。但 #define 中的表达式不是由预处理器求值,所以这里的代码是合法的。
现在来看看 getword 函数。我们写了一个比这个程序所需更为通用的 getword 函数,但并不复杂。getword 从 输入中获取下一个“单词”,其中单词是一个由字母开头的字母或数字的字符串,或者单个非空格字符。函数的值是单词的首字母,或者是代表文件结束的EOF,或者是字符本身,如果该字符不是字母的话。
/* getword:从输入中获取下一个单词或字符 */
int getword(char *word, int limit)
{
int c, getch(void);
void ungetch(int);
char *w = word;
while (isspace(c = getch()))
;
if (c != EOF)
*w++ = c;
if (!isalpha(c)) {
*w = '\0';
return c;
}
for ( ; --lim > 0; w++)
if (!isalphanum(*w = getch())) {
ungetch(*w);
break;
}
*w = '\0';
return word[0];
}
getword 使用了我们在第四章写的 getch 和 ungetch 函数。当字母和数字组成的 token 结束时,getword 多读了一个字符。调用 ungetch 把这个字符推回给输入,供下一次调用使用。getword 还使用了 isspace 来跳过空白字符,isalpha 来识别字母,isalphanum来识别字母和数字;这些函数都来自标准头文件
练习6-1、我们这个版本的 getword 没有正确处理下划线,字符串常量,注释,以及预处理控制行。写个更好的版本。