C语言 第六章 结构
第六章 结构
结构是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。结构将一组相关的变量看作一个单元而不是各自独立的实体,因此结构有助于组织复杂的数据,特别是大型的程序中。
ANSI标准在结构方面最主要的是变化是定义了结构的复制操作,结构可以拷贝、赋值、传递给函数,函数也可以返回标准类型的返回值。自动结构和数组也可以进行初始化。
6.1 结构的基本知识

关键字struct引入结构声明。结构声明由包含在花括号内的一系列声明组成。关键字struct后面的名字是可选的,结构标记用于结构命名,定义之后,结构标记就代表花括号内的声明,金额以用它作为该声明的简写模式。
结构中定义的变量称为成员。结构成员、结构标记和普通变量(非成员)可以采用相同的名字,他们之间不会冲突,因为通过上下文分析总可以对他们进行区分。另外不同结构中的成员可以使用相同的名字,通常只有密切相关的对象才会使用相同的名字。
struct声明定义了一种数据类型。在标志结构成员表结束的右花括号之后可以跟一个变量表,这与其他基本类型的变量声明是相同的。
struct { ... } x, y, z; 这种方式声明与声明 int x, y, z; 具有类似意义。这两个声明都将x,y,z声明为指定类型的变量,并且为他们分配存储空间。
如果结构声明的后面不带变量表,则不需要为它分配存储空间,它仅仅描述了一个结构的模板或轮廓。如果结构声明中带有标记,那么在以后定义结构实例时便可以使用该标记定义。例如:
struct point pt
定义了一个 struct point 类型的变量pt。结构初始化可以在定义的后面使用存初值表进行。初值表中同每个成员对应的初值必须是常量表达式。初值表中同每个成员对应的初值必须是常量表达式,例如:
struct point maxpt = {320, 200}
自动结构也可以通过赋值初始化,还可以通过调用返回相应类型结构的函数进行初始化。
在表达式中,可以通过下列形式引用某个特定结构中的成员:
结构名.成员
例如:printf("%d, %d",pt.x,pt.y);
通过下列代码计算原点到pt的距离:
double dist, sqrt(double);
dist = sqrt((double)pt.x * pt.x + (double)pt.y * pt.y);

struct rect {
struct point pt1;
struct point pt2;
};
结构rect包含两个point类型的成员。按照下面声明 struct recct screen; 则可以用语句 screen.pt1.x 引用screen的成员pt1的x坐标.
6.2 结构与函数
结构的合法操作只有几种:作为一个整体复制和赋值,通过&运算符取地址,访问其他成员。复制和赋值包括向函数传递参数以及从函数返回值。结构不可以进行比较。可以用一个常量成员值列表初始化结构,自动结构也可以通过赋值进行初始化。
通过三种方法传递结构:一是分别传递各个结构成员,二是传递整个结构,三是传递指向结构的指针。
如果传递给函数的结构很大,使用指针方式的效率通常比复制整个机构的效率要高。结构指针类似于普通变量指针。
struct point *pp 将pp定义为一个指向struct point 类型对象的指针。如果pp指向一个point结构,那么*pp即为该结构,而(*pp).x和(*pp).y则是结构成员。下列是使用方式。
pp = &origin;
printf("origin is (%d,%d)\n", (*pp).x, (*pp).y); 其中,(*pp).x中的圆括号是必须的,因为结构成员运算符“.”的优先级比*的优先级高。表达式*pp.x的含义等价于*(*pp.x),因为x不是指针,所以表达式是非法的。
结构指针的使用频率非常高,C语言提供了简写模式。假定p是一个指向结构的指针。可以使用
p->结构成员
printf("origin is (%d,%d)\n",pp->x, pp->y);
运算符. 和 ->都是从左到右结合的,对于下列声明:struct rect r, *rp = &r;
以下四个表达式是等价的: r.pt1.x rp->pt1.x (r.pt1).x (rp->pt1).x
在所有运算符中,下面四个运算符的优先级最高:结构运算符“.”和“->“,用于函数调用的”()“以及用于下标的”[ ]“,他们同操作数之间的结合也最紧密。
struct {
int len;
char *str;
} *p;
表达式 ++p->len, 将增加len的值,而不是增加p的值,等于++(p->len)。可以使用括号改变结构次序。例如:(++p)->len将先执行p的加1操作,在对len执行操作;而(p++)->len则先对len执行操作,然后在将p加1。同样,*p->str 读取的的是指针str所指向的对象的值;*p->str++先读取指针str指向的对象的值,然后再将str加1(与*s++相同);(*p->str) ++将指针str指向的对象的值加1;*p++ -> str先读取指针str子项的对象的值,然后再将p加1。
6.3 结构数组
struct key {
char *word;
int count;
} keytab[NKEYS];
它声明了一个结构类型key,并定义了该类型的结构数组keytab,同时为其分配存储空间。数组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,
.......
};
更精确的写法是:
{ "auto", 0 },
{ "break", 0 },
{ "case", 0 },
如果初值是简单变量或字符串,并且其中的仍和值都不为空,则内层的花括号可以省略。通常情况下,如果初值存在并且方括号[ ]中没有数值,编译程序将计算数组keytab中的项数。
数组的长度在编译时已经完全确定,它等于数组的长度乘以项数:
keytab 的长度 / struct key的长度 , C语言提供了一个编译时一元运算符sizeof,它可以用来计算任一对象的长度。
表达式 sizeof 对象 以及 sizeof (类型名) , 将返回一个整型值,它等于指定对象或类型占用的存储空间字节数。(严格的说,sizeof返回值是无符号整型值,其类型为size_t,该类型在头文件<stdddef.h>中定义。)其中,对象可以是变量、数组或结构;类型可以是基本类型,如int、double,也可以是派生类型,如结构类型或指针类型。在该例子中,关键字的个数等于数组的长度除以单个元素的长度。
条件编译语句#if中不能使用sizeof,因为预处理器不对类型名进行分析。但预处理器并不计算#define语句中的表达式,在#define中使用sizeof是合法的。
练习6-1,getch,ungetch在另外一个文件,其他声明在C06.h中
1 #include "C06.h" 2 3 4 struct key { 5 char *word; 6 int count; 7 } keytab[] = { 8 "auto", 0, 9 "break", 0, 10 "case", 0, 11 "char", 0, 12 "const", 0, 13 "continue", 0, 14 "default", 0, 15 /* ... */ 16 "unsigned", 0, 17 "void", 0, 18 "volatile", 0, 19 "while", 0 20 }; 21 22 23 24 #define NKEYS (sizeof keytab / sizeof(keytab[0])) 25 #define MAXWORD 100 26 27 28 int getword(char *, int); 29 int binsearch(char *, struct key *, int); 30 31 int main() { 32 int n; 33 char word[MAXWORD]; 34 while (getword(word, MAXWORD) != EOF) //获取字符 35 if (isalpha(word[0])) //如果是字母 36 if ((n = binsearch(word, keytab, NKEYS)) >= 0) //对比 37 keytab[n].count++; //如果发现有相同就+1 38 for (n = 0; n < NKEYS; n++) 39 if (keytab[n].count > 0) //输出 40 printf("%4d %s\n",keytab[n].count, keytab[n].word); 41 return 0; 42 } 43 44 45 int binsearch(char *word, struct key tab[], int n) 46 { 47 int cond; 48 int low, high, mid; 49 50 low = 0; 51 high = n - 1; //n是上面结构里面一共有多少个对象,之所以-1,是因为下面循环是0开始的 52 while (low <= high) { 53 mid = (low+high) / 2; //找到中间的位置 54 if ((cond = strcmp(word, tab[mid].word)) < 0) //进行对比 55 high = mid - 1; 56 else if (cond > 0) 57 low = mid + 1; 58 else 59 return mid; 60 } 61 return -1; 62 } 63 64 int getword(char *word, int lim) 65 { 66 int c,d,comment(void); 67 char *w = word; 68 69 while (isspace(c = getch())); 70 if (c != EOF) 71 *w++ = c; 72 if (isalpha(c) || c == '_' || c == '#'){ //检查是否为字母,下划线,# 73 for (; --lim > 0; w++) 74 if (!isalnum(*w = getch()) && *w != '_'){ //检查获得的字符既不是下划线,也不是数字的字符 75 ungetch(*w); //存入缓存区 76 break; 77 } 78 } else if (c == '\'' || c == '"'){ //检查是否为斜杠或者双引号 79 for (; --lim > 0; w++){ 80 if ((*w = getch()) == '\\') //如果是双斜杠 81 *++w = getch(); 82 else if (*w == c) { //如果是斜杠或者引号 83 w++; //不写入 84 break; 85 }else if (*w == EOF) //如果是结束符 86 break; 87 } 88 } else if (c == '/') { 89 if ((d = getch()) == '*') 90 c = comment(); 91 else 92 ungetch(d); 93 } 94 *w = '\0'; 95 return c; 96 } 97 98 int comment(void) 99 { 100 int c; 101 while ((c = getch()) != EOF) 102 if (c == '*') { 103 if ((c = getch()) == '/') 104 break; 105 else 106 ungetch(c); 107 } 108 return c; 109 110 }
6.4 指向结构的指针
#include "C06.h" /* 关于结构:千万不要认为结构的长度等于各成员长度的和。 因为不同的对象有不同的对其要求,所以,结构中可能会出现未命名的“空穴(hole)“ 假设char类型占用一个字节,int类型占用4个字节 struct { char c; int i; } 上面结构可能需要8个字节的存储空间,为不是5个字节。 使用sizeof运算符可以返回正确对现象的长度。 */ 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 }; #define NKEYS (sizeof keytab / sizeof(keytab[0])) #define MAXWORD 100 int getword(char *, int); /* 当函数的返回值类型比较复杂时(如下列结构指针),很难看出函数名, 不容易使用文本编辑器找到函数名。可以采用另一中格式。 struct key * binsearch(char *word, struct key *tab, int n) */ struct key *binsearch(char *, struct key *, int); //函数返回为struct key类型的指针 int main() { char word[MAXWORD]; struct key *p; while (getword(word, MAXWORD) != EOF) { if (isalpha(word[0])){ if ((p = binsearch(word, keytab, p++)) != NULL){ p->count++; } } } /* 下列,如果p是指向结构的指针,对p的算术运算需要考虑结构的长度,表达式p++执行时, 将在p的基础上加上一个正确的值,保证得到结构数组的下一个元素。让条件能够正确循环并且终止 */ for (p = keytab; p < keytab + NKEYS; p++){ if (p->count > 0){ printf("%4d %s\n",p->count,p->word); } } return 0; } struct key *binsearch(char *word, struct 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) / 2 是错误的写法,两个指针之间的加法运算时非法的。 //但是指针减法运算确实合法的,如下 mid = low + (high-low) / 2; if ((cond = strcmp(word, mid->word)) < 0){ high = mid; //这里没有进行mid-1操作的原因时确保不会生成非法的指针,或者时试图访问数组范围之外的元素。 /*&tab[-1]和&tab[n]都超出了数组tab的范围。前者是绝对非法的 ,对后者的间接引用也是非法的。但是,C语言的定义保证数组末尾之后的 第一个元素(既&tab[n])的指针算数运算可以正确执行。*/ } else if(cond > 0){ low = mid + 1; } else { return mid; } } return NULL; } int getword(char *word, int lim) { int c,d,comment(void); char *w = word; while (isspace(c = getch())); if (c != EOF) *w++ = c; if (isalpha(c) || c == '_' || c == '#'){ //检查是否为字母,下划线,# for (; --lim > 0; w++) if (!isalnum(*w = getch()) && *w != '_'){ //检查获得的字符既不是下划线,也不是数字的字符 ungetch(*w); //存入缓存区 break; } } else if (c == '\'' || c == '"'){ //检查是否为斜杠或者双引号 for (; --lim > 0; w++){ if ((*w = getch()) == '\\') //如果是双斜杠 *++w = getch(); else if (*w == c) { //如果是斜杠或者引号 w++; //不写入 break; }else if (*w == EOF) //如果是结束符 break; } } else if (c == '/') { if ((d = getch()) == '*') c = comment(); else ungetch(d); } *w = '\0'; return c; } int comment(void) { int c; while ((c = getch()) != EOF) if (c == '*') { if ((c = getch()) == '/') break; else ungetch(c); } return c; }
6.5 自引用结构
#include "C06.h" struct tnode { char *word; int count; struct tnode *left; struct tnode *right; }; #define MAXWORD 100 /* 函数addtree时递归的。主函数main一参数方式传递给改函数的第一个单词 作为最顶层(既树根)。在每一步中,新单词与节点中存储大的单词进行比较, 随后通过递归调用addtree而转向左字树或右子树。该单词最终将与树中的某节点匹配 (这种情况下计数值加1),或遇到一个空指针(表明必须创建一个节点并加入树中)。 若生成了新节点,则addtree返回一个指向新节点的指针,该指针保存在父节点中。 */ struct tnode *addtree(struct tnode *, char *); struct tnode *talloc(void); char *strdup(char *); void treeprint(struct tnode *); main() { struct tnode *root; //一个指向结构对象root的指针 char word[MAXWORD]; //单个单词最大个数 root = NULL; //把root这个(tnode结构)的对象设置为空,来存储第一个单词 while (getword(word, MAXWORD) != EOF) { if (isalpha(word[0])){ //检查是否为字母 root = addtree(root, word); } } treeprint(root); //输出 return 0; } struct tnode *addtree(struct tnode *p, char *w) { int cond; if (p == NULL){ //如果p指向的空间并不为空,那么就去和新的单词做比较,如果为空,那么就存储新的单词 p = talloc(); //新节点的存储空间由子程序talloc获得。talloc函数返回一个指针,指向能容纳一个树节点的空闲空间。 p->word = strdup(w); //函数strdup将新单词复制到某个隐藏位置。 p->count = 1; //给单词所在结构空间的属性count+1 (例如p是名字,那么p.word属性和p.count属性) p->left = p->right = NULL; //两个子树被置为空,循环开辟的两个子树空间 }else if ((cond = strcmp(w, p->word)) == 0) //对比w(新单词)p->word(老单词) { p->count++; }else if (cond < 0) { p->left = addtree(p->left, w); }else { //如果新单词大于老单词,递归调用addtree本身,并且传入结构对象p的right属性。 //right是一个指向tnode的指针,是p下面一个新的空间,p是一座房子,right和left就是房子里的两个房间的门 //right和left的房间里 也存在着两个属性word和count,并且有两扇门同样叫right和left p->right = addtree(p->right, w); } return p; }; /* treeprint函数按顺序打印树,在每个节点,它先打印左子树(小于该单词的所有单词), 然后是该单词本身,最后是右子树(大于该单词的所有单词)。 */ void treeprint(struct tnode *p) { if (p != NULL){ treeprint(p->left); printf("%4d %s\n",p->count, p->word); treeprint(p->right); } } /* 函数talloc,将malloc的返回值声明为一个指向void类型的指针,然后显式的将该指针强制转换为所需类型 */ struct tnode *talloc(void) { return (struct tnode *) malloc(sizeof(struct tnode)); } //strdup函数只是把通过其参数传入的字符串复制到某个安全的位置 //在没有可用空间时,malloc函数返回NULL,同时,strdup函数也将返回NULL,strdup函数调用着负责出错处理。 char *strdup(char *s) { char *p; p = (char *) malloc(strlen(s)+1); //查看是否有可用空间 if (p != NULL){ //如果有 strcpy(p, s); //复制到p中 } return p; }
下面是第六章分文件的个文件代码
C06.h 头文件
1 #ifndef __C06_H__ 2 #define __C06_H__ 3 4 5 6 7 #include <stdio.h> 8 #include <ctype.h> 9 #include <string.h> 10 #include <stdlib.h> 11 12 #define BUFSIZE 100 13 #define MAXWORD 100 14 #define NDISTINCT 1000 15 #define YES 1 16 #define NO 0 17 18 struct tnode_6_2 { 19 char *word; //记录下一个文本字符 20 int match; //匹配 21 struct tnode_6_2 *left; //左树 22 struct tnode_6_2 *right; //右树 23 }; 24 struct tnode_6_2 *talloc_6_2(void); 25 26 27 struct tnode_6_3 { 28 char *word; //points to the text 指向下一个字符串 29 struct linklist *lines; //line numbers 行号 30 struct tnode_6_3 *left; //left child 左节点 31 struct tnode_6_3 *right; //right child 右节点 32 }; 33 struct tnode_6_3 *talloc_6_3(void); 34 35 36 struct tnode_6_4 { //the tree node: 37 char *word; //points to the text 38 int count; //number of occurrences 反复出现的次数 39 struct tnode_6_4 *left; //left child 40 struct tnode_6_4 *right; //right child 41 }; 42 struct tnode_6_4 *talloc_6_4(void); 43 44 45 int getch(void); 46 void ungetch(int c); 47 int getword(char *word, int lim); 48 49 char *strdup(char *s); 50 51 #endif
addtree.c文件,函数用来添加新的节点并且存储数据
1 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 2 3 4 struct tnode_6_4 *addtree(struct tnode_6_4 *p, char *w) 5 { 6 int cond; 7 if (p == NULL){ //如果p指向的空间并不为空,那么就去和新的单词做比较,如果为空,那么就存储新的单词 8 p = talloc_6_4(); 9 //新节点的存储空间由子程序talloc获得。talloc函数返回一个指针,指向能容纳一个树节点的空闲空间。 10 p->word = strdup(w); 11 //函数strdup将新单词复制到某个隐藏位置。 12 p->count = 1; //给单词所在结构空间的属性count+1 (例如p是名字,那么p.word属性和p.count属性) 13 p->left = p->right = NULL; //两个子树被置为空,循环开辟的两个子树空间 14 }else if ((cond = strcmp(w, p->word)) == 0) //对比w(新单词)p->word(老单词) 15 { 16 p->count++; 17 }else if (cond < 0) 18 { 19 p->left = addtree(p->left, w); 20 }else 21 { 22 //如果新单词大于老单词,递归调用addtree本身,并且传入结构对象p的right属性。 23 //right是一个指向tnode的指针,是p下面一个新的空间,p是一座房子,right和left就是房子里的两个房间的门 24 //right和left的房间里 也存在着两个属性word和count,并且有两扇门同样叫right和left 25 p->right = addtree(p->right, w); 26 } 27 return p; 28 };
getword.c文件,用来获取字符
1 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 2 3 /* 4 这里要穿插一段代码,通过试验尝试关于指针的操作结果问题 5 char buffer[200], s[] = "computer", c = 'l'; 6 int i = 35, j; 7 float fp = 1.7320534f; 8 char *p = buffer; 9 char *x = p; 10 // 格式化并打印各种数据到buffer 11 *p++ = 'a'; 12 printf("%d\n",*p); 13 *p++ = 'b'; 14 printf("%d\n",p); 15 *p = 'c'; 16 printf("%d\n",p); 17 p++; 18 for (;--i>0;p++){ 19 *p = 'd'; 20 } 21 printf("%s\n",buffer); 22 下面是显示结果,当把p换成x结果是一样的 23 -37 24 -75006 25 -75006 26 abcdddddddddddddddddddddddddddddddddd 27 */ 28 29 30 int getword(char *word, int lim) //getword函数把接受的字符集word变成指针 31 { 32 int d,comment(void); 33 char *w = word,c; //w指针指向word指针 34 /* 35 函数isspace检查参数c是否为空格字符,也就是判断是否为空格(' ')、水平定位字符 36 ('\t')、归位键('\r')、换行('\n')、垂直定位字符('\v')或翻页('\f')的情况 37 */ 38 while (isspace(c = getch())); //如果是空格就跳过,注意这里是单个语句,有分号 39 //如果是练习6-3使用 while (isspace(c = getch()) && c != '\n'); 40 if (c != EOF){ 41 *w++ = c; //存入word中 42 } 43 if (isalpha(c) || c == '_' || c == '#'){ //检查是否为字母,下划线,# 44 for (; --lim > 0; w++){ //循环存入,如果发现有不是字母数字并且不是下划线就存入 ,否则就从存入缓存区 45 if (!isalnum(*w = getch()) && *w != '_'){ //函数isalnum判断字符变量w是否为字母或数字,若是则返回非零,否则返回零,并且不是下划线 46 ungetch(*w); //存入缓存区 47 break; 48 } 49 } 50 } 51 else if (c == '\'' || c == '"'){ //检查是否为斜杠或者双引号 52 for (; --lim > 0; w++){ 53 if ((*w = getch()) == '\\'){ //如果是双斜杠 54 *++w = getch(); 55 } 56 else if (*w == c) { //如果是斜杠或者引号 57 w++; //不写入 58 break; 59 } 60 else if (*w == EOF){ //如果是结束符 61 break; 62 } 63 } 64 } 65 else if (c == '/') { 66 if ((d = getch()) == '*'){ 67 c = comment(); 68 } 69 else{ 70 ungetch(d); 71 } 72 } 73 *w = '\0'; 74 return c; 75 } 76 77 78 int comment(void) 79 { 80 int c; 81 while ((c = getch()) != EOF) 82 if (c == '*') { 83 if ((c = getch()) == '/'){ 84 break; 85 } 86 else{ 87 ungetch(c); 88 } 89 } 90 return c; 91 } 92 93 94 static char buf[BUFSIZE]; 95 static int bufp = 0; 96 97 98 int getch(void) 99 { 100 return (bufp > 0) ? buf[--bufp] : getchar(); // 101 } 102 void ungetch(int c) 103 { 104 if (bufp >= BUFSIZE) //查看是否有足够的空间 105 printf("ungetch: too many characters\n"); 106 else 107 buf[bufp++] = c; //存入 108 }
talloc.c文件,函数用来从内存分配一个新的对象空间
1 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 2 3 4 struct tnode_6_2 *talloc_6_2(void) //指向练习6-2定义的结构 5 { 6 return (struct tnode_6_2 *) malloc(sizeof(struct tnode_6_2)); 7 } 8 9 /* 10 函数talloc,将malloc的返回值声明为一个指向void类型的指针,然后显式的将该指针强制转换为所需类型 11 */ 12 13 struct tnode_6_3 *talloc_6_3(void) //指向练习6-3结构 14 { 15 return (struct tnode_6_3 *) malloc(sizeof(struct tnode_6_3)); 16 } 17 18 struct tnode_6_4 *talloc_6_4(void) //指向练习6-4定义的结构 19 { 20 return (struct tnode_6_4 *) malloc(sizeof(struct tnode_6_4)); 21 } 22 23 24 25 //strdup函数只是把通过其参数传入的字符串复制到某个安全的位置 26 //在没有可用空间时,malloc函数返回NULL,同时,strdup函数也将返回NULL,strdup函数调用着负责出错处理。 27 char *strdup(char *s) 28 { 29 char *p; 30 p = (char *) malloc(strlen(s)+1); //查看是否有可用空间 31 if (p != NULL){ //如果有 32 strcpy(p, s); //复制到p中 33 } 34 return p; 35 }
1 /* 2 练习6-2 3 编写一个程序,用于读入一个C语言程序,并按字母表顺序分组打印变量名,要求每一组内各变量名的前6个字符相同 4 其余字符不同。字符串和注释中的单词不予考虑,请将6作为 一个可在命令行中设定的参数。 5 */ 6 7 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 8 9 10 11 12 13 struct tnode_6_2 *addtreex(struct tnode_6_2 *, char *, int, int *); 14 void treexprint(struct tnode_6_2 *); 15 16 /* 17 print in alphabetic order each group of variable names 18 按字母顺序打印每组变量名 19 identical in the first num characters (default 6) 20 第一个数字字符相同(默认6) 21 */ 22 23 24 main(int argc, char *argv[]) 25 { 26 struct tnode *root; 27 char word[MAXWORD]; 28 int found = NO; 29 int num; 30 31 num = (--argc && (*++argv)[0] == '-') ? atoi(argv[0]+1) : 6; 32 root = NULL; 33 while (getword(word, MAXWORD) != EOF) { 34 if (isalpha(word[0]) && strlen(word) >= num){ 35 root = addtreex(root, word, num, &found); 36 } 37 found = NO; 38 } 39 treexprint(root); 40 return 0; 41 } 42 43 44 45 int compare(char *, struct tnode_6_2 *, int, int *); 46 47 //addtreex: add a node with w, at or below p 48 //添加一个节点w,在p或者以下 49 50 struct tnode_6_2 *addtreex(struct tnode_6_2 *p, char *w, int num, int *found) 51 { 52 int cond; 53 54 if (p == NULL){ 55 p = talloc_6_2(); //新的空间 56 p->word = strdup(w); 57 p->match = *found; 58 p->left = p->right = NULL; 59 } else if ((cond = compare(w, p ,num, found)) < 0) 60 { 61 p->left = addtreex(p->left, w, num, found); 62 } 63 else if (cond > 0) 64 { 65 p->right = addtreex(p->right, w, num, found); 66 } 67 return p; 68 } 69 70 //compare: compare words and update p->match 比较更形单词 71 int compare(char *s, struct tnode_6_2 *p, int num, int *found) 72 { 73 int i; 74 char *t = p->word; 75 76 for (i = 0; *s == *t; i++, s++, t++){ 77 if (*s == '\0'){ 78 return 0; 79 } 80 } 81 if (i >= num) { //identical in first num chars 82 *found = YES; 83 p->match = YES; 84 } 85 return *s - *t; 86 } 87 88 //treexprint: in-order print of tree p if p->match == YES 如果p属性match为YES 按顺序打印 89 void treexprint(struct tnode_6_2 *p) 90 { 91 if (p != NULL) { 92 treexprint(p->left); 93 if (p->match){ 94 printf("%s\n",p->word); 95 } 96 treexprint(p->right); 97 } 98 }
1 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 2 3 /* 4 练习6-3:编写一个交叉引用程序,打印文档中所有单词的列表,并且每个单词还有一个列表,记录出现过该单词的行号。 5 对the、end等非实意单词不考虑 6 */ 7 8 struct linklist { // linked list of line numbers 行号的链表 9 int lnum; 10 struct linklist *ptr; 11 }; 12 13 // struct tnode { 14 // char *word; //points to the text 指向下一个字符串 15 // struct linklist *lines; //line numbers 行号 16 // struct tnode *left; //left child 左节点 17 // struct tnode *right; //right child 右节点 18 // }; 19 20 21 struct tnode_6_3 *addtreex(struct tnode_6_3 *, char *, int); 22 int noiseword(char *); 23 void treexprint(struct tnode_6_3 *); 24 25 26 //cross-referencer 交叉引用 27 28 main() 29 { 30 struct tnode_6_3 *root; 31 char word[MAXWORD]; 32 int linenum = 1; 33 34 root = NULL; 35 while (getword(word,MAXWORD) != EOF){ 36 if (isalpha(word[0])){ 37 if (noiseword(word) == -1){ //判断是否为忽略的单词 38 root = addtreex(root, word, linenum); 39 } 40 } 41 else if (word[0] == '\n'){ //如果是换行符,那么行号+1 42 linenum++; 43 } 44 } 45 treexprint(root); 46 return 0; 47 } 48 49 struct linklist *lalloc(void); 50 void addln(struct tnode_6_3 *, int); 51 52 53 //addtreex: add a node with w, at or below p 添加节点w在p节点或者p节点之下 54 struct tnode_6_3 *addtreex(struct tnode_6_3 *p, char *w, int linenum) 55 { 56 int cond; 57 58 if (p == NULL){ 59 p = talloc_6_3(); //开辟新空间 60 p->word = strdup(w); //存储 61 p->lines = lalloc(); //p->lines是指向新的结构的指针,此操作开辟新的空间 62 p->lines->lnum = linenum; //给lines新空间添加行号 63 p->lines->ptr = NULL; 64 p->left = p->right = NULL; 65 } 66 else if ((cond = strcmp(w, p->word)) == 0){ //如果发现有重复字母,那么添加新的行号 67 addln(p, linenum); 68 } 69 else if (cond < 0){ 70 p->left = addtreex(p->left, w, linenum); 71 } 72 else 73 { 74 p->right = addtreex(p->right, w, linenum); 75 } 76 return p; 77 } 78 79 80 //addin: add a line number to the linked list 给列表添加行号 81 void addln(struct tnode_6_3 *p, int linenum) 82 { 83 struct linklist *temp; 84 85 temp = p->lines; 86 while (temp->ptr != NULL && temp->lnum != linenum){ 87 temp = temp->ptr; 88 } 89 if (temp->lnum != linenum){ 90 temp->ptr = lalloc(); 91 temp->ptr->lnum = linenum; 92 temp->ptr->ptr = NULL; 93 } 94 } 95 96 //treexprint: in-order print of free p 排序打印p 97 void treexprint(struct tnode_6_3 *p) 98 { 99 struct linklist *temp; 100 if (p != NULL) { 101 treexprint(p->left); 102 printf("%10s:",p->word); 103 for (temp = p->lines; temp != NULL; temp = temp->ptr){ 104 printf("%4d ",temp->lnum); 105 } 106 printf("\n"); 107 treexprint(p->right); 108 } 109 } 110 111 //lalloc: make a linklist node 创建连接列表节点 112 struct linklist *lalloc(void) 113 { 114 return (struct linklist *) malloc(sizeof(struct linklist)); 115 } 116 117 118 //noiseword: identify word as a noise word 将单词识别为不被考虑的单词 119 120 int noiseword(char *w) 121 { 122 static char const *nw[] = { 123 "a", 124 "an", 125 "and", 126 "are", 127 "in", 128 "is", 129 "of", 130 "or", 131 "that", 132 "the", 133 "this", 134 "to", 135 }; 136 int cond, mid; 137 int low = 0; 138 int high = sizeof(nw) / sizeof(char *) - 1; 139 140 while (low <= high) { 141 mid = (low + high) / 2; 142 if ((cond = strcmp(w, nw[mid])) < 0){ 143 high = mid - 1; 144 } 145 else if (cond > 0){ 146 low = mid + 1; 147 } 148 else { 149 return mid; 150 } 151 } 152 return -1; 153 } 154 155 /* 156 树中的每个节点都对应着一个不同的单词。树节点的结构如下: 157 word 一个指针,指向有关单词的问题 158 lines 一个指针,指向一个由行号构成的链表 159 left 一个指针,指向本节点的左子节点 160 right 一个指针,指向本节点的右子节点 161 行号链表的每个元素又是一个类型名为linklist的结构。每个结构包含一个行号和一个指针,指针指向链表中的 162 下一个元素(链表最后一个元素的指针指向NULL)。 163 函数addtreex把单词田间到变量名树中并把行号添加到相应的链表中。如果是一个新单词,就保存在变量linenum 164 中的当前行号赋值给链表中的第一个元素,如下所示: 165 p->lines->lnum = linenum 166 如果单词已经在树中(既已经在文档中出现过)则 167 ((cond = strcmp(w, p->word)) == 0) 成立,用函数addln把行号追加到链表的尾部。 168 addln函数首先检查链表中是否已经存在着相同的行号,如果没有,它将到达链表尾并遇到NULL, 169 while (tem->ptr != NULL && temp->lnum != linenum) 170 temp = temp->ptr; 171 如果这个行号没有在链表中出现过,addln函数就会把它追加到了链表尾部 172 if (temp->lnum != linnum) { 173 temp->ptr = lalloc(); 174 temp->ptr->lnum = linenum; 175 temp->ptr->ptr = NULL; 176 } 177 函数treexprint把树中的单词节点按字母顺序打印出来。对于树中的每一个单词,这个函数将把这个单词 178 和它出现在文档中的所有行号全部打印出来。 179 noiseword函数用来去掉文档中诸如"the","and"之类不需要统计的单词,这些单词都列在一个static 180 数组中。对于需要进行统计的单词,这个函数将返回-1.可以在数组nw[]中任意添加各种不统计的单词,但前提是 181 必须保持这个数组中的单词按ascii字符升序排列。 182 为了记录行号,我们还修改了getword函数,它能偶返回换行符('\n') 183 while (isspace(c = getch()) &&c!= '\n'); 184 */
1 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 2 3 4 /* 5 练习6-4:编写一个程序,根据单词的出现频率按降序打印输入的各个不同单词,并在每个单词的前面 6 标上它出现的次数。 7 */ 8 9 struct tnode_6_4 *addtree(struct tnode_6_4 *, char *); 10 void sortlist(void); 11 void treestore(struct tnode_6_4 *); 12 13 struct tnode_6_4 *list[NDISTINCT]; //pointers to tree nodes 这个指向结构指针的数组,通过它的指向来排序 14 int ntn = 0; //number of tree nodes 15 16 17 //print distinct words sorted in decreasing order of freq 打印按频递减排序不同的单词 18 19 main() 20 { 21 struct tnode_6_4 *root; 22 char word[MAXWORD]; 23 int i; 24 25 root = NULL; 26 while (getword(word, MAXWORD) != EOF) 27 if (isalpha(word[0])) 28 root = addtree(root, word); 29 treestore(root); //给结构对象添加一个指针数组,数组中每个指针指向一个结构对象 30 sortlist(); //排序指针指向 31 for (i = 0; i < ntn; i++) 32 printf("%2d:%20s\n",list[i]->count, list[i]->word); 33 return 0; 34 } 35 36 //treestore: store in list[] pointers to tree nodes 将指向节点的指针存储在指针列表中 37 void treestore(struct tnode_6_4 *p) 38 { 39 if (p != NULL){ 40 treestore(p->left); 41 if (ntn < NDISTINCT) 42 list[ntn++] = p; 43 treestore(p->right); 44 } 45 } 46 47 48 //sortlist: sort list of pointers to free nodes 对指针排序 对list指针数组进行排序 49 50 void sortlist() 51 { 52 int gap, i ,j; 53 struct tnode_6_4 *temp; 54 55 for (gap = ntn/2; gap > 0; gap /= 2) 56 for(i = gap; i < ntn; i++) 57 for (j = i-gap; j >= 0; j -= gap){ 58 if ((list[j]->count >= (list[j+gap]->count))) 59 break; 60 temp = list[j]; 61 list[j] = list[j+gap]; 62 list[j+gap] = temp; 63 } 64 } 65 66 /* 67 常数NDISTINCT对不同单词的最大数设置了限制。tnode与教材上的一样。list是一个指针数组, 68 其中的每个指针都指向一个tnode类型的结构。变量ntn中保存着树节点的个数。 69 程序读入每个单词并把它放到树中。函数treestore把那些指向tnode结构的指针保存到数组list中。 70 函数sortlist是shellsort函数的改进版本,它按单词出现次数由高到低的顺序对数组list进行排序。 71 */
6.6. 表查找
练习题6-5,6-6,头文件和上一小节一样未作更改。getword函数略作更改,在*w++ = c;后面添加判断,如果c == '#',那么直接返回c。
1 #include "/mnt/c/Users/wang/Desktop/c06_practice/book_test/C06.h" 2 #define HASHSIZE 101 3 4 5 struct nlist { //table entry: 表入口 6 struct nlist *next; //next entry in chain 链表的下一向 7 char *name; //defined name 名字 8 char *defn; //replacement text 替换文本 9 }; 10 11 static struct nlist *hashtab[HASHSIZE]; //pointer table 指针列表 12 struct nlist *lookup(char *s); 13 14 void error(int, char *); 15 void getdef(void); 16 void skipblanks(void); 17 void undef(char *); 18 void ungetch(int); 19 void ungets(char *); 20 struct nlist *install(char *name, char *defn); 21 22 23 // 编写一个适合C语言程序使用的#define处理器的简单版本(既无参数的情况下)。你会发现getch和ungetch函数非常有用。 24 main() 25 { 26 char w[MAXWORD]; 27 struct nlist *p; 28 29 while (getword(w, MAXWORD) != EOF) 30 if (strcmp(w, "#") == 0) //beginning of direct 直接开始 31 getdef(); 32 else if (!isalpha(w[0])) //如果不是字母 33 printf("%s", w); //cannot be define 34 else if ((p = lookup(w)) == NULL) //通过函数lookup查找,如果为找到返回null代表不存在 35 printf("%s", w); //not defined 未定义的 36 else //如果找到已经定义的参数 37 ungets(p->defn); //如果已经定义过,就把里面的参数拿出来,放到输入中,输出参数 38 return 0; 39 40 } 41 42 //getdef: get definition and install it 获取定义并且安装它 43 void getdef(void) 44 { 45 int c, i; 46 char def[MAXWORD], dir[MAXWORD], name[MAXWORD]; 47 48 skipblanks(); //跳过空格和制表符 49 if (!isalpha(getword(dir, MAXWORD))) //如果获得的张字符不是字母就输出错误 50 error(dir[0],"getdef: expecting a directive after #"); //在#之后期待指令 51 else if (strcmp(dir, "define") == 0) { //如果是字母,那么去对比,如果是define,执行下列 52 skipblanks(); 53 if (!isalpha(getword(name, MAXWORD))) //检查是否字母 54 error(name[0], "getdef: non-alpha - name expected"); //不是期待的名字 55 else { //存储定义名 56 skipblanks(); 57 for (i = 0; i < MAXWORD-1; i++) 58 if((def[i] = getch()) == EOF || def[i] == '\n') 59 break; //end of definition 结束定义 60 def[i] = '\0'; 61 if (i <= 0) //如果没字母 62 error('\n',"getdef; incomplete define"); //不完整的定义 63 else //install definition 64 install(name, def); //存入结构中 65 } 66 } else if (strcmp(dir, "undef") == 0) { //如果定义为undef 67 skipblanks(); 68 if (!isalpha(getword(name, MAXWORD))) 69 error(name[0], "getdef: non-alpha in undef"); //没有需要取消定义的名字 70 else 71 undef(name); //从结构中删除 72 } else 73 error(dir[0], "getdef: expecting a directive after #"); //期望在#之后有一个指令 74 } 75 76 //error: print error message and skip the rest of the line 打印错误消息并跳过其余行 77 void error(int c, char *s) 78 { 79 printf("error: %s\n", s); 80 while (c != EOF && c != '\n') 81 c = getch(); 82 } 83 84 85 //skipblanks: skip blank and tab characters 跳过空格和制表符 86 void skipblanks(void) 87 { 88 int c; 89 90 while ((c = getch()) == ' ' || c == '\t'); 91 92 ungetch(c); 93 } 94 /* 95 主函数控制着全局。define和undef指令必须跟在一个#后面,函数gedet也正是利用这一点来解析他么。 96 如果getword函数返回的字符不是一个字母,就不可能对那个单词做出定义,程序将报告出错并把那个单词打印 97 出来 ;否则,程序将开始搜索可能与该单词配对的定义(它可能存在,也可能不存在)。如果该单词确实有一个 98 配对的定义,函数unget将把它们按逆序重新压回输入流。 99 函数getdef能够处理下面这两种指令: 100 #define name definition 101 #undef name 102 其中,name是一个由字母或数字字符构成的变量,definition是这个变量的定义。 103 如果遇到define指令,下面这个循环: 104 for (i = 0;i < MAXWORD-1; i++) 105 if ((def[i] = getch()) == EOF || def[i] == '\n) 106 break; 107 将逐个字符把变量名的定义收集到一起,直到到达行尾('\n')或文件尾(EOF)为止。如果变量名有一个配对 108 的定义,getdef函数将会调用install函数把它添加到表中。 109 如果遇到的是undef指令,程序将从表中删除指定的变量名。 110 为了让程序输出与输入数据相似,我们还修改了getword函数,使它能够返回空格。 111 */ 112 113 114 115 /* 116 该算法采用的是散列查找方法--将输入的名字转换为一个小的非负整数,该整数随后将作为一个指针 117 数组的下标。数组的每个元素指向某个链表的表头,链表中的各个块用于描述具有该散列值的名字。如果没有 118 名字散列到该值,则数组元素的值为NULL 119 散列函数hash在lookup和install函数中都被用到,它通过一个for循环进行计算,每次循环中, 120 它将上一次循环中计算得到的结果值经过变换(乘以31)后得到的新值同字符串中当前字符的值相加 121 (*s + 31 * hashval),然后将该结果值同数组长度执行取模操作,其结果既是该函数的返回值。 122 */ 123 124 125 //hash: form hash value for string s 为字符串生成哈希值 126 unsigned hash(char *s) 127 { 128 unsigned hashval; 129 for (hashval = 0; *s != '\0'; s++) 130 hashval = *s + 31 * hashval; 131 return hashval % HASHSIZE; 132 } 133 134 /* 135 散列过程生成了在数组hashtab中执行查找的起始下标。如果该字符串可以被查找到,则它一定位于该 136 起始下标指向的链表的某个块中。具体过程有lookup函数实现。如果lookup函数发现表项已存在,则返回 137 指向该表项的指针,否则返回NULL。 138 */ 139 140 141 //lookup: look for s in hashtab 在哈希表中查找 142 143 struct nlist *lookup(char *s) 144 { 145 struct nlist *np; 146 147 for (np = hashtab[hash(s)]; np != NULL; np = np->next) //首先通过函数计算得到哈希值 148 if (strcmp(s, np->name) == 0) //对比查看是否存在 149 return np; //如果存在,返回哈希值 150 return NULL; 151 } 152 153 /* 154 install函数借助lookup函数判断待加入的名字是否已经存在。如果已存在,则用新的定义取代之; 155 否则,创建一个新表项。如无足够空间创建新表项,则install函数返回NULL. 156 */ 157 158 char *strdup(char *); 159 160 //install: put (name, defn) in hashtab 在哈希表中放入name, defn两个属性 161 /* 162 install(s,t)函数将名字s和替换文本t记录到某个表中,其中s和t仅仅是字符串。lookup(s)函数在 163 表中查找s,若找到,则返回指向该处的指针;若没找到,则返回NULL。 164 install函数借助lookup函数判断待加入的名字是否已经存在。如果已存在,则用新的定义取而代之; 165 否则,创建一个新表项。如果无足够空间创建新表项,则install函数返回NULL。 166 */ 167 168 struct nlist *install(char *name, char *defn) 169 { 170 struct nlist *np; 171 unsigned hashval; 172 173 if ((np = lookup(name)) == NULL) { //查找是否存在 174 np = (struct nlist *) malloc(sizeof(*np)); //如果不存在,分配内存 175 if (np == NULL || (np->name = strdup(name)) == NULL) // 176 return NULL; 177 hashval = hash(name); 178 np->next = hashtab[hashval]; 179 hashtab[hashval] = np; 180 } 181 else //already there 已经存在 182 { 183 free((void *) np->defn); //free previous defn 释放前一个定义 184 } 185 if ((np->defn = strdup(defn)) == NULL) 186 return NULL; 187 return np; 188 } 189 190 // undef: remove a name and definition fron the table 从表中移除名字和定义 191 192 void undef(char *s) 193 { 194 int h; 195 struct nlist *prev, *np; 196 197 prev = NULL; 198 h = hash(s); //hash value of string s 字符转哈希 199 for (np = hashtab[h]; np != NULL; np = np->next) { 200 if (strcmp(s, np->name) == 0) 201 break; 202 prev = np; //remember previous entry 找到np之前的那个位置 203 } 204 if (np != NULL) { //found name 找到名字 205 if (prev == NULL) //first in the hash list 如果没找到 206 hashtab[h] = np->next; 207 else //elsewhere in the hash list 在散列表其他地方 208 { 209 prev->next = np->next; 210 } 211 free((void *) np->name); 212 free((void *) np->defn); 213 free((void *) np); //free allocated structure 自由分配结构 214 } 215 } 216 217 /* 218 函数undef将在表中查找字符串s。当undef找到字符串s时,他将跳出for循环,如下所示 219 if(strcmp(s, np->name) == 0) 220 break; 221 如果字符串s不在表中,这个for循环将在指针np变成NULL时终止。 222 指针数组hashtab中的各个元素分配指向一个链表的开头。如果指针np不为NULL,就说明其所指向的那个 223 表中存在一组符合清楚要求的变量名和定义;此时,指针np指向将被清除的那个数据项,而指针prev则指向出现 224 在np位置之前的那个数据项。如果prev是NULL,就说明np是以hashtab[h]开头的那个链表的第一个元素 225 if (prev == NULL) 226 hashtab[h] = np->next; 227 else 228 prev->next = np->next; 229 在清除了np所指向的数据项之后,我们还要通过free函数把该数据项的名字、定义及存储结构都是放。 230 */ 231 232 233 // 函数ungets,将整个字符串s压回到输入中。 234 //ungets: push string back onto the input 235 void ungets(char s[]) 236 { 237 int len = strlen(s); 238 while (len > 0) 239 ungetch(s[--len]); 240 } 241 242 /* 243 ungets函数不需要直接对buf和bufp进行操作,buf、bufp和出错检查由函数ungetch处理 244 */
6.7 类型定义(typedef)
C语言提供了一个称为typedef的功能,它用来建立新的数据类型名,例如。声明 typedef int Length;
将Length定义为与int具有同等意义的名字。类型Length可用于类型声明、类型转换等,它与类型int完全相同,例如:
Length len, maxlen;
Length *lengths[ ];
类似的声明
typedef char* String;
将String定义为与char *或字符指针同义,此后,便可以在类型声明和类型转换中使用String,例如:
String p, lineptr[MAXLINES], alloc(int);
int strcmp(String, String);
p = (String) malloc(100);
注意,typedef中声明的类型在变量名的位置出现,而不是紧接在关键字typedef之后。typedef在语法上类似存储类 extern、static等。这里以大写字母作为typedef定义的类型名的首字母,以示区别。
复杂例子:
1 // 用typedef定义本章节介绍的树节点 2 3 typedef struct tnode *Treeptr; 4 5 typedef struct tnode { //the tree node: 树节点 6 char *word; //points to the text 指向文本的指针 7 int count; //number of occurrences 反复次数 8 struct tnode *left; //left child 9 struct tnode *right; //right child 10 } Treenode; 11 12 //上述类型定义创建了两个新类型关键字:Treenode(一个结构)和Treeptr(一个指向该结构的指针) 13 //函数talloc可以修改为 14 15 Treeptr talloc(void) 16 { 17 return (Treeptr) malloc(sizeof(Treenode)); 18 } 19 20 /* 21 这里必须强调的是,typedef声明并没有创建一个新类型,它只是为某个已存在的类型增加了一个 22 新的名称而已。typedef声明也没有增加任何新的语义:通过这种方式声明的变量与通过普通声明方式 23 声明的变量具有相同的属性。实际上,typede类似于#define语句,由于typedef是有编译器解释的, 24 因此它的文本替换功能超过预处理器能力。 25 */ 26 27 typedef int (*PFI) (char *, char *); 28 /*该语句定义了类型PFI是“一个指向函数的指针,该函数具有两个char *类型的参数,返回值类型为int”, 29 它可用于某些上下文中,例如,第五章的排序程序中*/ 30 31 PFI strcmp, numcmp; 32 /* 33 除了表达方式更简洁之外,使用typedef还有另外两个重要原因。它可以是程序参数化,以提高程序的可 34 移植性。如果typedef声明的数据类型同机器有关,那么,当程序移植到其他机器上时,只需要改变typedef 35 类型定义就可以了。一个经常用到的情况是,对于各种不同大小的整型值来说,都使用通过typedef定义的类型名 36 然而,分贝为各种不同的宿主机选择合适的short、int、和long类型大小即可。标准库中有一些例子,标准库 37 中有一些例子,例如size_t和otrdiff_t等。 38 typedef的第二个作用是为程序提供更好的说明性--Treeptr类型行显然比一个声明为指向复杂就够的指针 39 更容易让人理解。 40 */
6.8 联合
联合是可以(在不同时刻)保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对其要求。联合提供了一种方式,以在单块存储区中管理不同类型的数据,而不需要在此程序中嵌入任何同机器有关的信息。
假设一个常量可能是int、float或字符指针。特定类型的常量值必须保存在合适类型的变量中,然而,如果该常量的不同类型占据相同大小的存储空间,且保存在同一个地方的话,且保存在同一个地方的话,表管理将最方便。这就是联合的目的--一个不变可以合法地保存多种数据类型中任何一种类型的对象。其语法结构如下:
union u_tag {
int ival;
float fval;
char *sval;
} u;
变量u必须足够大,以保存这3中类型中最大的一种,具体长度同具体的实现有关。这些类型中的任何一种类型的对象都可以复制给u,且可使用在随后的表达式中,但必须保证是一致的:读取的类型必须是最近一次存入的类型。程序员负责跟踪当前保存在联合中的类型。如果保存的类型行与读取的类型不一致,其结构取决于具体实现。
可以通过语法访问量和的成员:
联合名.成员
或
联合指针->成员
它与访问结构的方式相同。如果用变量utype跟踪保存在u这种的当前数据类型,则可以像下面这样使用联合:
1 //使用联合的办法 2 #include <ctype.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 union u_tag { 7 int ival; 8 float fval; 9 char *sval; 10 } u; 11 12 main() 13 { 14 int utype; 15 if (utype == INT) 16 printf("%d\n", u.ival); 17 if (utype == FLOAT) 18 PRINTF("%f\n",u.fval); 19 if (utype == STRING) 20 PRINTF("%s\n", u.sval); 21 else 22 printf("bad type %d in utype\n", utype); 23 } 24 25 //联合可以使用在结构和数组中,繁殖亦可。访问结构中的联合(或反之)的某一成员的表示法与嵌套结构相同。 26 27 #define NSYM 10 28 struct { 29 char *name; 30 int flags; 31 int utype; 32 union { 33 int ival; 34 float fval; 35 char *sval; 36 } u; 37 } symtab[NSYM]; 38 39 //访问ival 40 41 symtab[i].u.ival 42 43 //也可以通过下列语句之一引用字符串sval的第一个字符: 44 45 *symtab[i].u.sval 46 symtab[i].u.sval[0]
实际上,联合就是一个结构,他的所有成员相对于基础地址的偏移量都为0,此结构空间要大到,足够容纳最“宽”的成员,并且,其对齐方式也适合于联合中所有类型的成员。对联合允许的操作与对结构允许的操作相同:作为一个整体单元进行赋值、复制、取地址及访问其中一个成员。
联合只能用其第一个成员类型的值进行初始化,因此,上述联合u只能用整数值进行初始化。
6.9 位字段
在存储空间很宝贵的情况下,有可能需要将多个对象保存在一个机器字中。一种常用的方法是,使用类似于编译器符号表的单个二进制位标志集合。外部强加的数据格式(如硬件设备接口)也经常需要从字的部分值中读取数据。
考虑编译器中符号表才做的有关细节。程序中的每个标识符都有与相关的特定信息,例如,他是否为关键字,它是否是外部的且(或)是静态的,等等。这些信息进行编码最简洁的方法就是使用一个char或int对象中的位标志集合。
通常采用的方法是,定义一个与相关位的位置对应的“屏蔽码”集合,如:
#define KEYWORD 01
#define EXTRENAL 02
#define STATIC 04
或
enum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };
这些数字必须是2的幂。这样,访问这些位就变成了用第二章中描述的移位运算、屏蔽运算以及补码运算进行简单的位操作。
flags |= EXTERNAL | STATIC;
该语句将flags中的EXTERNAL 和 STATIC 位置为1,而下列语句:
flags &= ~(EXTERNAL | STATIC);
将他们置为0.并且,当这两位都为0时,下列表达式:
if ((flags & (EXTERNAL | STATIC)) == 0) 的值为真。
尽管这些方法很容易掌握,但是,C语言仍然提供了另一种可代替的方法,即直接定义和访问一个字中的位字段的能力,而不需要通过按位逻辑运算符。位字段(bit-field),或简称字段,是“字”中相邻的集合。“字”(word)是单个的存储单元,它同具体的实现有关.例如,上述符号表的多个#define语句可用下列3个字段的定义来代替:
struct {
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_staticc : 1;
} flags;
变量flags包含3个一位的字段。冒号后的数字表示字段的宽度(用二进制为数表示)。字段被声明为unsigned int类型,以保证他们是无符号量。
单个字段的引用方式与其他结构成员相同,例如:flags.is_keyword等等。字段的作用与小整数相似。同其他整数一样,字段可出现在算术表达式中。上面的例子可用更自然的方式表达式
flags.is_extern = flags.is_static = 1;
该语句将is_extern与is_static位打开。
flags.is_extern = flags.is_static = 0;
将is_extern和is_static位置为关闭。
if (flags.is_extern == 0 && flags.is_static == 0)
用于对is_extern和is_static位进行测试。
字段的所有属性几乎都同具体的实现有关。字段是否能覆盖字边界由具体的实现定义。字段可以不命名,无名字段(只有一个冒号和宽度)起填充作用。特殊宽度0可以用来强制在下一个边界上对齐。
某些机器上字段的分配是从字的左端至右端进行的,而某些机器上则相反。这意味着,尽管字段对维护内部定义的数据结构很有用,但在选择外部定义数据的情况下,必须仔细考虑哪端优先的问题。依赖于这些因素的程序是不可移植的。字段也可以仅仅声明为int,为了方便移植,需要显示声明该int类型是signed还是unsigned类型。字段不是数组,并且没有地址,因此对他们不能使用&运算符。

浙公网安备 33010602011771号