第8章结构体与联合体8.1程序与程序文件8.2结构体数组8.3结构体与指针8.4链表8.5联合体8.6枚举类型与自定义类型名8.7程序举例8.1程序与程序文件8.1.1结构体类型变量的定义定义结构类型变量包括两个方面:首先要定义结构体类型,以便确定该类型中有哪些成员,各成员属于什么数据类型;然后再定义属于该结构体类型的变量。1.定义结构体类型定义结构体类型的一般形式如下:struct结构体类型名{成员表};其中在“成员表”中定义了该类型中有哪些成员,各成员属于什么数据类型。2.定义结构体类型变量定义结构体类型变量的一般形式为struct结构体类型名变量表;定义结构体类型与定义结构体类型变量是分开说明的。C语言还允许在定义结构体类型的同时定义结构体类型变量。其形式为struct结构体类型名{成员表}变量表;如果在函数体外定义了一个结构体类型,则从定义位置开始到整个程序文件结束之间的所有函数中均可定义该类型的变量;但在函数体内所定义的结构体类型,只能在该函数体内能定义该类型的变量。即结构体类型的定义与普通变量定义的作用域是相同的。8.1.2结构体类型变量的引用在程序中定义了某结构体类型的变量后就可以被引用。结构体变量的一般引用方式如下:结构体变量名.成员名其中“.”为结构体成员运算符,它的优先级最高。8.1.3结构体的嵌套C语言规定,结构体类型的定义可以嵌套。8.1.4结构体类型变量的初始化与普通变量一样,在定义结构体类型变量的同时也可以对结构体类型变量赋初值。但C语言规定,只能对全局的或静态的局部结构体类型变量进行初始化。为了将结构体类型变量定义为静态存储类型,在定义时应加上static关键字。但是,目前在大部分计算机系统中,对结构体类型变量初始化时不必加static关键字,其原理与普通数组的初始化一样。8.1.5结构体与函数1.结构体类型变量的成员作为函数参数与数组元素可以作为函数参数一样,结构体类型变量中的成员也可以作为函数参数。在这种情况下,在被调用函数中的形参是一般变量,而调用函数中的实参是结构体类型变量中的一个成员,但要求它们的类型应一致。2.结构体类型变量作为函数参数与一般变量可以作为函数参数一样,结构体类型的变量也可以作为函数参数。在这种情况下,在被调用函数中的形参是结构体类型的变量,调用函数中的实参也是结构体类型的变量,但要求它们属于同一个结构体类型。3.结构体类型的函数与定义标准数据类型函数一样,C语言也允许定义结构体类型的函数。结构体类型函数的返回值是结构体类型的数据。8.2结构体数组8.2.1结构体数组的定义与引用与整型数组、实型数组、字符型数组一样,在程序中也可以定义结构体类型的数组。但C语言规定,同一个结构体数组中的元素应为同一种结构体类型。例如,structstudent{intnum;charname[10];charsex;intage;floatscore[3];}stu[10];定义了“学生情况”型的一个数组stu,可存放10个学生的情况。每一个学生的情况包括:学号(num)、姓名(name[10])、性别(sex)、年龄(age)、3个成绩(score[3])。实际上,定义了该数组后,相当于开辟了一个如表8.1所示的表格空间。表8.1学生情况型的数组表格空间num学号name姓名sex性别age年龄score[0]成绩1score[1]成绩2score[2]成绩38.2.2结构体数组作为函数参数与普通数组一样,结构体类型数组也能作为函数参数,并且形参与实参结合的方式完全一样。如果在被调用函数中改变了结构体类型形参数组元素中各成员值,实际上也就改变了结构体类型实参数组元素中的各成员值。因为结构体类型形参数组与结构体类型实参数组是同一个存储空间。8.3结构体与指针8.3.1结构体类型指针变量的定义与引用结构体类型的指针变量指向结构体类型变量或数组(或数组元素)的起始地址。例如,structstudent{intnum;charname[10];charsex;intage;floatscore;};structstudentst1,st2,st[10],*p;其中定义了一个指向结构体“学生情况”型的指针p。由上所述,当结构体类型的指针变量p指向一个结构体类型变量后,下列3种表示是等价的:结构体变量名.成员(*p).成员p-成员它们都表示结构体变量中的一个成员。8.3.2结构体类型指针作为函数参数结构体类型指针可以指向结构体类型的变量,因此,当形参是结构体类型指针变量时,实参也可以是结构体类型指针(即地址)。在结构体类型指针作为函数参数的情况下,由于传送的是地址,因此,如果在被调用函数中改变了结构体类型形参指针所指向的地址中的值,实际上也就改变了结构体类型实参指针所指向的地址中的值。例8.7用结构体类型指针作为函数参数。在下面的程序中,主函数的功能是定义了一个结构体student型的变量st,同时为之初始化,然后输出变量st中各成员的值,将结构体类型变量st的地址(即&st)作为实参调用函数chang()后再输出变量st中各成员的值;函数chang()的功能是修改结构体类型形参指针t所指向的结构体类型数据中成员t-score的值,并输出修改前后结构体类型指针所指向的数据中各成员的值。#includestdio.hstructstudent{intnum;charname[10];charsex;intage;floatscore;};voidchang(t)structstudent*t;{printf(t=%6d%8s%3c%4d%7.2f\n,t-num,t-name,t-sex,t-age,t-score);t-score=95.0;printf(t=%6d%8s%3c%4d%7.2f\n,t-num,t-name,t-sex,t-age,t-score);}main(){staticstructstudentst={101,Zhang,'M',19,89.0};printf(st=%6d%8s%3c%4d%7.2f\n,st.num,st.name,st.sex,st.age,st.score);chang(&st);printf(st=%6d%8s%3c%4d%7.2f\n,st.num,st.name,st.sex,st.age,st.score);}结构体类型指针也可以指向数组或数组元素,因此,当形参是结构体类型指针变量时,实参也可以是结构体类型数组名或数组元素的地址。与标准数据类型的数组与指针一样,在结构体类型数组指针作函数参数时,也可以有以下4种情况:(1)实参与形参都用结构体类型数组名;(2)实参用结构体类型数组名,形参用结构体类型指针变量;(3)实参与形参都用结构体类型指针变量;(4)实参用结构体类型指针变量,形参用结构体类型数组名。8.4链表8.4.1链表的基本概念1.链表的一般结构链表由结点元素组成。为了适应链表的存储结构,计算机存储空间被划分为一个一个小块,每一小块占若干字节,通常称这些小块为存储结点。将存储空间中的每一个存储结点分为两部分:一部分用于存储数据元素的值,称为数据域;另一部分用于存放下一个数据元素的存储序号(即存储结点的地址),称为指针域。数据域指针域存放数据元素存放下一个结点元素的地址每一个结点的结构如图8.1所示。图8.1链表的结点结构在链表中,用一个专门的指针HEAD指向链表中第一个数据元素的结点(即存放第一个数据元素的存储结点的序号)。链表中最后一个元素后面已没有结点元素,因此,链表中最后一个结点的指针域为空(用NULL或0表示),表示链表终止。链表的逻辑结构如图8.2所示。数据1HEAD数据2数据nNULL…图8.2链表的逻辑结构2.结点结构体类型的定义在C语言中,定义链表结点结构的一般形式如下:struct结构体名{数据成员表;struct结构体名*指针变量名;};3.结点的动态分配在C语言中,可以利用malloc函数向系统申请分配链表结点的存储空间,其形式为malloc(存储区字节数)该函数返回存储区的首地址。例如,structnode{intd;structnode*next;};structnode*p;p=(structnode*)malloc(sizeof(structnode));释放存储区用如下函数:free(p);8.4.2链表的基本运算1.在链表中查找指定元素在对链表进行插入或删除的运算中,总是首先需要找到插入或删除的位置,这就需要对链表进行扫描查找,在链表中寻找包含指定元素值的前一个结点。当找到包含指定元素的前一个结点后,就可以在该结点后插入新结点或删除该结点后的一个结点。下面是在非空链表中寻找包含指定元素值的前一个结点的C语言描述。structnode/*定义结点类型*/{ETd;/*ET为数据元素类型名,下同*/structnode*next;};/*在头指针为head的非空链表中寻找包含元素x的前一个结点p(结点p作为函数值返回)*/structnode*lookst(head,x)ETx;structnode*head;{structnode*p;p=head;while((p-next!=NULL)&&(((p-next)-d)!=x))p=p-next;return(p);}2.链表的插入链表的插入是指在原链表中的指定元素之前插入一个新元素。要在链表中包含元素x的结点之前插入一个新元素b。其插入过程如下:(1)用malloc()函数申请取得新结点p,并置该结点的数据域为b。即令p-d=b。(2)在链表中寻找包含元素x的前一个结点,设该结点的存储地址为q。链表如图8.3(b)所示。(3)最后将结点p插入到结点q之后。为了实现这一步,只要改变以下两个结点的指针域内容:①使结点p指向包含元素x的结点(即结点q的后件结点),即令p-next=q-next②使结点q的指针域内容改为指向结点p,即令q-next=p图8.3链表的插入xHEAD0……(a)原来的链表xHEAD0……(b)申请得到结点p,在链表中找到包含元素x的前一个结点qbpqxHEAD0……(c)p插入到q之后bpq3.链表的删除链表的删除是指在链表中删除包含指定元素的结点。为了在链表中删除包含指定元素的结点,首先要在链表中找到这个结点,然后将要删除结点放回到可利用栈。要在链表中删除包含元素x的结点。其删除过程如下:(1)在链表中寻找包含元素x的前一个结点,设该结点地址为q。则包含元素x的结点地址p=q-next。(2)将结点q后的结点p从链表中删除,即让结点q的指针指向包含元素x的结点p的指针指向的结点,即令q-next=p-next(3)将包含元素x的结点p释放。此时,链表的删除运算完成。图8.4链表的删除xHEAD0……(a)原来的链表xHEAD0……(b)从链表中删除包含元素x的结点p后qp8.5联合体C语言中中的联合数据类型可以满足这种需要。联合体又称为共用体,意为各种不同数据共用同一段存储空间。与结构体类似,为了定义联合体类型变量,首先要定义联合体类型,说明该联合体类型中包括哪些成员,它们各属于何数据类型,然后再定义该类型的变量。定义联合体数据类型的一般形式为union联合体名{成员表};例如,unionw{intk;doubled;charc;};定义了一个联合体类型w,包括代表整型量的成员k、代表双精度型量的成员d和代表字符型量的成员c。下面对联合体类型变量作几点说明:(1)由于一个联合体变量中的各成员共用一段存储空间,因此,在任一时刻,只能有一种类型的数据存放在该变量中,即在任一时刻,只有一个成员的数据有意义,其他成员的数据是没有意义的。(2)在引用联合体变量中的成员时,必须保证数据的一致。(3)在定义联合体变量时不能为其初始化,并且,联合体变量不能作为函数参数。(4)联合体类型与结构体类型可以互相嵌套,即联合体类型可以作为结构体类型的成员,结构体类型也可以作为联合体类型的成