当前位置:网站首页>>电脑技术>>C、C++语言>>我的著作 双击自动滚屏
C语言速成(第七章 联合、枚举和自定义数据类型)

发表日期:2006年1月1日      已经有10146位读者读过此文

第七章  联合、枚举和自定义数据类型

    在这一章里,将要介绍C语言中的另两种构造类型──联合和枚举,并介绍自定义数据类型符 typedef 的使用。

第一节  联合

    联合是C语言中又一种构造类型,它的形式与结构比较相似,它是一个特殊形式的结构。在结构中各成员有各自的存储区域,而联合则不同,它把不同类型数据放在存储器相同的位置中。也就是说,联合里的各成员占用的存储区域是相互重叠的,通过联合中不同的成员可以用不同的方式访问同一块存储区域。在任何给定的时刻,仅有一个成员驻留在联合中,这和结构也是不同的。
    定义一个联合的一般形式为:

        union <联合名> { <联合成员表> } <联合变量> ;

    例如:

        union key {
           char ch[2];
           int i;
        } c;

就定义了一个联合变量,其联合名为 key,联合变量为 c,联合中有两个成员,一个是整形变量 i,一个是字符型数组 ch,它们在内存中重叠地占用两个字节的空间,i 的地址和 ch 的起始地址是一样的,并且,这个地址和整个联合 c 的地址也是一样的。和形式相似的结构比较一下,就不难看出它们之间的区别。如果有一个结构:

        struct key {
           char ch[2];
           int i;
        } d;

则这个结构的两个成员在内存里就要占用4个字节的存储空间。联合的变量可以是基本变量,也可以是数组或指针。联合成员的引用和结构相同,即用点操作符或箭头操作符。直接对联合操作可使用点操作符,如果通过指针访问联合变量,则使用箭头操作符。例如有一个联合:

        union add {
           int j;
           char c;
           union key *p;
        } k;

k.j、k.c 都是联合 add 的成员,k.p->ch[0]、k.p->i 都是联合 key 的成员。必须注意,由于联合中各成员共享同一个存储区,所以在任何时刻只能引用它的一个成员。

    例8.1  编制一个测试键盘扫描码的程序。

    #include "bios.h"                  /* 用预处理命令指定一个包含文件 */
    main()                             /* 主函数 */
    {
      union key {                      /* 定义一个联合变量 */
        unsigned char ch[2];
        int i;
      } c;
      for(;;) {                        /* 为连续测试设置的循环 */
        printf("请按键(按ESC键退出)\n");  /* 屏幕打印操作提示 */
        c.i=_bios_keybrd(0);           /* 接收一个按键值赋给联合成员 */
        if(c.ch[0]==27)  return;       /* 如果按键为 ESC 键,退出 */
        printf("高位:%d      低位:%d\n",c.ch[1],c.ch[0]); /* 屏幕打印扫描码 */
      }
    }

    这个程序不但可以用来测试单键,而且可以用来测试如 Ctrl-W、Alt-U 等复合键。程序里调用了定义在 Microsoft C包含文件 bios.h 里的库函数 _bios_keybrd(int cmd),它通过调用 BIOS INT16H 中断执行各种键盘操作(在 Turbo C中也有类似的函数 bioskey)。当参数 cmd=0 时,返回按键的16位扫描码。如果扫描码低8位不为0,则为 ASCII 码;如果低8位为0,则高8位为扩展键代码,用以代表一些特殊键和组合键。
    在上述程序中,用 _bios_keybrd(0) 取出按键的扫描码后,依靠联合 key 把高位和低位分开。_bios_keybrd(0) 返回16位扫描码的长度和一个整形变量的长度是一样的,因此把它赋值给联合中的一个整形变量 c.i,由于另一个成员 c.ch[2] 也占用着和 c.i 相同的这两字节共16位存储区,因此,我们就能够从 c.ch[0] 得到这16位扫描码的低8位,从 c.ch[1] 得到高8位了。 程序中的  for(;;)  循环省略了循环条件表达式,它和前面例7.6中的 while(1) 一样,是一个无限循环。在这类循环里必须至少有一个符合一定条件跳出循环的“出口”。
    对联合的初始化和结构不同,它只对成员表中第一个元素初始化。在使用联合时,联合中各成员的长度最好相同,如例8.1中的 c.i 和 c.ch[2] 长度均为2个字节,它们可以相互完全覆盖。当联合中各成员长度不同时,采用最长的成员的字节数在内存里存放联合,也就是说,当定义一个联合时,编译程序自动建立一个可保存联合中最大成员的变量。
    联合可以作为结构的成员或数组的元素,反过来,结构和数组也可以作为联合的成员。如:

        union data {
           char s[2];
           int i;
           struct person a;
           union key p;
        } x,y[10];

以及:

        struct person {
           int i;
           char name[10];
           union key a;
        };

都是合法的定义方式。
    在联合定义时,联合名也可以省略,但必须同时定义联合变量。和结构的情况相类似,老的C语言版本规定只有指向联合的指针才能作为函数的参数,函数不能返回一个联合,但可以返回指向联合的指针。在 ANSI C标准里,这些禁区已经被打破了。把联合变量定义为指针,即为联合指针,它初始化后指向联合所在存储区的首地址。
    下面介绍一个定义在包含文件 dos.h 里的联合 REGS:

    union REGS {
       struct WORDREGS {
          unsigned int ax,bx,cx,dx,si,di,cflag;
       } x;
       struct RYTEREGS {
          unsigned char al,ah,bl,bh,cl,ch,dl,dh;
       } h;
    };

这个联合是在使用某些中断调用库函数时用于八位和十六位寄存器传值的。该联合中包括两个结构,结构 WORDREGS 存放八位寄存器的值,结构变量为 x;结构 BYTEREGS 存放十六位寄存器的值,结构变量为 h。
    例8.2是一个应用库函数 int86 调用第 10H 号中断第2号功能给光标定位的例程。

    例8.2  设计一个光标定位函数。

    #include "dos.h"                   /* 用预处理命令指定一个包含文件 */
    locate(y,x)                        /* 光标定位函数 */
    int y,x;                           /* 形参说明 */
    {
       union REGS r;                   /* 说明一个联合变量 r */
       r.h.ah=2;                       /* 功能号 2 赋给 AH 寄存器 */
       r.h.dl=x;                       /* 光标列坐标赋给 DL 寄存器 */
       r.h.dh=y;                       /* 光标行坐标赋给 DH 寄存器 */
       int86(0x10,&r,&r);              /* 调用库函数 int86,0x10 为中断号 */
    }

    在这个例程中,库函数 int86 带有三个参数,第一个参数为调用的中断号,后两个参数分别为存放调用和返回值的联合变量指针。本例中调用和返回参数都放在同一个联合变量r 里。程序中取联合中结构成员的方法,和第七章里讲到的结构成员为另一个结构时多次运用取成员运算符的方法是一样的。因为库函数 int86 和联合 REGS 都定义在包含文件dos.h 里,在函数 locate 编译前必须用预处理命令 #include 嵌入包含文件 dos.h。

第二节  枚举

    在一些较新的C语言版本中设置了枚举类型。在我们处理问题时,常常遇到这样的情况,一个变量的特性有时是由几个十分确定的量中的一个来决定的。例如,性别上的“男”和“女”,考核的“优秀”、“良好”、“及格”、“不及格”等等。对这种有限集合的问题用枚举来处理就比较容易。
    定义枚举的一般格式为:

        enum < 枚举名 > { < 枚举常量表 > } < 枚举变量 >;

    例如,在人事统计程序里可以定义一个枚举变量:

        enum peaple { male,female } sex=male;

    其中,peaple 为枚举类型名(有时可以省略);male 和 female 是枚举常量,male 代表“男”,female 代表“女”;sex为枚举变量,在定义时把枚举常量 male 作为 p 的初值。当然,定义枚举类型和定义枚举变量也可以分开进行。如:

        enum peaple { male,female };
        enum peaple sex=male;

    枚举定义的格式看上去有点象结构,但它和结构是不同的。结构体中的各成员是不同类型的变量,而枚举花括号间的各成员则为枚举常量,在编译过程中,编译程序会分别各它们一个确定的整形量,因此,枚举类型是针对整形数的。如果对枚举成员在说明时没有另给一个整形值,则编译程序自动地把第一个枚举成员的值置为0,以后的成员依次加1。如上面的枚举 peaple 中,male 值为0,female 的值为1。没有必要专门说明枚举常量的类型是 int,因为这是由编译程序自动完成的。
    不过也允许在定义一个枚举时对各成员另行指定整形量,这个整形量可以是正整数、负整数或0。如:

        enum color { blue=1,green,yellow=blue+5 } c;

    在上面的表达式中,blue 的值被置为1;green 没有赋值,它的值比前一个枚举常量大1,因此,green 的值为2;yellow 的值被确定为6。允许一个枚举不同的成员置成相同的数值。枚举被定义后,不允许再对枚举常量重新赋值,必须记住,一旦定义后,枚举成员就是常量了,对常量是不能赋值的。如果再出现 green=5; 的赋值语句就是非法的了。枚举常量可用于用关系运算符进行的比较,如:

        if(c<green) {
          ......
        }

    枚举不能作为一个整体进行运算或操作,只能对它的成员进行操作。枚举变量的引用直接使用枚举变量名,枚举成员的引用直接用成员的名称。如:

        c=blue;
        sex=female;

    要注意,虽然 blue 的值为1,但是给枚举变量 c 赋值时,若写成 c=1; 则是非法的。对枚举变量的赋值仅限于枚举常量表中列出的用标识符表示的枚举成员。不过,有时候可将一个整数通过强制转换对枚举变量赋值,如:

        c=(enum color)1;

表示将顺序号为1的枚举成员 green 赋给 c。
    在使用枚举时要保证在其作用范围内枚举类型名、枚举常量标识符和枚举变量名都不相同,而且与该作用域中的其他变量名或元素名也不相同。枚举类型可以作为结构的成员,也可以作为函数的参数。下面是一个使用枚举的例子。

    例8.3  将例7.1程序中的性别数据改用枚举进行处理。

    #include "stdio.h"                 /* 用预处理命令指定包含文件 */
    struct person {                    /* 定义一个结构 */
      char name[10];
      enum { male,female } sex;
      int age;
      float sal;
    }
    main()                             /* 主函数 */
    {
      char c;                          /* 定义一个字符型变量 */
      struct person p;                 /* 定义一个结构变量 */
      printf("姓名:");                 /* 屏幕打印输入提示 */
      scanf("%s",p.name);              /* 键盘输入字符串,赋给结构相应成员 */
      newline();                       /* 调用函数 newline */
      while(1) {                       /* 为在输入字符不对时重输设的循环 */
        printf("性别(请输入 m 或 f):");/* 屏幕打印输入提示 */
        scanf("%1c",&c);               /* 键盘输入一个字符放入 c */
        newline();                     /* 调用函数 newline */
        if(c==''''''''m'''''''' || c==''''''''f'''''''') break;    /* 如输入 m 或 f,退出循环 */
      }
      if(c==''''''''m'''''''') p.sex=male;           /* 如果输入字符为 m,
                                            把 male 赋给结构中的联合成员 */
      else p.sex=female;               /* 否则把 female 赋给该成员 */
      printf("年龄:");                 /* 屏幕打印输入提示 */
      scanf("%d",&p.age);              /* 键盘输入一个整数赋给结构相应的成员 */
      newline();                       /* 调用函数 newline */
      printf("工资:");                 /* 屏幕打印输入提示 */
      scanf("%f",&p.sal);              /* 键盘输入浮点数赋给结构相应成员 */
      newline();                       /* 调用函数 newline */
      printf("姓名:%s  性别:%s  年龄:%d  工资:%6.2f",
                     p.name,(p.sex==male)"男":"女",p.age,p.sal);
                                       /* 屏幕打印输出结构中的各数据 */
    }
    newline()                          /* 吃掉 scanf 输入的多余字符和回车符 */
    {
      while(getchar()!=''''''''\n'''''''');          /* 输入的不为回车符则继续循环 */
    }
   
    在程序里,printf 打印的参数中有一个表达式 (p.sex==male)"男":"女" 用到了三目运算符“  : ”,括号里的是条件判断式,当判断为“真”时,打印“男”字,否则打印“女”字。

第三章  自定义数据类型

    自定义数据类型是通过关键词 typedef 来定义用户自己的数据类型,它把某一标识符规定成具有类型说明符所说明的数据类型。它的一般格式为:

        typedef <类型说明符> <标识符>

    例如:

        typedef int INTE;

把 INTE 规定为和 int 等价的数据类型。在后面的定义语句中如果使用 INTE i; 就等同于使用 int i;。又如:

        typedef int *WORK;           /* 把 WORK 定义为指向 int 的指针类型 */
        typedef struct key {
                        char ch[2];
                        int i;
                 } KY;               /* 把 KY 定义为 struct key 类型 */
        typedef char ARR[5];         /* ARR 定义为包含5个元素的字符数组类型 */
        KY x;                        /* 相当于 struct key x; */
        ARR S;                       /* 相当于 char s[5]; */

    自定义数据类型并不产生新的数据类型,但可以用它产生现有数据类型的复合类型。例如有一个多级指针变量,用自定义符 typedef 进行变换,如果有:

        typedef char *B;             /* 把 B 定义为指向 char 的指针;*/
        typedef B *BB;               /* 把 BB 定义为指向 B 的指针; */
        typedef BB *BBB;             /* 把 BBB 定义为指向 BBB 的指针。*/

则多级指针

        char ***p;

可以改写成

        BBB p;

再如:

        typedef char CH[10];

把 CH 定义成“长度为10的字符数组”的同义词。

        CH ch;

就等同于

        char ch[10];
而自定义数据类型表达式:

        typedef char *FUNC();

把 FUNC 定义为“返回一个指向 char 的指针值的函数”的同义词。此后如果定义 

        FUNC func;

就等同于:

        char *func();

    在自定义数据类型时,新的类型标识符用大写或小写字母都是允许的,但为了醒目,习惯上常常用大写字母来表达。
    自定义类型的用处在于:一是把很长的复杂的定义进行压缩,使得以后的书写更为简便。有时把新的类型名取成和对象性质相关的标识符,有利于理解。
    另外,采用自定义类型还有利于程序的移植。当一种数据类型在不同的计算机上长度不同时,在程序移植时,程序员需要更换某种类型符为另一种类型符,不必一一修改所有这种类型符,而只要修改自定义类型一处就可以了,这无疑是非常方便的。例如一个 long 类型变量在 IBM PC 机上占4个字节,如果要把程序移植到一台一个 int 变量占4个字节的计算机上,往往要把程序里所有定义变量的 long 改为 int,这时用自定义数据类型方法就非常方便。如果原来程序里有

        typedef long LG;

移植时只须将这一句改成

        typedef int LG;

就可以了。


相关专题: 我的著作
专题信息:
  C语言速成(第三章 程序控制语句)(2006-1-1 13:51:28)[9449]
  C语言速成(第六章 结构)(2006-1-1 13:52:05)[8854]
  C语言速成(第五章 指针)(2006-1-1 17:28:54)[4604]
  C语言速成(第四章 数组)(2006-1-1 13:52:42)[4760]
  C语言速成(第二章 常量、变量、运算符与表达式)(2006-1-1 13:53:05)[7271]

相关信息:
 没有相关信息
  打印本页
设为首页 | 加入收藏 | 联系我们 | 管理入口
皖ICP备05018956号
Copyright © 2003 J.W.SHEN All Rights Reserved
后台管理系统 V1.0 制作