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类型。字段不是数组,并且没有地址,因此对他们不能使用&运算符。

  

  

  

  

  

 

  

  

posted @ 2020-01-09 21:07  C,python,linux,java  阅读(310)  评论(0)    收藏  举报