6.4 指向结构的指针
为了进一步说明指向结构的指针和结构数组,我们重新编写关键字统计程序,这次采用指针,而不使用数组下标
keytab
的外部声明不需要修改,但 main
和 binsearch
函数必须修改:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define MAXWORD 100
int getword(char *, int);
struct key *binsearch(char *, struct key *, int);
/* count C keywords; pointer version */
main()
{
char word[MAXWORD];
struct key *p;
while (getword(word, MAXWORD) != EOF)
if (isalpha(word[0]))
if ((p = binsearch(word, keytab, NKEYS)) != NULL)
p->count++;
for (p = keytab; p < keytab + NKEYS; p++)
if (p->count > 0)
printf("%4d %s\n", p->count, p->word);
return 0;
}
/* binsearch: find word in tab[0]...tab[n-1] */
struct key *binsearch(char *word, struck key *tab, int n)
{
int cond;
struct key *low = &tab[0];
struct key *high = &tab[n];
struct key *mid;
while (low < high) {
mid = low + (high - low) / 2;
if ((cond = strcmp(word, mid->word)) < 0)
high = mid;
else if (cond > 0)
low = mid + 1;
else
return mid;
}
return NULL;
}
这里需要注意几点
首先,binsearch
函数在声明中必须表明:它返回的值类型是一个指向 struct key
类型的指针,而非整型
这在函数原型及 binsearch
函数中都要声明
如果 binsearch
找到与输入单词匹配的数组元素,它将返回一个指向该元素的指针,否则返回 NULL
其次,keytab
的元素在这里是通过指针访问的,这就需要对 binsearch
做较大的修改
low
和 high
的初值分别是指向表头元素的指针和指向表尾元素后面的一个元素的指针
这样就无法简单地通过 mid = (low + high) / 2 /* WRONG */
表达式计算中间元素的位置
这是因为,两个指针之间的加法运算是非法的
但是,指针的减法运算却是合法的,high - low
的值就是数组元素的个数
因此,可以用 mid = low + (high - low) / 2
将 mid
设置为指向位于 high
和 low
之间的中间元素的指针
对算法的最重要修改在于,要确保不会生成非法的指针,或者是试图访问数组范围之外的元素
问题在于 &tab[-1]
和 &tab[n]
都超出了数组 tab
的范围
前者是绝对非法的,而对后者的间接引用也是非法的
但是,C 语言的定义保证数组末尾之后的第一个元素(即 &tab[n]
)的指针算术运算可以正确执行
主程序 main
中有下列语句:
for (p = keytab; p < keytab + NKEYS; p++)
如果 p
是指向结构的指针,则对 p
的算术运算需要考虑结构的长度
所以,表达式 p++
执行时,将在 p
的基础上加上一个正确的值,以确保得到结构数组的下一个元素
这样,上述测试条件便可以保证循环正确终止
但是,千万不要认为结构的长度等于各成员长度的和
因为不同的对象有不同的对齐要求,所以,结构中可能会出现未命名的空穴(hole)
例如,假设 char
类型占用一个字节,int
类型占用 4 个字节,则下列结构:
struct {
char c;
int i;
};
可能需要 8 个字节的存储空间,而不是 5 个字节
使用 sizeof
运算符可以返回正确的对象长度
最后,说明一点程序的格式问题:当函数的返回值类型比较复杂时(如结构指针):
struct key *binsearch(char *word, struct key *tab, int n)
很难看出函数名,也不太容易使用文本编辑器找到函数名
可以采用另一种格式书写上述语句:
struct key *
binsearch(char *word, struct key *tab, int n)
具体采用哪种写法属于个人的习惯问题,可以选择自己喜欢的方式并始终保持自己的风格