union ic
{
int i;
unsigned char ch[2];
};
main()
{ union ic u;
u.i=32767;
printf("u.ch[1]=%X,u.ch[0]=%X\n",u.ch[1],u.ch[0]);
u.i=32768;
printf("u.ch[1]=%X,u.ch[0]=%X\n",u.ch[1],u.ch[0]);
}
结果为:
u.ch[1]=7F,u.ch[0]=FF
u.ch[1]=80,u.ch[0]=0
由此可知一个int型数据的低字节存放其低8位,而高字节存放其高8位。这正是利用了无符号字符数组ch与整形变量i共享同一块存储空间的特点。
在使用联合类型数据时要注意以下一些特点:
l 在任一时刻,联合变量只有一个成员有意义,其它成员则无意义。正确地存取联合变量的某个成员是程序设计者的职责。
l 联合变量中有意义的成员即是其最后一次赋值的成员。
l 联合变量的地址和其各成员的地址相同。
l 不能在定义联合变量的同时对其进行初始化。
9.9 枚举类型
枚举类型是ANSI C新标准所增加的。如果一个变量只有几种可能的值,则可以定义其为枚举类型。所谓“枚举”是指将变量的可能取值一一列举出来,变量的值只限于列举出来的值的范围。定义枚举类型用保留字enum。其定义形式为:
enum [枚举类型名] {枚举常量名[=整形值], ...} [变量列表];
例如:
enum weekday {sun,mon,tue,wed,thu,fri,sat};
定义了一个枚举类型weekday。该枚举类型有7个枚举常量,分别是sun、mon、tue、wed、thu、fri和sat。和结构或联合类型一样,我们可以用枚举类型来定义相应的枚举变量。如:
enum weekday d1,d2; (enum不能省略)
定义了两个weekday型的枚举变量d1和d2,它们的值可以是sun到sat之一。例如:
d1=mon;
d2=sun;
我们也可以直接定义枚举变量,例如:
enum {red,yellow,blue} color1,color2;
同样,也可以同时定义枚举类型及枚举变量,例如:
enum color {red,yellow,blue} color1,color2;
枚举类型实际上是一个int型常量的“集合”。亦即每一个枚举常量实际上是一个int型常量,而枚举变量实际上是一个int型变量。使用枚举类型时要注意以下几点:
1) 上述的枚举常量sun,mon,red,blue等是int型常量,所以不能对它们赋值。
2) 枚举常量的值满足:第i个枚举常量的值 = 第i-1个枚举常量的值 + 1 。
3) 按C的缺省规定,第一个枚举常量的值为0。
4) 在定义枚举类型时,可以指定每个枚举常量的值。如不指定,则按第2条的规定决定其值。
5) 使不同的枚举常量具有不同的值是程序员的责任。
6) 枚举变量同int型变量,同样,使其值落在合法的枚举常量的范围之内也是程序员的责任。
7) 对枚举变量可进行同int型变量一样的运算和操作。
[例9.6]
#include enum color {red=3,green,blue=4};
enum workday {mon,tue,wed=7,thu,fri};
main()
{ enum color c1,c2;
enum workday d1,d2;
c1=red;
c2=++c1;
d1=mon;
d2=wed;
printf("c1=%d,c2=%d\n",c1,c2);
printf("d1=%d,d2=%d\n",d1,d2);
printf("red=%d,green=%d,blue=%d\n",red,green,blue);
printf("mon=%d,tue=%d,wed=%d,thu=%d,fri=%d\n",mon,tue,wed,thu,fri);
}
结果为:
c1=4,c2=4
d1=0,d2=7
red=3,green=4,blue=4
mon=0,tue=1,wed=7,thu=8,fri=9
使用枚举类型及枚举变量的目的是增加C程序的直观性和易读性。
9.10 用typedef定义类型
在C语言中,还可以用typedef来定义一个新的“类型名”。例如:
typedef int INTEGER;
就定义了一个新的类型名“INTEGER”,即使INTEGER与int成为同义词。此后 INTEGER 就能用在类型int能出现的任何地方,且作用完全相同。例如:
INTEGER i1,i2; 等价于 int i1,i2;
INTEGER a[10],*p; 等价于 int a[10],*p;
同样,
typedef char *STRING;
则定义了一个新类型名“STRING”,且其和类型(char *)成为同义词,即STRING为字符指针类型。以后我们就可以用STRING来定义字符指针变量。例如:
STRING p,lines[MAX_LINES]; 等价于 char *p,*lines[MAX_LINES];
注意,用typedef定义的新类型名并不是紧跟在保留字typedef之后,且具体的语法也不太容易用一个语法表达式来刻画。我们可以这样来描述typedef的用法:
如果要定义一个新类型名,则首先定义一个与之同名的“同类型”的变量,然后再在该“变量的定义”前面加上保留字typedef,则该“变量”名便上升成为“类型”名。例如:
struct {
int month;
int day;
int year;
} DATE;
定义了一个“结构变量”DATE,而
typedef struct {
int month;
int day;
int year;
} DATE;
则定义了一个相应的“结构类型”DATE。且
DATE date1,date2;
等价于
typedef struct {
int month;
int day;
int year;
} date1,date2;
可见,通过用typedef定义新类型可以简化程序的书写并提高程序的易读性。而程序的“易读性”是现代程序设计最重要的特性之一。他对程序的维护、功能扩充、移植等都具有举足轻重的作用。从前面的C的预处理程序到现在的类型名定义,可以看出C在这方面比别的程序设计语言有独到的设计,这也是C之所以风靡全球的原因之一。
为了和C的保留字以及变量名相区别,用typedef定义的类型名通常总是用大写英文字母来命名。但必须强调的是,typedef并不是“创建”了一个新的数据类型,它只是为现有的某些数据类型起了一个新的名字而已,并没有扩展C的数据类型。
typedef与#define有相似之处。但事实上,它们二者是不同的。主要区别在:
l typedef是C的“保留字”,即不能用其来命名变量等对象,但define并不是C的“保留字”,我们可以用其来命名变量,只不过不提倡这样做而已。
l #define是在预处理时处理的,它只能做简单的字符串替换,而typedef是在编译时处理的。实际上它并不是做简单的字符串替换,例如:
typedef int INTEGER; 在某些情况下可代之以 #define INTEGER int
但
typedef int NUM[10];
却无法以任何#define代之。
使用typedef有两个主要的理由:其一是有利于程序移植。如果把typedef用于可能与机器有关的数据类型,则在程序移植时仅需改变typedef,从而减少大量的程序修改工作。第二个原因是使用typedef能为程序提供更多的可读信息,用一个适当的符号名表示一个复杂的结构类型会增强程序的可读性。
习 题
9.1 填空
① C语言提供了三种构造数据类型,它们是 、 和 。
② 使几个不同类型的量共享同一段内存的结构,称为“ ”。
9.2 单项选择
① 说明语句
struct student
{
int num;
char name[20];
char sex;
};
定义了( )。
(1) 结构类型student (2) 联合类型student
(3) 结构变量student (4) 数组类型student
② typedef struct
{
int month;
int day;
int year;
} DATE;
定义了( )
(1) 宏名为DATE (2) 结构变量DATE
(3) 联合类型DATE (4) 新类型名DATE
③ typedef struct
{
int month;
int day;
int year;
} *PDATE;
定义了( )。
(1) 结构类型PDATE (2) 联合类型PDATE
(3) 宏PDATE (4) 指向结构的指针类型PDATE
④ union
{
int i;
char ch;
float f;
} a;
定义了( )。
(1) 联合类型a (2) 联合变量a
(3) 结构类型a (4) 结构变量a
⑤ 若定义
student node
{
int key;
struct node *next;
} a;
则引用结构变量a的next成员需要采用( )。
(1) a.next (2) node.next
(3) a->next (4) node->a.next
⑥ 若定义
enum color{red,yellow,blue,white};
那么yellow的值为( )。
(1) 0 (2) 1 (3) 2 (4) "yellow"
⑦ 若定义
enum color{red=2,yellow,blue,white};
那么blue的值为( )。
(1) 0 (2) 2 (3) 2 (4) 无定义
9.3 结构和联合有何区别?
9.4 设每个学生的数据包括学号、姓名、年龄、五门课的成绩。试说明相应的结构类型student。
9.5 设有50个学生的档案,数据结构同上,编制一个程序,它读入每个学生的档案数据,然后计算出每个学生的平均成绩和总成绩。最后将所有平均成绩高于总平均成绩的学生档案输出。
9.6 电话簿中每个人的数据由姓名和电话号码两项构成,试设计一个结构数组来表示电话簿,读入每个人的数据并按电话号码从小到大排序。然后等待用户输入一个电话号码,如果电话簿中有该号码,则输出相应用户的姓名,否则输出此号码是空号的信息。
9.7 建立一个链表,每个结点包括姓名和年龄。然后按年龄从小到大排序。
9.8 学生的数据包括学号、姓名、性别、民族、出生日期、家庭住址及本人简历。其中出生日期和本人简历又分别是一个结构,分别由年、月、日和起止年月、简历、证明人构成。试说明相应的结构。
9.9 要用计算机对图书馆中的图书进行管理,一本图书的信息包括:书名、作者、摘要、索引号、ISBN号、出版社、出版日期和页数。其中出版社包括出版社名称和地址。鉴于书和出版社之间是多对一的关系,为了减少数据的冗余,应该如何设计所需要的结构?
9.10 typedef int INTEGER;和宏替换#define INTEGER int有何区别?
9.11 枚举类型在C内部是如何表示的?引入它的主要目的是什么?
9.12 定义复数类型并分别写出完成复数加、减、乘、除运算的函数。
9.13 建立一个函数,它建立由siz个整数组成的动态数组,并能从键盘上读入数据,初始化这个数组。函数成功返回1,否则返回0。
9.14 设计一个函数,其参数是一个一维数组名和数组中元素的个数,函数的返回值是数组元素的个数。若数组元素的数据类型为
struct elem{
int key; /* 查询关键字 */
int freq; /* key的查询频率 */
}
设计这个函数,从键盘接收一个整数,在数组中查询该数是否存在,如果没有,就把它做为一个新项存入数组的key中。用freq记录查询频率。