【C语言学习笔记】《C程序设计语言》 第6章(结构)

Warning:
为了避免非零基础人群感到身体不适、头晕恶心、易怒及粗口,请不要查看以下内容。

本章我们将继续学习C语言关于结构的相关知识。

第6章 结构

结构是一个或多个变量的集合,这些变量可能为不同类型,为了处理的方便而将这些变量组织在一个名字之下。(某些语言将结构成为“记录”,比如Pascal语言。)由于结构将一组相关的变量看作一个单元而不是各自独立的实体,因此结构有助于组织复杂的数据,特别是在大型的程序中。
工资记录是用来描述结构的一个传统例子。每个雇员有一组属性描述,如姓名、地址、社会保险号、工资等。其中的某些属性也可以是结构,例如姓名可以分为几部分,地址甚至工资也可能出现类似的情况。C语言中更典型的一个例子来自于图形领域:点由一对坐标定义,矩形由两个点定义,等等。
ANSI标准在结构方面最主要的变化是定义了结构的赋值操作——结构可以拷贝、赋值、传递给函数,函数也可以返回结构类型的返回值。多年以前,这一操作就已经被大多数的编译器所支持,但是,直到这一标准才对其属性进行了精确定义。在ANSI标准中,自动结构和数组现在也可以进行初始化。

6.1 结构的基本知识

我们首先建立一些适用于图形领域的结构。点是最基本的对象,我们建立2个点(0,0)和(4,3):

struct point{
   int x;
   int y;
};

关键字struct引入结构声明。point称为结构标记,用于为结构命名。

结构中定义的变量成为成员。结构成员、结构标记和普通变量(即非成员)可以采用相同的名字,它们之间不会冲突。不同结构中的成员也可以使用相同的名字。

struct声明定义了一种数据类型。在标志结构成员表结束的右花括号之后可以跟一个变量表,这与其它基本类型的变量声明是相同的,例如:

struct { ... } x, y, z;

如果结构后面不带变量表,则不需要为他分配存储空间,它仅仅描述了一个结构的模板或轮廓。但是,如果结构声明中带有标记,那么在以后定义结构实例时便可以使用该标记定义。例如:

struct point maxpt = { 320, 200 };

自动结构也可以通过赋值初始化,还可以通过调用返回相应类型结构的函数进行初始化。

在表达式中,可以通过下列形式引用某个特定结构中的成员:

结构名.成员

其中结构成员运算符“.”将结构名与成员名连接起来。例如,可用下列语句打印点pt的坐标:

printf("%d,%d", pt.x, pt.y);

结构也可以嵌套。我们可以用对角线上的两个点来定义矩形,相应的结构定义如下:

struct rect{
    struct point pt1;
    struct point pt2;
};

结构rect包含两个point类型的成员。如果按照下列方式声明screen变量:

struct rect screen;

则可以使用语句

screen.pt1.x

引用screen的成员pt1的x坐标。

6.2 结构与函数

首先我们看一下函数makepoint,它带有两个整型参数,并返回一个point类型的结构:

struct point makepoint(int x, int y)
{
    struct point temp; 
    temp.x = x;
    temp.y = y;
    return point;
}

当结构作为函数的参数时,如果传递给函数的结构很大,使用指针方式的效率通常比复制整个结构的效率要高。结构指针类似于普通变量指针。声明:

struct point *pp;

将pp定义为一个指向struct point类型对象的指针。如果pp指向一个point结构,那么*pp即为该结构。而(*pp).x和(*pp).y则是结构成员。

结构指针的使用频度非常高,为了使用方便,C语言提供了另一种简写方式。假定p是一个指向结构的指针,可以用:

p->结构成员

这种形式引用相应的结构成员。这样,就可以用下面的形式改写上面的一行代码:

printf(“origin is (%d,%d)\n”, pp->x, pp->y);

6.3 结构数组

考虑到编写一个这样的程序,统计输入中各个C语言关键字出现的次数。我们需要用一个字符串数组存放关键字名,一个整型数组存放相应关键字的出现次数。

一种实现方法是,使用两个独立的数组keyword和keycount分别存放它们,如下所示:

char *keyword[NKEYS];
int keycount[NKEYS];

我们注意到,这两个数组的大小相同,考虑到该特点,可以采用另一种不同的组织方式,也就是我们这里所说的结构数组。每个关键字项包括一对变量:

char *word;
int count;

这样的多个变量对共同构成一个数组,我们来看下面的声明:

struct key {
   char *word;
   int count;
}keytab[NKEYS];

它声明了一个结构类型的key,并定义了该类型的结构数组keytab,同时为其分配存储空间。数组keytab的每个元素都是一个结构。

因为结构keytab包含一个固定的名字的集合,所以,最好将它声明为外部变量,这样,只需要初始化一次,所有的地方都可以使用。这种结构的初始化方法同前面所讲述的初始化方法类似——在定义的后面通过一个圆括号括起来的初值表进行初始化。如下所示:

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,
};

与结构成员相对应,初值也要按照成对的方式列出。更精确的做法是,将每一行的初值都括在花括号内。

{auto, 0},
{break, 0},
{case, 0},
...

但是,如果初值是简单的变量或字符串,并且其中的任何值都不为空,则内层的花括号可以省略。通常情况下,如果初值存在并且方括号[]中没有数值,编译程序将计算数组keytab中的项数。

C语言提供了一个编译时一元运算符sizeof,它可以用来计算任一对象的长度。表达式:

sizeof 对象

以及

sizeof (类型名)

将返回一个整型数,它等于指定对象或类型占用的存储空间字节数。(严格的说,sizeof的返回值是无符号整型值,其类型为size_t,该类型在头文件<stddef.h>中定义。)其中,对象可以是变量、数组或结构;类型可以是基本类型,也可以是派生类型,如:结构类型或指针类型。

6.4 指向结构的指针

为了进一步的说明指向结构的指针和结构数组,我们重新编写关键字统计程序。修改后的程序如下:

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define MAXWORD 100

int getwrod(char *, int)
struct key *binsearch(char *, struct key *, int);

/* 统计关键字的出现次数;采用指针方式实现的版本 */
main()
{
    char word[MAXWORD];
    struct key *p;
 
    while(getword(word, MAXWORD) != EOF)
        if(isalpha(word[0]))
            if((p=binsearch(word, keytab, NKEYS)) != NULL)
                   p->count++;
    for(p = keytab; p < keytab + NKEYS; p++)
        if(p->count > 0)
           printf("%4d %s\n"; p->count; p->word);
    return 0;
}

/* binsearch函数:在tab[0]...tab[n-1]中查找与读入单词匹配的元素 */
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-low) / 2;
          if((cond = strcmp(word, mid->word)) < 0)
               high = mid;
          else if (cond > 0)
               low = mid + 1;
          else 
               return mid;   
      }
      return NULL;
}

这里首先要注意几点,首先,binsearch函数在声明中必须表面:它返回的值类型是一个指向struct key类型的指针,而非整型,这在函数原型以及binsearch函数中都要声明。如果binsearch找到与输入单词匹配的数组元素,它将返回一个指向该元素的指针,否则返回NULL。

其次,keytab的元素在这里是通过指针访问的。这就需要对binsearch做较大的修改。

6.5 自引用结构

假定我们需要处理一个更一般化的问题,统计输入中所有的单词出现的次数。为了提高效率,我们采用二叉树的数据结构进行程序设计。

每个不同的单词都是一个节点,每个节点包含:

  • 一个指向该单词内容的指针
  • 一个统计出现次数的计数值
  • 一个指向左子树的指针
  • 一个指向右子树的指针

对节点的所有操作要保证,任何节点的左子树只包含按字典序小于该节点中单词的那些单词,右子树只包含按字典序排列大于该节点中单词的那些单词。

要查找一个新单词是否已在树中,可以从根节点开始,比较新单词与该节点中的单词。若匹配,则得到肯定的答案。若新单词小于该节点中的单词,则在左子树中继续查找,否则就在右子树中进行查找。

我们再来看节点描述问题。最方便的表示方式是表示为包括4个成员的结构:

struct tonde {                        /* 树的节点 */
      char *word;                     /* 指向单词的指针 */
      int count;                      /* 单词时出现的次数 */
      struct tonde *left;             /* 左子节点 */
      struct tonde *right;            /* 右子节点 */
};

这种对节点的递归的声明方式看上去好像是不确定的,但他的确是正确的。一个包含其自身实例的结构是非法的,但是,下列声明是合法的。

struct tonde *left;

它将left声明为指向tonde的指针,而不是tonde实例本身。

我们偶尔也会使用自引用的变体,即两个结构相互引用。例如:

struct t {
   ...
   struct s *p;    /* p指向一个s结构 */
};

struct s {
   ...
   struct t *q;   /* q指向一个t结构 */
};

这里看不懂了。。。暂时略过。。

6.6 表查找

初学阶段,暂时不深入研究。

6.7 类型定义(typedef)

C语言提供了一个称为typedef的功能,它用来建立新的数据类型名,例如,声明:

typedef int Length;

将Length定义为与int具有同等意义的名字。类型Length可用于类型声明、类型转换等,它和int类型完全相同。

注意,typedef中声明的类型在变量名的位置出现,而不是紧接在关键字typedef之后。typedef在语法上类似于存储类extern、static等。我们在这里以大写字母作为typedef定义的类型名的首字母,以示区别。

6.8 联合

联合是可以(在不同时刻)保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对齐要求。联合提供了一种方式,以在单块存储区中管理不同类型的数据,而不需要在程序中嵌入任何同机器有关的信息。它类似于Pascal语言中的变体记录。

我们来看一个例子(可以在编译器的符号表管理程序中找到该例子)。假设一个常量可能是int、float或字符指针。特定类型的常量值必须保存在合适类型的变量中,然而,如果该常量的不同类型占据大小相同的存储空间,且保存在同一个地方的话,表管理将最方便。这就是联合的目的——一个变量可以合法的保存多种数据类型中任何一种类型的对象。其语法基于进本结构,如下:

union u_tag{
   int ival;
   float fval;
   char *sval;
}u;

变量u必须足够大,以保存这3种类型中最大的一种,具体长度同具体的实现有关。这些类型中的任何一种类型的对象都可赋值给u,且可使用在随后的表达式中,但必须保证是一致的:读取的类型必须是最近一次存入的类型。程序员负责跟踪当前保存在联合中的类型。如果保存的类型于读取的类型不一致,其结果取决于具体的实现。

可以通过下列语法访问联合中的成员:

联合名.成员
或
联合指针->成员

它与访问结构的方式相同,如果用变量utype跟踪保存在u中的当前数据类型,则可以像下面这样使用联合:

if (utype == INT )
   printf("%d\n", u.ival);
else if (utype == FLOAT)
   printf("%f\n", u.fval);
else if (utype == STRING)
   printf("%s\n", u.sval);
else
   printf("bad type %d in utype\n", utype);

联合可以使用在结构和数组中,反之亦可。访问结构中的联合(或反之)的某一成员的表示法与嵌套结构相同。

实际上,联合就是一个结构,它的所有成员相对于基地址的偏移量都为0,此结构空间要大到足以容纳最"宽"的成员,并且,其对齐方式要适合于联合中所有类型的成员。对联合允许的操作与对结构允许的操作相同。

联合只能用其第一个成员类型的值进行初始化。

6.9 位字段

初学阶段,暂时不做讲解。

总结

本章我们学习了结构的相关知识。在C语言中,结构也是非常重要的一部分。接下来我们将学习最后一章“输入与输出”(由于暂时没接触linux相关知识,暂时不学习最后一章)。继续坚持,学完最后一章就对C语言的整体知识框架有了一定的了解。后续会对单独的知识点进行深入研究和灵活运用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值