K&R6.3节中,有一个匹配关键词的程序,它的作用是对输入的文本进行关键词次数统计。需要注意的是,结构类型的变量 lib 在初始化时,需要对关键词按顺序进行排序,这是因为在后面用到的二分查找中,由于检索的过程中它是调用了strcmp(*, *)函数,如果关键词是按升序进行的,则在if语言中,strcmp返回大小为两者单词从左至右第一个不相同的字母之差,若为负数,则表明输入单词word与lib中前半部分某个单词相匹配,则将lib中间往前一个的单词赋给右端;如果strcmp为正值,则把lib中间往后一个的单词赋给左端;如果strcmp返回零,则表明单词word和lib中的单词相同,返回该单词在lib中的序号。
这里的strcmp位于标准库string.h中,其函数为
int strcmp(char *s, char *t)
{
for( ; *s == *t; s++, t++)
if(*s =='\0')
return 0;
return *s - *t;
}
这个函数中for循环不能用while(*s++ ==* t++)替换,因为当不满足*s++==*t++跳出循环时,指针s和t都向比for循环中多向前移动了一位,因此返回的*s - *t 是不正确的,例如当*s="abc“ *t = "acc"时,返回应该时b与c的差值-1,而不是c与c的差值0。
对于strcmp,有时候希望它只对字符串前n个单词进行比较,则可以这样修改strcmp函数:
int strcmp(char *s, char *t, int n)
{
char *p=t;
while(n--)
t++;
*t = '\0';
t=p;
for(;*s==*t;s++,t++){
if(*s=='\0')
return 0;
}
return *s-*t;
}
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXWORD 100
#define BUFSIZE 10000
#define NKEYS ( sizeof (lib)/sizeof (lib[0]) )
struct key{
char *word;
int count;
} lib[] = {
"1984",0,
"apple",0,
"book",0,
"bottle",0,
"box",0,
"death",0,
"frank",0,
"key",0,
"surface",0,
"swift",0,
"tea",0,
"water",0
};
/* 这里必须按字符顺序排列,因为binsearch中是按strcmp检索的,如果不按顺序排,则检索不到所需关键字的位置 */
int mybinsearch(char *, struct key *, int );
int gainword(char *, int );
int main(int argc, const char *argv[])
{
int n;
char word[MAXWORD];
while (gainword(word, MAXWORD)!=EOF)
if (isalpha(word[0]))
if((n=mybinsearch(word, lib, NKEYS)) >= 0) //>=0说明在binsearch中能找到匹配单词
lib[n].count++;
for(n=0; n<NKEYS; n++)
if(lib[n].count > 0){
printf(" %d",lib[n].count);
printf("\t%s",lib[n].word);
printf("\n");
}
return 0;
}
//结构体的二分查找
int mybinsearch(char *word, struct key v[], int n)
{
int temp;
int low, high, mid;
low = 0;
high = n-1;
while (low <= high){
mid = (low + high)/2;
if ((temp=strcmp(word, v[mid].word)) < 0)
high = mid - 1;
else if (temp > 0)
low = mid + 1;
else
return mid;
}
return -1;
}
//获取字符串
int gainword(char word[], int lim)
{
int c;
int Getch(void);
void Ungetch(int);
char *w=word;
while (isspace(c=Getch()))//如果输入连续多个空格,则c只接受一个空格,并把它保存到w中
;
if (c != EOF)
*w++ = c; //这里w用来存放除EOF之外的任意字符,但是如果该字符不是字母,则在后面追加‘\0’,病返回该字符
if (!isalpha(c)){
*w = '\0';
return c; //返回EOF或者任何非字母的字符
}
for ( ; --lim > 0; w++){
if (!isalnum(*w = Getch())){ //从缓冲区中读入一个字符,该字符如果不是字母或数字,则压回并跳出循环
Ungetch(*w);
break;
}
}
*w = '\0';
return word[0]; //返回单词的第一个字符
}
//Getch和Ungetch函数:用于将从缓冲区输出与压回
char buf[BUFSIZE];
int bufp = 0;
int Getch(void)
{
return (bufp>0) ? buf[--bufp] :getchar();
}
void Ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("Ungetch: too many characters\n");
else
buf[bufp++] = c;
}
如果要将上述程序改为结构体的指针形式,可以修改main函数和mybinsearch函数,mybinsearch返回一个结构体指针,指向所匹配的结构体成员或者NULL,修改后的main函数和mybinsearch函数如下,这里需要注意的是这里的high是=指向的是lib[n]而非lib[n-1](这是可以的,但不能间接引用lib[n]),同时,指针之间不能做加法运算,因此mid=(low+high)/2需要变为mid=low+(high-low)/2。
需要特别注意的一点是,由于lib结构体中不同成员的类型不同,有char和int两种形式,但是结构的长度并不等于所有结构体成员的类型长度之和,这是因为结构体中可能会增加一些”填充物“来满足对象之间的对齐要求。
//主函数
int main(int argc, const char * argv[]){
struct key *p;
char word[MAXWORD];
while (gainword(word, MAXWORD) != EOF)
if(isalpha(word[0]))
if((p=mybinsearch(word, lib, NKEYS)) != NULL)
p -> count++;
printf("%s\t%s\n","TIMES", "WORDS");
for(int i=0;i<15;i++)
printf("-");
printf("\n");
for(p= lib; p<lib+NKEYS; p++)
if(p->count>0)
printf("%4d\t\t%s\n",p->count, p->word);
return 0;
}
//mybinsearch函数
struct key * mybinsearch(char *word, struct key *tab, int n){
int cond;
struct key *low, *high, *mid;
low = tab;
high = low + n;
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;
}