第 11章结构体与共用体
1、结构体类型的定义
2、结构体变量的定义及引用
3、结构体数组
4、结构体类型的指针
5、链表的基本操作
6、共用体和枚举类型(了解)
结构体类型的引入
问题:为了描述一个事物的不同属性,需要用到各种 不同类型的数据,这些 数据彼此相关,形成一个有机的整体。例如:
一个教师的基本信息由姓名、性别、年龄、职称、工资等几项组合而成。如何描述一个教师的情况呢?
前面我们已学习过各种基本类型的变量和数组,而且我们知道,各个变量之间是相互独立的,无任何联系;而数组只能用来表示一批相同类型的数据。因此,若用单个变量分别表示教师的姓名、性别、年龄等属性,则难以反映他们之间的内在联系;若用数组,则根本无法表示,因为姓名、性别、
年龄等不属同一种数据类型。
C语言中用“结构体”来描述由多个不同类型的数据组成的数据集合。相当于其他高级语言中的“记录”,
11.1结构体类型的定义
与基本数据类型不同的是,结构体是又一种构造类型,是由多个类型的数据成员组合而来的。因此该类型的具体内容应根据需要先定义,后使用。
可以定义如下结构体类型来描述教师的基本情况:
struct teacher /*struct 是关键字 */
{char name[30]; /*{}内是该类型的各成员 */
char sex;
int age;
char position[10];
float salary;
}; /*语句末尾是,;” */
该结构体类型名为 struct teacher,teacher 是该结构体的标识符;
该类型包含有 6个成员的数据项,name,sex,age,position
和 salary,其中每个成员项都有自己的类型。
可见,定义一种新的结构体类型的一般形式是:
struct 结构体类型名
{成员类型 成员名;
……
成员类型 成员名;
};
其中,struct 是 关键字,结构体类型名、结构体成员名的命名规则同变量的命名规则一样。
特别提示,struct teacher只是一种具体的结构体类型,根据需要,程序员可以定义多个不同内容的结构体类型。其中的成员项是该类型的组成部分,而不是变量。
结构体类型的成员可以是基本类型的变量或数组,也可以是结构体类型的数据。即 结构体类型的嵌套定义 。
例如,若将教师的年龄改为出生日期,则可以将出生日期定义为一个结构体类型,然后嵌入 struct teacher中。
将出生日期单独定义为一个结构体类型后再嵌入。
Struct date_type
{ int year;
int month;
int day;
};
Struct teacher_2
{ char name[30];
char sex;
struct date_type birthday;
char position[10];
float salary;
} (常用 )
直接嵌入。
Struct teacher_3
{ char name[30];
char sex;
struct date_type
{int year;
int month;
int day;
} birthday;
char position[10];
float salary;
} /*成员 birthday又是一个结构体类型的数据 。 */
11.2结构体变量的定义及引用
经以上定义后,结构体类型 struct teacher与系统定义的类型 int,long,float 等一样,可以用它来定义该类型的变量、数组、函数等。
不同的是,结构体类型的作用范围是有限制的。在函数体内定义的结构体类型的作用域为本函数内,
在函数体外定义的结构体类型的作用域为本程序文件内,若要引用不在本文件内的结构体类型,
通常用 #include命令将定义该类型的文件包含进来。
例 11.1定义一个结构体变量,用于存放一个教师的信息,然后将其输出。
#include "stdio.h"
main()
{ struct teacher /*该类型的作用范围在本函数内 */
{ char name[30];
char sex;
int age;
char position[10]; float salary;
};
struct teacher person; /*定义结构体变量 person*/
strcpy(person.name,"wang li");
person.sex=‘f’; /*给各成员赋值 */
person.age=30;
strcpy(person.position,"middle");
person.salary=1600;
printf("\n name sex age position salary");
printf("\n%-10s %3c %5d %10s %8.2f",person.name,
person.sex,person.age,person.position,person.salary);
}
分析:
*本例中 结构体类型在函数体内定义,其作用范围在本函数体内。
* 先定义结构体类型,后定义结构体变量。
* 对结构体变量输入输出操作、或将基本类型的数据赋给结构体变量时,需分别访问各个基本类型的成员,不能整体赋值或输入输出。
如,printf(“%s%c%d%s%f”,person); 错!
person={“li li”,’f’,24,“primary”,1000}; 错!
例 11.2 定义一个结构体类型,包含通讯录中的如下信息:
姓名、年龄、电话、通讯地址;并定义该类型的变量,
输入数据并输出。
程序 l11_2.c
#include "stdio.h"
struct address_list /*在函数体外定义结构体类型 */
{char name[10]; /*该类型的作用域为本程序内 */
int age;
char tel[10];
char address[40];
};
main()
{ struct address_list s; /*定义变量 s,占空间 62个字节 */
printf("\n input name:");
gets(s.name);
printf(" input age:");
scanf("%d",&s.age);getchar();
printf("input tel:");
gets(s.tel);
printf("input address:");
gets(s.address);
printf(" name age tel address");
printf("\n%5s%6d%10s %-40s",s.name,s.age,s.tel,
s.address);
}
分析:
*结构体类型 address_list 放在函数体外定义,其作用范围为本程序文件内。
* 结构体变量的输入输出需逐个访问基本类型的成员,引用方法与同类型的变量、数组相同。
例 11.3 定义一结构体类型,包含工人的工资信息:编号、姓名、工资。并定义变量存放两人的数据,然后交换,输出交换前后的信息。
程序 l11_3.c
#include "stdio.h"
main()
{struct salary_list
{char no[10];
char name[30];
float salary;
} ;
struct salary_list z,x={"10","li ming",2000},
y={"12","wang yan",2500};
/*结构体变量的初始化,给出各成员的数据并用 {}括起来,
各成员之间用逗号分开 */
printf("\n no name salary");
printf("\nx:%-5s%-6s%10.2f",x.no,x.name,x.salary);
printf("\ny:%-5s%-6s%10.2f",y.no,y.name,y.salary);
z=x; x=y; y=z; /*交换两个结构体变量 */
printf("\nafter exchange:");
printf("\nx:%-5s%-6s%10.2f",x.no,x.name,x.salary);
printf("\ny:%-5s%-6s%10.2f",y.no,y.name,y.salary);
}
特别提示,同类型 的结构体变量可以整体赋值,即可以将一个结构体变量整体赋给相同类型的另一个结构体变量。
说明一、定义结构体变量
定义结构体变量的方法有三种,(经常用第一种):
方法一:先定义结构体类型,再定义结构体变量。
如,struct teacher t1,t2; (例 11.1)
方法二:定义结构体类型的同时定义结构体变量 (例 11.2) 。 又如:
struct course math
{ long no; 4B
char name[30];
} math,english; 30B
若以后还需用到此结构体类型,定义方法同 1:
struct course chemic;
方法三,格式同方法(二),其中类型名 course 省略,缺点是该类型无法再次引用 。
*结构体变量的存储:系统按结构体类型中各成员的类型给变量分配存储空间。如上图:
no
name
说明二、结构体变量的引用
结构体变量定义后,可以分两个层次引用:
*访问结构体变量的成员。
*引用整个结构体变量 。
1、使用成员运算符,.”引用结构体变量的成员,
,.”的优先级仅次于括号。
如例 11.1,11.2,11.3中的 person.name,person.age
x.no,s,salary等成员的赋值,输入、输出。
person.age++等价于( person.age)++
对结构体变量中的成员的操作与基本类型的变量完全相同。
注意:当结构体类型嵌套定义时,应逐级访问。
如 struct teacher_2 pp; pp.birthday.year=1999;
2,相同类型的结构体变量之间可以整体赋值。 如例 11.3中交换两个结构体变量的值。
11.2结构体数组
结构体数组用于保存一批同类型的结构体数据。每个数组元素相当于一个结构体变量。
例,11.4输入三门课的信息(课程编号、课程名),并输出。
分析:
* 每门课的信息包含 2项,用结构体类型表示,其成员有两项,即课程编号和课程名。
*三门课的信息,用一维数组保存。
输入:可以赋初值,也可由用户输入,本例中采用赋初值的方式。
程序 l11_2_1.c
说明:结构体数组的初始化同基本类型的一维数组,只不过每个数组元素是用 {}括起的一个结构体常量。
数组元素中每个成员的访问,同变量的成员的访问。
例 11.5输入 N个学生的学号、姓名、三门课的成绩,
计算每人的平均成绩,并按平均成绩由高到低排序,
输出排序后的成绩表。
分析:
* 每个学生的信息包括多项,用结构体类型表示,
其成员有学号、姓名、三门课的成绩、平均成绩。
N个学生的信息用一维数组保存。
* 算法,输入并计算平均成绩、排序、输出三步。
可将后面两项功能分别用函数实现,在主函数中调用。设排序函数为 sort(),输出函数为 output().
分析每个函数的具体组成:
函数类型 参数 函数体程序 l11_2_2.c
/*例 11.5程序 */
struct stu
{ char no[8];
char name[10];
float score[3];
float aver;
};
#include "stdio.h"
#include "stdlib.h"
#define N 3
#define STU_SC struct stu
main()
{void sort(STU_SC sc1[ ] ),output(STU_SC sc2[ ] );
STU_SC sc_list[N];
int i,j; char temp[10]; float sum;
printf("\n input no,name,score(1~3):");
for(i=0;i<N;i++)
{printf("\nstudent %d,\n",i+1);
sum=0;
printf("no:"); gets(sc_list[i].no);
printf("name:"); gets(sc_list[i].name);
for(j=0;j<3;j++)
{printf("score %d,",j+1); gets(temp);
sc_list[i].score[j]=atof(temp);
sum+=sc_list[i].score[j];
}
sc_list[i].aver=sum/3;
}
sort(sc_list);
output(sc_list);
}
void sort(STU_SC sc1[ ])
{int i,j,k; STU_SC t;
for(i=0;i<N-1;i++)
{ k=i;
for(j=i+1;j<N;j++)
if(sc1[j].aver>sc1[k].aver) k=j;
if(k!=i) {t=sc1[k];sc1[k]=sc1[i];sc1[i]=t;}
}
}
void output(STU_SC sc2[ ])
{ int i;
printf("\n no name score1 score2 score3 aver");
for(i=0;i<N;i++)
{ printf("\n %-8s %-10s",sc2[i].no,sc2[i].name);
printf("%8.2f %8.2f %8.2f %8.2f",sc2[i].score[0],
sc2[i].score[1],sc2[i].score[2],sc2[i].aver);
}
}
通过本例说明:
1、结构体数组中的每一个数组元素相当于一个同类型的结构体变量。
2、对数组元素的输入输出及运算均需对元素的基本类型的成员进行,只有同类型的数组元素才可以作为整体相互赋值。
3、结构体数据的成员通常类型各不相同,输入各成员数据时应注意:
4、关于成员数据的输入技巧:
*为了正确读入任意字符串,通常用 gets()输入字符串,而不用
scanf()。
*为了避免多余的回车符影响数据的正常输入,整型、实型的数据也用 gets()但作字符串读入,然后再用相应的函数转换成所需类型。
如,char t[20];
gets(t);
sc_list[i].score[j]=atof(t);
同理,atoi(t)则转换成整型。
*但输入单个字符时,通常用,ch=getchar(); getchar();
5、本例中,用结构体数组名作函数参数,传递的是数组的首地址,实现主、被调函数共用同一个结构体数组。
11.3指向结构体类型的指针
结构体变量在内存中占据一内存段,该内存段的首地址就是该结构体变量的指针。
通过指向结构体类型数据的指针变量可以间接的访问结构体变量,也可以使用指向结构体数据的指针变量访问结构体数组中的元素。
例 11.3.1定义一结构体类型,表示复数。定义该类型的变量和指针变量,用指针变量访问结构体变量。
分析:复数由实部和虚部组成,因此该结构体类型有两个成员,real,image。
L11.3.1程序:
main()
{ struct complex /*表示复数的结构体类型 */
{ float real;
float image;
};
struct complex x={2,3},*p;
p=&x; /*指针变量 p指向变量 x*/
printf("\n %f+%fi",x.real,x.image); p x
printf("\n %f+%fi",p->real,p->image);
} /*通过 p访问 x*/
运行结果,2+3i
2+3i /*两个输出结果相同 */
2
3
使用指向结构体数据的指针变量引用结构体成员的一般格式:
指针变量名 ->成员名
->为指向运算符,即取指针变量指向的结构体数据的成员。如:
p->real 等价于 (*p).real
其含义为:取指针变量 p指向的结构体数据的成员 real。
例 11.3.2使用指向结构体数组元素的指针引用数组元素:输入输出 N个复数。
分析:复数用结构体类型表示,输入时用下标法引用数组元素,
输出实用指针引用各元素。
注意,p++的含义是 p指向下一个数组元素。
为了引用已定义的结构体类型,通常将结构体类型名定义为更简单的宏名。
struct complex
{int real;
int image;
};
#define COMPLEX struct complex
#define N 5
main()
{ COMPLEX ss[N],*p; int i;
printf("\n input %d complex:\n",N);
for(i=0;i<N;i++) /*使用下标法引用元素,使用,.”引用成员 */
scanf("%d%d",&ss[i].real,&ss[i].image);
for(p=ss;p<ss+N;p++) /*使用指向运算符,->”引用成员 */
printf("\n %d+%di",p->real,p->image);
}
11.4动态内存分配与链表
动态内存分配的引入
问题:如果要在内存中保存一个班学生的数据,按照现有的手段,首先要知道该班的人数,然后据此定义数组,可是,
每一个班的人数并不完全相同,并且是浮动的,用确定长度的数组无法保存任意班级学生的数据,如果定义的数组太大,
造成内存浪费,太小将导致程序无法实现原功能要求。这显然不够理想和灵活。
解决以上问题的方法是使用动态内存分配:在程序运行中实时申请内存分配,申请到的内存在不用时还可以随时释放。
这样就可以根据每次运行程序时要处理的数据的多少随时申请内存,因此可以处理任意人数的数据。
用于动态内存分配的和释放的函数:
( 1) malloc()
函数原型,void *malloc(unsigned int size)
功能:申请分配 size字节的连续内存空间。若分配成功,
函数返回所分配内存的首地址;否则返回空指针
( NULL)。
( 2) calloc()
函数原型,void *calloc(unsigned int n,unsigned int size)
功能:申请分配 n各连续的内存空间、每个空间为 size字节。
若成功,返回所分配内存的首地址;否则返回空指针
( NULL)。
( 3) free()
函数原型,void free( void *p)
功能:释放以前分配到的、指针 p指向的内存空间。无返回值。
例 11.4.1编程实现:输入一批实验数据 (整型 ),计算其平均值。(个数由用户输入)
#include <stdio.h>
#include <stdlib.h>
main()
{int n,i,*p; float sum=0;
printf("\n input data's num:");
scanf("%d",&n);
p=(int *)malloc(sizeof(float)*n);
if(!p)
{printf("\n falure."); exit(0);}
printf("\n input %d integer:",n);
for(i=0;i<n;i++,p++)
{scanf("%d",p); sum+=*p;}
printf("\n aver,%f",sum/n); free(p);
}
程序分析:
特别提示,
( 1)使用分配失败是返回的空指针会造成程序的流产或系统的破坏,因此,程序中应先判断分配是否成功,然后再分别做出处理。
( 2)所分配的首地址应先强制转换为所需的类型,然后使用。
( 3)动态内存使用后应及时释放,否则,会导致内存资源的枯竭。
链表:是一种重要的数据结构,用来保存一批相同类型的结构体数据。与数组不同的是,链表中的各元素不要求连续存放,而且链表的长度事先不必确定。当要输入数据时,先向系统申请分配一个结构体数据的空间,因此实现链表时要用到动态内存分配技术。由于链表的组成元素 ---结点,在 C语言中是用结构体来实现的,所以链表和链表操作是结构体应用的重要实例。
本节讨论最简单的链表,单向链表。
单向链表,
头指针头结点 尾结点
链表结点:
结点是一结构体数据,其成员包括两部分:结点有效数据和结点指针(如图)。结点指针指向下一个结点,由此将所有结点“链”
在一起、形成一根数据的“链条”。尾结点的指针为 0,即空指针 NULL,表示链表的结束。
有效数据 结点指针
0
单向链表的基本操作:建立链表、插入 /删除结点、
释放结点或整个链表。
例 11.4 使用链表建立工人的工资单档案:编号、姓名、工资。并能及时添加记录、删除记录、插入记录、输出工资表。
分析:
首先定义每个记录的结构体类型:
struct work
{ long no;
char name[10]; /*有效数据,3项 */
float salary;
struct work *next; /*指向下一个结点的指针 */
};
一、建立单向链表:
功能:输入一个或多个结点数据(工人编号输入 0时结束),按输入的先后顺序生成链表。返回链表的头指针。
函数,struct work *create()
结点指针 head为头指针,指向第一个结点;指针 p1指向新输入的结点,p2指向链表的尾节点。 n表示结点的个数。
算法:首先输入一个结点数据,若该数据有效(即编号不等于 0),n加 1;然后将该结点加入链表:若 n等于 1,
则该结点应是头结点,即 head=p1; 否则,连接在尾结点后面,p2->next=p1; 然后 p2后移,p2=p1;使得 p2始终指向结点。重复以上过程。
申请一个结点空间并输入数据
struct work *input()
{ struct work *q; float x;
q=(struct work *)malloc(LEN);
if(!q){printf("\n failure.");exit(0);}
printf("\n input a worker's information(no=0 for
end):\n");
printf(" no:"); scanf("%ld",&q->no); getchar();
printf(" name:"); gets(q->name);
printf(" salary:"); scanf("%f",&x); q->salary=x;
getchar();
return(q); /*返回该结点的首地址 */
}
建立链表

返回头指针
struct work *create(void)
{ struct work *head,*p1,*p2;
int n=0;
p1=p2=input();
head=NULL;
while(p1->no!=0)
{ n++;
if(n==1) head=p1;
else p2->next=p1;
p2=p1;
p1=input(); /*输入下一个人的信息 */
}
p2->next=NULL;
return(head);
}
二、添加结点
功能:在链表的末尾追加多个结点,直至输入的结点编号等于 0为止。
函数,struct work *append(struct work *head)
算法:指针 p指向链表的尾结点,q指向新输入的结点。
重复执行,若链表为空,则 q指向的结点尾头结点;否则,
将 q链在 p之后 。
添加结点之链表尾
struct work *append(struct work *head2)
{struct work *q,*p=head2;
q=input(); q->next=NULL;
while(q->no!=0)
{ if(head2!=NULL)
{while(p->next!=NULL) p=p->next;
p->next=q;
}
else head2=p=q;
q=input();q->next=NULL;
}
return(head2);
}
三、输出链表各结点
算法:
指针 p指向链表的头结点,然后重复,若 p
指向的结点不为空,即链表未结束,则输出链表的结点成员; p指向下一个结点。
输出链表各结点
void out_list(struct work *head)
{struct work *p;
printf("\n no name salary\n");
p=head;
if(head==NULL) printf("\n no data.");
else
while(p!=NULL)
{ printf("\n %4ld %10s %7.2f",p->no,p->name,p-
>salary);
p=p->next; }
}
四、按编号删除结点
函数:
struct work *delete(struct work *head3,long no)
算法:
先判断链表是否为空,
若是,则结束;
否则,从头结点开始查找要删除的结点。若找到,
则删除,即将其从链表中去掉,并释放该结点;否则,
若到了链表的为结点仍未找到,则输出,not found”。
按编号删除结点
struct work *delete(struct work *head3,int no)
{ struct work *p,*q=head3;
if(head3==NULL) {printf("\n list null.");
return(head3);}
while(no!=q->no&&q->next!=NULL)
{p=q;q=q->next;}
if(no==q->no)
if(q==head3) head3=q->next;
else {p->next=q->next; free(q);}
else
printf("\n not found.");
return(head3);
}
在第 n个结点前插入一个结点
分析,变量 p指向第 n个结点,q指向第 n-1个结点,
即新结点插在 p和 q指向的结点之间。
分三种情况,1)插在头结点之前。
2)插在中间,t->next=p;q->next=t;
3)链表中无第 n个结点。
在第 n个结点前插入一个结点
struct work *insert(struct work *head,int n)
{ int i; struct work *p=head,*q,*t;
t=input();
if(n==1)
{t->next=head; head=t;return(head);}/*插在表头 */
for(i=1;i<n;i++)
if(p->next!=NULL) {q=p;p=p->next;}
else break;
if(i<n) printf("\n there is not %d jie dian ");
else if(t->no!=0)
{q->next=t;t->next=p;}
return(head);
}
主函数
#include <stdlib.h>
#include <stdio.h>
#define LEN sizeof(struct work)
main()
{ struct work *head;
long num; int n1;
head=create();
printf("\n append,"); head=append(head);
printf("\n insert:");
scanf("%d",&n1);
head=insert(head,n1);
printf("\n input no for del:"); scanf("%ld",&num);
head=delete(head,num);
out__list(head);
}
11,5 共用体
所谓共用体数据类型是指将不同的数据项组织为一个整体,它们在内存中占用首地址相同的一段存储单元 。 共用体也称为联合 。 例如:
union exam
{ int a;
float b;
char c;
}x;
共用体类型说明的一般形式为:
union 共用体标识名
{ 类型名 1 共用体成员名表 1;
类型名 2 共用体成员名表 2;

类型名 n 共用体成员名表 n;
};
其中 union是关键字,是共用体类型的标志。共用体中的成员可以是简单变量,也可以是数组、指针、结构体和共用体。
11,5,2共用体变量的定义及引用定义共用体变量的方式与结构体变量相似 。
1,定义类型的同时定义变量 。
union data
{ int i;
char ch;
float f;
}a,b,c;
2,将类型定义与变量定义分开 。
union data
{ int i;
char ch;
float f;
}; union data a,b,c;
3,直接定义共用体变量 。
union
{ int i;
char ch;
float f;
}a,b,c;
可以引用一个共用体变量中的某个成员,引用方式与引用结构体变量中的成员相似 。 例如,a.i=5; a.ch='b';
a.f=8.6;
特别提示,一个共用体变量不是同时存放多个成员的值,
某一时刻只能存放其中的一个成员的值,这就是最后赋予它的值 。 比如,执行上面三个赋值语句后,变量 a
中存的是实型数 8.6,如果此时执行语句
printf("%d,%c,%f",a.i,a.ch,a.f);
输出的分别是,a.f中最低两个字节( a.i的值)、最低字节的值( a.ch),a.f的值。
例 11,9:利用共用体的特点分别取出 int变量中高字节和低字节中的两个数 。
#include <stdio.h>
union word
{ char ch[2];
int n;
};
typedef union word WORD;
main()
{ WORD w;
printf("输入,");
scanf("%d",&(w.n));
printf("低位 =%d\n",w.ch[0]);
printf("高位 =%d\n",w.ch[1]);
}
运行结果:
输入,24930↙
低位 =98
高位 =97
11,6 枚举类型所谓“枚举”是指将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。例如:
enum
weekday{Sunday,Monday,Tuesday,Wednesday,Thu
rsday,Friday,Saturday};
说明:
1,enum是关键字,标识枚举类型,定义枚举类型必须用 enum开头。
2.在 C编译中,枚举类型中的元素按常量处理,
称为枚举常量,不能对它们赋值。
3.枚举常量是有值的,C语言按定义时的顺序定义它们的值为 0,1,2,…… ;也可以在定义时指定枚举常量的值。例如:
enum weekday(Sunday=7,Monday=1,
Tuesday,Wednesday,Thursday,Friday,Saturday);
指定枚举常量的值后,其后的元素的之依次递增,如定义
Sunday=7,Monday=1,则 Tuesday的值顺序加 1,即
Tuesday=2,Saturday=6。
4.枚举值可以用来作判断比较,按其值大小进行比较。
如:
if(today = = Monday) printf("Monday");
5.一个整数不能直接赋给一个枚举变量,应先进行强制类型转换才能赋值。例如:
若定义,enum weekday day;
则 day=(enum weekday)3; 等价于,day=Wednesday;
11,7 用 typedef定义类型