6.1 结构的基本知识
//①定义结构struct point.实际意义:定义一个点的坐标。
struct point
{
int x;
int y;
}x,y; //等价于int x,y;
//声明struct point类型的结构变量maxpt(区别定义结构point)
//定义了一个struct point类型的变量maxpt,并将其初始化
//实际意义:定义了一个struct point点,点名称为maxpt,并将其坐标初始化为(320,200)
struct point maxpt = { 320,200 };
//声明struct point类型的结构变量pt(区别定义结构point)
//实际意义:定义了一个点pt
struct point pt;
printf("%d,%d", pt.x, pt.y); //以“结构名.成员”的方式引用特定结构种的特定成员
/* 结构可以嵌套。
②定义结构rect.且该结构rect中嵌套了结构point——结构rect中包含了两个struct point类型的成员pt1、pt2.
struct point类型的变量pt1、pt2中,亦分别包含两个结构成员(坐标)x,y.
实际意义:用两个点pt1、pt2,来定义一个矩形struct rect. */
struct rect{
struct point pt1;
struct point pt2;
};
//声明struct rect类型的结构变量screen(区别定义结构rect)
//实际意义:定义了一个struct rect矩形,矩形名称为screen.
struct rect screen;
/* 结构可以连续引用。
当引用struct rect类型的结构变量screen中的(struct point类型的变量)成员pt1中的int整形成员x时,
即定义矩形struct rect的其中一个点pt1的横坐标x,用如下表达式 */
screen.pt1.x;
6.2 结构与函数
函数makepoint
//定义函数makepoint,它带有两个整形参数,并且返回struct point类型值temp
struct point makepoint(int x, int y)
{
struct point temp; //声明struct point类型的结构变量temp(区别定义结构point)
temp.x = x;
temp.y = y;
return temp;
}
求矩形重心的坐标
求矩形screen两对角线交点middle的坐标,即矩形screen重心middle的坐标。
#include <stdio.h>
#define XMAX 100
#define YMAX 100
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
struct point makepoint(int, int);
int main()
{
struct point pt1 = {0,0}; //初始化struct point类型的变量pt1为{0,0}
struct point pt2 = {5,5}; //初始化struct point类型的变量pt2为{5,5}
struct rect screen = {pt1,pt2}; //初始化struct rect类型的变量screen为{pt1,pt2},即{{0,0},{5,5}}(矩形的两点)
struct point middle = {0,0}; //初始化struct point类型的变量middle为{0,0}
screen.pt1 = makepoint(0,0);
screen.pt2 = makepoint(XMAX,YMAX);
middle = makepoint((screen.pt1.x + screen.pt2.x) / 2,
(screen.pt1.y + screen.pt2.y) / 2);
printf("%d %d", middle.x, middle.y);
return 0;
}
struct point makepoint(int x, int y)
{
struct point temp = {0,0};
temp.x = x;
temp.y = y;
return temp;
}
将两个点的坐标相加
/* 将两个点的坐标相加 */
#include <stdio.h>
struct point {
int x;
int y;
};
struct point addpoint(struct point p1, struct point p2);
int main()
{
struct point p1 = { 0,0 };
struct point p2 = { 15,15 };
struct point p1_add = { 0,0 };
p1_add = addpoint(p1, p2);
printf("%d %d", p1_add.x, p1_add.y);
return 0;
}
struct point addpoint(struct point p1, struct point p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
};
判断点是否在矩形内部
判断一个点是否在给定的矩形内部,如果在则返回1,如果不在则返回0.
#include <stdio.h>
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
int ptinrect(struct point p, struct rect r);
int main()
{
struct point pt = {0,0};
struct point p1 = {0,0};
struct point p2 = {5,5};
struct rect screen = { p1,p2 };
int n = 0;
n = ptinrect(pt, screen);
printf("%d", n);
return 0;
}
/*函数ptinrect:判断一个点是否在给定的矩形内部 */
int ptinrect(struct point p, struct rect r)
{
return p.x >= r.pt1.x && p.x < r.pt2.x && p.y >= r.pt1.y && p.y < r.pt2.y;
}
将矩形坐标规范化
将矩形坐标规范化,pt1的坐标小于pt2的坐标。
/* 将矩形坐标规范化,pt1的坐标小于pt2的坐标 */
#include <stdio.h>
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
struct rect canonrect(struct rect r);
int main()
{
struct point pt1 = { 5,7 }; //赋值
struct point pt2 = { 2,1 }; //赋值
struct rect screen = { pt1,pt2 }; //赋值
screen = canonrect(screen);
printf("(%d,%d) (%d,%d)", screen.pt1.x, screen.pt1.y, screen.pt2.x, screen.pt2.y);
return 0;
}
struct rect canonrect(struct rect r)
{
struct rect temp;
temp.pt1.x = min(r.pt1.x, r.pt2.x);
temp.pt1.y = min(r.pt1.y, r.pt2.y);
temp.pt2.x = max(r.pt1.x, r.pt2.x);
temp.pt2.y = max(r.pt1.y, r.pt2.y);
return temp;
}
结构指针
#include <stdio.h>
int main()
{
struct p
{
int len;
const char* str;
}p1 = { 0,"b"};
struct p *pp = &p1;
printf("%p\n%p\n", pp->str, pp); //输出结构成员str,指针pp指向的变量的地址
printf("%c %p\n%c %p", *pp->str++, pp->str, *pp++->str, pp);
//*pp->str++,*((pp->str)++),引用str,读取指针str指向的值后,将str指向下一个const char,并输出此时str指向的指针变量的地址
//*pp++->str,*((pp++)->str),引用str,读取指针str指向的值后,将指针pp指向下一个结构p,并输出此时pp指向的指针变量的地址
return 0;
}
6.3 结构数组
普通的数组
char* keyword[NKEYS];
int keycount[NKEYS];
结构数组
struct key {
char* word;
int count;
}keytab[] = {
{"auto",0},
{"break",0},
{"case",0}
};
getword函数
统计输入中C语言关键字的出现次数
#include <stdio.h>
#include <ctype.h>
#include <string.h>
struct key {
const char* word;
int count;
}keytab[] = {
{"auto",0},
{"break",0},
{"case",0}
};
#define MAXWORD 100
#define NKEYS (sizeof keytab / sizeof(struct key))
//#define NKEYS (sizeof keytab / sizeof keytab[0]) 项数
int binsearch(char*, struct key*, int);
int getword(char*, int);
/* 统计输入中C语言关键字的出现次数 */
int main()
{
int n;
char word[MAXWORD];
while (getword(word, MAXWORD) != EOF)
//word是一个char型数组,函数getword的第一个形参,是指针类型。是因为数组名也是一个指针。
{
if (isalpha(word[0]))
{
if ((n = binsearch(word, keytab, NKEYS)) >= 0)
keytab[n].count++;
}
}
for (n = 0; n < NKEYS; n++) //这里的n与上面的n没有关系,这里是从0开始的坐标
{
if (keytab[n].count > 0)
printf("%4d %s\n", keytab[n].count, keytab[n].word);
}
return 0;
}
/* binsearch函数:在tab[0]到tab[n-1]中查找单词 */
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)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return mid;
}
return -1;
}
int getch(void);
void ungetch(int);
//区别P106 分配n个字符的存储空间的alloc函数 与 释放n个字符的存储空间的afree函数
#define BUFSIZE 100
static char buf[BUFSIZE];
static 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;
}
/* getword函数:从输入中读取下一个单词或字符,并将其复制到名字为该函数的第一个参数的数组中 */
int getword(char* word, int lim)
//其第一个形参就是指针(写成了这种形式,实参才是数组word[MAXWORD]数组。)
{
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 (!isalnum(*w = getch())) //是*w不再是c
{
ungetch(*w);
break;
}
}
*w = '\0';
return word[0];
}
练习6-1
上述 getword 函数不能正确处理下划线、字符串常量、注释及预处理器控制指令。请编写一个更完善的 getword 函数。
【用上面的程序也同样可以“跳过”非字母的字符,但加上这部分处理,使代码更严谨。
】
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define MAXWORD 100
#define BUFSIZE 100
#define NKEYS (sizeof (keytab) / sizeof(keytab[0]))
char buf[BUFSIZE];
int bufp = 0;
int comment(void);
int getch(void);
int getword(char*, int);
void ungetch(int c);
int binsearch(char*, struct key*, int);
struct key {
const char* word;
int count;
};
/* 计数C关键字 */
int main(void)
{
int n;
char word[MAXWORD];
struct key keytab[] = {
{"break", 0 },
{"case", 0 },
{"char", 0 },
{"const", 0 },
{"continue", 0 },
{"else",0},
{"if",0},
{"struct",0}
};
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].word);
return 0;
}
/* binsearch: 找到词标签[0]…标签(n - 1) */
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)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return mid;
}
return -1;
}
/* getword: 从输入得到下一个字或词 */
int getword(char* word, int lim)
{
int getchar(void);
void ungetch(int);
int t, tm;
char* w = word;
while (isspace(t = getchar()))
;
if (t != EOF)
*w++ = t;
if (isalpha(t) || t == '_' || t == '#')/*这块的代码经测试没有问题*/
{
for (; --lim > 0; w++)
{
if (!isalnum(*w = getchar()) && *w != '_')
{
ungetch(*w);
break;
}
}
}
else if (t == '\'' || t == '"')/*经测试此函数也没什么问题*/
{
for (; --lim > 0; w++)
if ((*w = getchar()) == '\\')
*++w = getchar();
else if (*w == t)/*与自己重复时,注定常量输入结束*/
{
w++;
break;
}
else if (*w == EOF)/*正确处理下划线和预处理器控制符时都没有这么判断,为什么这里会有这个判断*/
break;
}
else if (t == '/')/*能够正确处理注释,且跳过注释类容,只返回一个字符,经测试无误*/
{
if ((tm = getchar()) == '*')
{
t = comment();
}
}
*w = '\0';
return t;
}
int comment(void)
{
char temp; // 定义一个字符变量temp,用于存储读取到的字符
while ((temp = getchar()) != EOF) // 使用while循环不断从输入流(如标准输入或文件)中读取字符,直到遇到文件结束符EOF
{
if (temp == '*') // 如果读取到的字符是星号(*)
{
if ((temp = getchar()) == '/') // 继续读取下一个字符,如果这个字符是正斜杠(/)
break; // 那么发现了一个注释开始标志(/*),跳出内层if语句,并继续执行下一次循环(即跳过整个注释内容)
else
ungetch(temp); // 如果读取到的不是正斜杠,则将当前字符放回输入流,以便下次循环能再次处理它(可能是错误的注释开始符号,或者在非注释文本中的星号)
}
}
return temp; // 函数最后返回的是最后一次读取的字符
}
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch:too many characters");
else buf[bufp++] = c;
}
6.4 指向结构的指针
#include <stdio.h>
#include <ctype.h>
#include <string.h>
struct key
{
const char* word;
int count;
}keytab[] =
{
{"auto",0},
{"break",0},
{"case",0}
};
#define MAXWORD 100
#define NKEYS (sizeof keytab / sizeof(struct key))
#define BUFSIZE 100
char buf[BUFSIZE];
int bufp = 0;
int comment(void);
int getch(void);
void ungetch(int c);
int getword(char*, int);
struct key* binsearch(char*, struct key*, int);
/* 统计关键字的出现次数:采用指针方式实现的版本*/
int main()
{
char word[MAXWORD];
struct key* p = &(keytab[3]);
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++)
//keytab[]的数据类型是struct key,而p的数据类型是指针,因此不能将p直接 p = keytab[0]
{
if (p->count > 0)
printf("%4d %s\n", p->count, p->word);
}
return 0;
}
/* binsearch函数:在tab[0]...tab[n-1]中查找与读入单词匹配的元素*/
struct key *binsearch(char* word, struct key tab[], int n)
{
struct key* low = &tab[0];
struct key* high = &tab[n];
struct key* mid ;
int count = 0;
int mid1 = 0;
while (low < high)
{
mid1 = int((high - low) / 2); //计算出小数后,强制类型转换为整数
mid = low + mid1; //保证指针+整数
if ((count = strcmp(word, mid->word)) > 0)
low = mid + 1;
else if (count < 0)
high = mid ;
else
return mid;
}
return NULL;
}
/* getword: 从输入得到下一个字或词 */
int getword(char* word, int lim)
{
int getchar(void);
void ungetch(int);
int t, tm;
char* w = word;
while (isspace(t = getchar()))
;
if (t != EOF)
*w++ = t;
if (isalpha(t) || t == '_' || t == '#')/*这块的代码经测试没有问题*/
{
for (; --lim > 0; w++)
{
if (!isalnum(*w = getchar()) && *w != '_')
{
ungetch(*w);
break;
}
}
}
else if (t == '\'' || t == '"')/*经测试此函数也没什么问题*/
{
for (; --lim > 0; w++)
if ((*w = getchar()) == '\\')
*++w = getchar();
else if (*w == t)/*与自己重复时,注定常量输入结束*/
{
w++;
break;
}
else if (*w == EOF)/*正确处理下划线和预处理器控制符时都没有这么判断,为什么这里会有这个判断*/
break;
}
else if (t == '/')/*能够正确处理注释,且跳过注释类容,只返回一个字符,经测试无误*/
{
if ((tm = getchar()) == '*')
{
t = comment();
}
}
*w = '\0';
return t;
}
int comment(void)
{
char temp;
while ((temp = getchar()) != EOF)
if (temp == '*')
if ((temp = getchar()) == '/')
break;
else
ungetch(temp);
return temp;
}
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch:too many characters");
else buf[bufp++] = c;
}
6.5 自引用结构
#include <stdio.h> //main函数、printf函数、getchar()函数
#include <stdlib.h> //malloc函数
#include <string.h> //strcpy_s函数、strcmp函数
#include <ctype.h> //isalnum函数、isalpha函数
#define MAXWORD 100
struct tnode {
char* word;
int count;
struct tnode* left;
struct tnode* right;
};
struct tnode* addtree(struct tnode*, char*);
void treeprint(struct tnode*);
int getword(char*, int);
struct tnode* talloc(void);
char* strdup(char*);
int main()
{
struct tnode* root;
char word[MAXWORD];
root = NULL;
while (getword(word, MAXWORD) != EOF)
{
if (isalpha(word[0]))
{
root = addtree(root, word);
}
}
treeprint(root);
return 0;
}
int getch(void);
void ungetch(int c);
#define BUFSIZE 1000
static char buf[BUFSIZE];
static int bufp = 0;
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch : too many characters");
else
{
buf[bufp++] = c;
}
}
/* addtree函数:在p的位置或p的下方增加一个w节点 */
struct tnode* addtree(struct tnode* p, char* w)
{
int cond;
if (p == NULL)
{
p = talloc();
p->word = strdup(w);
p->count = 1;
p->left = p->right = NULL;
}
else if ((cond = strcmp(w, p->word)) == 0)
p->count++;
else if (cond < 0)
p->left = addtree(p->left, w);
else
p->right = addtree(p->right, w);
return p;
}
void treeprint(struct tnode* p)
{
if (p != NULL)
{
treeprint(p->left);
printf("%4d %s\n", p->count, p->word);
treeprint(p->right);
}
}
int getword(char* word, int lim)
{
int c = 0;
char* w = word;
while (isspace(c = getch()))
;
if (c != EOF)
*w++ = c;
if (!isalpha(c))
{
*w = '\0';
return c;
}
for (; --lim > 0; w++)
{
if (!isalnum(*w = getch()))
{
ungetch(*w);
break;
}
}
*w = '\0';
return word[0];
}
/* talloc函数:创建一个tnode */
struct tnode* talloc(void)
{
return (struct tnode*)malloc(sizeof(struct tnode));
}
char* strdup(char* s)
{
char* p;
p = (char*)malloc(strlen(s) + 1);
if (p != NULL)
{
strcpy_s(p,sizeof(p), s);
}
return p;
}
//now is the time for all good men to come to the aid of thrie party
练习 6-4
编写一个程序,根据单词的出现频率按降序打印输入的各个不同单词,并在 每个单词的前面标上它的出现次数
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
struct tnode
{
char* word;
int count;
struct tnode* left;
struct tnode* right;
};
#define MAXWORD 100
#define BUFSIZE 100
#define MAXSUPPORTEDWORDS 10000
struct tnode* addtree(struct tnode*, char*);
void treeprint(void);
int getword(char*, int);
struct tnode* talloc(void);
char* mystrdup(char*);
int getch(void);
void ungetch(int c);
void quick_sort(struct tnode* a[], int low, int high);
static char buf[BUFSIZE];
static int bufp = 0;
struct tnode* treeNodes[MAXSUPPORTEDWORDS] = { 0 };
int treeP = 0;
int main()
{
struct tnode* root;
char word[MAXWORD];
root = NULL;
while (getword(word, MAXWORD) != EOF)
{
if (isalpha(word[0]) || word[0] == '_')
{
root = addtree(root, word);
}
}
quick_sort(treeNodes, 0, treeP - 1);
treeprint();
return 0;
}
struct tnode* addtree(struct tnode* p, char* w)
{
int result;
if (p == NULL) // a new word has arrived
{
p = talloc();
/* make a new node(为新节点申请相应的储存空间),
并将指向该储存空间的位置处的指针(即函数talloc作用的结果)赋给指针p ,即使指针p指向了新的节点 */
p->word = mystrdup(w);
p->count = 1;
p->left = p->right = NULL;
treeNodes[treeP++] = p; //将指向该新节点的指针p,写入到指向该二叉树各节点的指针的指针数组中
}
else if ((result = strcmp(w, p->word)) == 0)
p->count++; // repeated word
else if (result < 0) // less than into left subtree
p->left = addtree(p->left, w);
else // greater than into right subtree
p->right = addtree(p->right, w);
return p;
}
struct tnode* talloc(void)
{
return (struct tnode*)malloc(sizeof(struct tnode));
}
char* mystrdup(char* s) // make a duplicate of s
{
char* p = (char*)malloc(strlen(s) + 1); // + 1 for '\0'
if (p != NULL)
strcpy_s(p, sizeof(s) + 1, s);
return p;
}
// get next word or character from input
int getword(char* word, int lim)
{
int c;
char* w = word;
while ((c = tolower(getch())) == '\t' || c == ' ')
;
if (c != EOF)
*w++ = c;
if (!isalpha(c) && c != '_')
{
*w = '\0';
return c;
}
for (; --lim > 0; w++)
if (!isalnum(*w = tolower(getch())) && *w != '_')
{
ungetch(*w);
break;
}
*w = '\0';
return word[0];
}
// get a (possibly pushed back) character
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
// push character back on input
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
/* 将指向二叉树各节点的指针组成的数组 struct tnode* treeNodes[MAXSUPPORTEDWORDS] 作为实参传递给该函数后,
其处理结果是按各节点出现的频率升序,交换每个指针在指针数组中的位置。
即最终在指针数组treeNodes[]中,第一个指针元素指向出现频率最少的节点(该节点在原二叉树中的位置并没有变),
最后一个指针元素指向出现频率最多的节点。 */
void quick_sort(struct tnode* a[], int low, int high)
{
int i = low; //第一位
int j = high; //最后一位
struct tnode* key = a[i]; //将第一个数作为基准值-- 先找到一个基准值
//进行排序---> 最终结果就是 左面的 都比基准值小 ,右面的都比 基准值大,所以这是所有循环的结束条件
while (i < j)
{
//下面的循环执行的条件是 如果右面的比基准值大,就赋一下值,否则继续向前移动
//---如果直接把循环写成下面这样---
//while(a[j] >= key) //如果下面的不写这个i<j,这个就出错、越界,并且排序不准--理由:
//如果i<j,并且: 右面的值 大于 基准值 时,j往前移动一个
//i 跟 j 的可能情况 只有 i<j i==j
while (i < j && a[j]->count >= key->count)//i<j 是 当前while循环的结束条件,如果没有这个,i会大于j,出现越界,错误
{
j--;//继续走
}//如果不成立,也就是 a[j] <= key;右面的比key小了,那就换个位置
//这里的a[i]还是key,所以即把小于key的值a[j]所在的位置与key所在的位置交换,将其从后面放到前面
//把a[j]的数据给了a[i].但a[j]并没有变,即此时数组中有两个a[j]的值。原a[i]的值即key,在数组中暂时消失了。
a[i] = a[j];
//将事先保存好的基准值与左边的值进行比较,如果基准值大,保持不变,i往前
//然后 判断一下这个新的a[i],也就是之前的a[j]跟key值的关系---> 一定是 a[i]<key
//所以把i向前移动一下,i++
while (i < j && a[i]->count <= key->count) //i仍为初始值low,但a[i]的值已经发生改变了
{
i++;
}
//移动完以后,把大于key的a[i]的数值给刚才的a[j],即将大于key的值从前面放到后面,然后开始下一次循环
//但此时同样a[i]并没有变,即此时数组中有两个a[i]的值。数组中少的那个值同样是保存在变量key中的那个。
a[j] = a[i];
}
//i和j在中间相遇了就跳出循环,将基准值放入数据a[i]中
a[i] = key;
//对基准值左边 的所有数据 再次进行快速查找(递归)
if (i - 1 > low)
{
quick_sort(a, low, i - 1);
}
//对基准值右边的所有数据再次进行快速查找(递归)
if (i + 1 < high)
{
quick_sort(a, i + 1, high);
}
}
/*与前面采用递归的方式,利用指向该二叉数的指针,按“左子树-节点-右子树”的顺序打印二叉树不同,
此处是利用指向该二叉树各节点的指针数组treeNodes,及该数组的下标i,
按倒序(即按每个节点处的单词出现频率的降序)打印二叉树。
这两种都有顺序,采用的也都是指针,但具体的①打印的顺序方式变了;②采用的方法(递归与指针数组)也变了。 */
void treeprint(void)
{
for (int i = treeP - 1; i >= 0; i--)
printf("%4d %s\n", treeNodes[i]->count, treeNodes[i]->word);
}
//该函数中的treeNodes、treeP都是全局变量,整个文件中都可以直接引用,因此不用再单独设置形参并赋实参
//now is the time for all good men to come to the aid of thrie party
6.6 表查找
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct nlist
{
struct nlist* next;
char* name;
char* defn;
};
#define HASHSIZE 101
static struct nlist* hashtab[HASHSIZE];
/* hash函数:为字符串s生成散列值 */
unsigned hash(char* s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31 * hashval;
return hashval % HASHSIZE;
}
/* lookup函数:在hashtab中查找s */
struct nlist* lookup(char* s)
{
struct nlist* np;
for (np = hashtab[hash(s)]; np != NULL; np = np->next)
{
if (strcmp(s, np->name) == 0)
{
return np;
}
}
return NULL;
}
/* 将字符串s复制到某个位置 */
char* strdup(char* s)
{
char* p = NULL;
p = (char*)malloc(sizeof(s) + 1);
if (p != NULL)
{
strcpy_s(p, sizeof(s) + 1, s);
}
return p;
}
/* install函数:将(name,defn)加入到hashtab中 */
struct nlist* install(char* name, char* defn)
{
struct nlist* np;
unsigned hashval;
if ((np = lookup(name)) == NULL)
{
np = (struct nlist*)malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
{
return NULL;
}
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
//将新创建的结构体对象链接到对应哈希值的链表头部
}
else
{
free((void*)np->defn);
//如果名称name已经存在于符号表中,则释放原有的定义部分的内存
}
/* 不论名称是否已存在,都要尝试对 defn 进行深拷贝,
并将其结果赋值给当前 np 结构体的 defn 成员。
如果深拷贝失败,同样返回 NULL 表示错误。*/
if ((np->defn = strdup(defn)) == NULL)
{
return NULL;
}
/* 无论name是新插入还是已存在并更新了defn,
都返回指向结构体的指针np*/
return np;
}
练习6-5
编写函数undef,它将从由lookup和install维护的表中删除一个变量及其定义
且听且看 6-5:
在ASCII码中,每个字符都可以用一个数字代替,那么一个字符串同样可以转换为一个数字,通过一个函数将一个字符串转换为合适大小的数字,这个函数称为哈希函数,而这个数字就是存储这条字符串的数组的下标。下一次再遇到此字符串时,利用哈希函数可得到相同的数字,查找这个数字下标的数组元素就可以找到之前存储的字符串,这就是散列查找,也称哈希查找。哈希查找的时间复杂度为O(1),因为只需要利用哈希函数得到数组下标就可以通过该下标查找到元素。
但是哈希查找也有缺陷,易出现冲突,即两个不相同的字符串可能通过哈希函数得到的数字相同,尤其是数组规模不大时,这种冲突极易出现,解决冲突的方式有多种,此书采用的是链表法,即数组的每个元素是一个指向链表的指针,在链表中存放字符串,当遇到冲突字符串时,将冲突字符串添加到链表中,查找时遍历整个链表,寻找是否有相同字符串。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define HASHSIZE 101
#define BUFSIZE 100
static char buf[BUFSIZE];
static int bufp = 0;
struct nlist {
struct nlist* next;
char* name;
char* defn;
};
static struct nlist* hashtab[HASHSIZE];
unsigned hash(char* s);
struct nlist* lookup(char* s);
struct nlist* install(char* name, char* defn);
int undef(char* name);
char* strdup(char* s);
int getword(char* word, int lim);
int getch(void);
void ungetch(int c);
int main(void)
{
char name[100], define[100];
struct nlist* np;
printf("请输入创建表的名字\n");
while (getword(name, 100) != EOF)
{
printf("请输入该名字的替换文本\n");
getword(define, 100);
if (install(name, define) == NULL)
break;
printf("继续输入名字\n");
}
printf("请输入待查找的名字\n");
while (getword(name, 100) != EOF)
{
if ((np = lookup(name)) != NULL)
printf("%s %s\n", np->name, np->defn);
else
printf("error\n");
}
printf("请输入待删除的名称\n");
while (getword(name, 100) != EOF)
{
if (undef(name))
printf("删除成功\n");
else
printf("error\n");
}
printf("请输入待查找的名字\n");
while (getword(name, 100) != EOF)
{
if ((np = lookup(name)) != NULL)
printf("%s %s\n", np->name, np->defn);
else
printf("error\n");
}
return 0;
}
unsigned hash(char* s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31 * hashval;
return hashval % HASHSIZE;
}
struct nlist* lookup(char* s)
{
struct nlist* np;
for (np = hashtab[hash(s)]; np != NULL; np = np->next)
if (strcmp(s, np->name) == 0)
return np;
return NULL;
}
struct nlist* install(char* name, char* defn)
{
struct nlist* np;
unsigned hashval;
if ((np = lookup(name)) == NULL)//查找链表有无匹配的名字,如果没有
{
np = (struct nlist*)malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];//从链表的表头添加项,hashtab[hashval]指向的是原链表的表头
hashtab[hashval] = np;//np现在成为新链表的表头,hashtab[hashval]指向np
}
else
free((void*)np->defn);//释放原替换文本分配的空间
if ((np->defn = strdup(defn)) == NULL)//不管链表有无匹配的名字,都将通过此语句为defn分配空间并赋值
return NULL;
return np;
}
int undef(char* name)
{
struct nlist* np1, * np2;
for (np1 = hashtab[hash(name)], np2 = NULL; np1 != NULL; np2 = np1, np1 = np1->next)
/* np2代表的是np1的前一个节点。
当np1是链表头节点,则上一个np2为空,此时将np1的下一个节点由指针数组元素hashtab[hash(name)]指向;
当np1不是链表的头节点,则上一个np2不为空,此时将np1的下一个节点由np2的结构成员next指向。
不管np1是否是链表的头节点,都是处理的其原来由np1->next指向的下一个节点,在np1删除之后,由谁指向的问题。
若np1的下一个节点不存在,则程序中通过np1->next赋给后的hashtab[hash(name)] 或 np2->next,仍是为空。
此时除了删除了节点np1,同样也没有改变表中的任何其它部分。 */
if (!strcmp(name, np1->name))
{
if (np2 == NULL)
hashtab[hash(name)] = np1->next;
else
np2->next = np1->next;
free(np1->name);
free(np1->defn);
free(np1);
return 1;
}
return 0;
}
char* strdup(char* s)
{
char* p;
p = (char*)malloc(strlen(s) + 1);
if (p != NULL)
strcpy_s(p, strlen(s) + 1,s);
return p;
}
int getword(char* word, int lim)
{
int c, getch(void);
void ungetch(int);
char* w = word;
while (isspace(c = getch()))
;
if (c != EOF)
*w++ = c;
if (!isalnum(c))
{
*w = '\0';
return c;
}
for (; --lim > 0; w++)
if (!isalnum(*w = getch()))
{
ungetch(*w);
break;
}
*w = '\0';
return word[0];
}
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("error: too many parameters\n");
else
buf[bufp++] = c;
}
练习6-6
以本节介绍的函数为基础,编写一个适合C语言程序使用的#define处理器的简单版本(即无参数的情况)。你会发现getch和ungetch函数非常有用。
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HASHSIZE 101
#define MAXWORD 1000
#define BUFSIZE 100
char buf[BUFSIZE];
int bufp = 0;
struct nlist {
struct nlist* next;
char* name;
char* defn;
};
static struct nlist* hashtab[HASHSIZE];
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;
}
return;
}
unsigned hash(char* s) {
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31 * hashval;
return hashval % HASHSIZE;
}
struct nlist* lookup(char* s) {
struct nlist* np;
for (np = hashtab[hash(s)]; np != NULL; np = np->next) {
if (strcmp(s, np->name) == 0) {
return np;
}
}
return NULL;
}
struct nlist* install(char* name, char* defn) {
struct nlist* np;
unsigned hashval;
if ((np = lookup(name)) == NULL) {
np = (struct nlist*)malloc(sizeof(*np));
if (np == NULL || (np->name = _strdup(name)) == NULL) {
return NULL;
}
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
}
else {
free((void*)np->defn);
}
if ((np->defn = _strdup(defn)) == NULL) {
return NULL;
}
return np;
}
int undef(char* name) {
struct nlist* np1, * np2;
if ((np1 = lookup(name)) == NULL) {
return 1;
}
for (np1 = np2 = hashtab[hash(name)]; np1 != NULL; np2 = np1, np1 = np1->next) {
if (strcmp(name, np1->name) == 0) {
if (np1 == np2) {
hashtab[hash(name)] = np1->next;
}
else {
np2->next = np1->next;
}
free(np1->name);
free(np1->defn);
free(np1);
return 0;
}
}
return 1;
}
int preproc(void) {
int c;
char name[MAXWORD + 1], defn[MAXWORD + 1];
char* n, * d;
for (n = name; isalpha(c = getch()) && n - name < MAXWORD; ++n) {
*n = c;
}
*n = '\0';
if (strcmp(name, "define") == 0) {
while (isspace(c)) {
if (c == '\n') {
putchar(c);
return 1;
}
c = getch();
}
for (n = name; (isalnum(c) || c == '_') && n - name < MAXWORD; ++n) {
*n = c;
c = getch();
}
*n = '\0';
while (isspace(c)) {
if (c == '\n') {
*defn = '\0';
}
c = getch();
}
for (d = defn; (isalnum(c) || c == '_') && d - defn < MAXWORD; ++d) {
*d = c;
c = getch();
}
*d = '\0';
if (install(name, defn) == NULL) {
return 2;
}
}
else {
putchar('#');
printf("%s", name);
}
while (c != '\n') {
if (c == EOF) {
return EOF;
}
putchar(c);
c = getch();
}
putchar(c);
return 1;
}
int backslash(void) {
int c, slash = 1;
putchar('\\');
while ((c = getch()) == '\\') {
slash = !slash;
putchar(c);
}
if (slash) {
putchar(c);
}
else {
ungetch(c);
}
return 0;
}
int comment(void) {
int c, afterStar = 0;
putchar('/');
if ((c = getch()) == '*') {
putchar(c);
c = getch();
while (c != EOF) {
if (c == '\\') {
backslash();
afterStar = 0;
}
else if (c == '*') {
afterStar = 1;
putchar(c);
}
else if (c == '/' && afterStar) {
putchar(c);
return 0;
}
else {
afterStar = 0;
putchar(c);
}
c = getch();
}
if (c == EOF) {
return EOF;
}
putchar(c);
return 0;
}
else {
ungetch(c);
return 0;
}
}
int literal(void) {
int c;
putchar('\"');
c = getch();
while (c != '\"' && c != EOF) {
if (c == '\\') {
backslash();
}
else {
putchar(c);
}
c = getch();
}
if (c == EOF) {
return EOF;
}
putchar(c);
return 0;
}
int readword(void) {
int c;
char word[MAXWORD];
char* w;
struct nlist* node;
c = getch();
for (w = word; (isalnum(c) || c == '_') && c != EOF; ++w) {
*w = c;
c = getch();
}
*w = '\0';
node = lookup(word);
if (node == NULL) {
printf("%s", word);
}
else {
printf("%s", node->defn);
}
if (c == EOF) {
return EOF;
}
ungetch(c);
return 0;
}
char* _strdup(char* s) {
char* p;
p = (char*)malloc(strlen(s) + 1);
if (p != NULL) {
strcpy_s(p, strlen(s) + 1,s);
}
return p;
}
int main() {
int c;
int status = 1;
for (;;) {
while (isspace(c = getch())) {
putchar(c);
if (c == '\n')
status = 1;
}
if (c == '#' && status == 1)
status = preproc();
else if (c == '\\')
status = backslash();
else if (c == '/')
status = comment();
else if (c == '\"')
status = literal();
else if (c == EOF)
return 0;
else if (!isalpha(c) && c != '_') {
putchar(c);
status = 0;
}
else {
ungetch(c);
status = readword();
}
if (status == 2)
return 1;
}
system("pause");
return 0;
}