第 七章
结构体、共用体和枚举类型
7-1 结构体类型概述
结构体的概念
前面,我们介绍了基本类型 (或称简单类型 )的变量 (如整型,
实型,字符型变量等 ),也介绍了一种构造类型数据 ___数组。
但这些数据类型还很不够,为此,C语言提供了一种数据结构,
称为结构体 (structure)。 可以将不同类型的数据组合成一个有
机的整体,且 数据是互相联系的 。 它相当于其它高级语言中
的“记录”, 如下表所示,每一项称为结构体的成员或域。
num name sex age score addr
10010 Li Min M 18 87.5 Beijing
结构体的定义
一般格式,
struct 结构体名称
{
结构体成员列表
};
例,
struct person
{ int num;
int age;
char sex;
char name[8];
} ;
结构体名称
结构体成员
注意,;,不能少
结构体 person:表示
用户定义了一种 新数
据类型,用这种类型
的定义的每一个变量
将在内存中占用的空
间为各成员所占空间
之和,本例为,
2+2+1+8=13Byte
7-2 结构体类型变量的定义和引用
定义结构体变量的方法
先定义结构体,再定义结构体变量。
例:前面定义了结构体类型,person,可以定义结构
体变量如下,
struct person student,worker;
在定义结构体的同时,定义结构体变量。
例,struct person
{ int num;
char name[8];
}student,worker;
结构体类型标识符 结构体变量名
结构体变量名
结构体类型标识符
直接定义结构体类型变量,
例,struct
{int num;
char name[8];
}student,worker ;
没有类型名的结构体类型
注意:因以上结构体没有名称,所以不能用
struct men; 的方法定义结构体量。
结构体的嵌套
在定义结构体类型时,可以利用已定义的另一个
结构体类型来定义其成员。
例,
struct date{int month;
int day;
int year;};
struct person{int num;
char name[8];
struct date birthday; };
定义了名为 date
的结构体类型
birthday 是一个
结构体类型成员
定义了名为 date
的结构体类型
结构体变量的初始化
一般可在定义结构体变量的同时进行初始化,
例,
struct person student={“张小三,,
12,15,1982};
与数组不同,不能直接用常量对结构体变量进行
初始化,
例,
struct person student;
student={“张小三,, 12,15,1982};
结构体变量的引用
引用一级成员,
student.num
引用多级成员 (成员的成员 ),
student.birthday.year
结构体变量名 结构体变量的成员
结构体变量名 结构体变量的成员 成员的成员
可以将一个结构体变量的值作为一个整体赋给另
一个结构体变量。
结构体变量的输入、输出
C中不允许将结构体变量作为一个整体进行输入、
输出。
只能输入或输出最末级成员。
例,scanf(“%d”,student.num);
scanf(“%d”,student.birthday.year);
printf(“%d”,student.num);
printf(“%d”,student.birthday.year);
例 L7-1-0 输出结构体变量各元素。
例 L7-1 输入、输出结构体变量各元素。
7-3 结构体数组
结构体数组的定义
在结构体变量的定义基础上增加下标即可。
结构体数组的初始化
与普通数组相似,但要求一定要用花括号将每个数组
元素的的数据括起来。
结构体数组的引用
在结构体变量名的引用基础上,在结构体变量名后加
下标即可。其它与结构体变量相同。
注意,
?一般不要在一个 scanf()中接收不同的数据类型,否则
可能出现难以预料的错误。
?尽量使用 gets()读取用户输入的字符串。
例 L7-2 定义、初始化结构体数组,并输出各元素。
7-4 结构体变量与函数
结构体变量作为函数参数
结构体变量作为函数参数时,直接将实参结构体变量的
各成员的值全部传递给形参。
实参与形参型必须完全一致。
例 L7-6
用 list()实现结构体数组变量各元素的输出。
从上例可见:作为参数这一点上,结构体变量与普通变
量一样,是一种值传递。
返回结构体类型值的函数
函数可以返回一种类型的值,当然也包括结构体类型。
例 L7-7 用一函数实现结构体各元素值的输入。
注意,
在函数中输入结构体元素数据时,不要用 scanf(),
否则,可能无法完成数据的正常输入。
在函数中输入数值型结构体元素数据时,一般可用字
符串先接收,再用转换函数转换成相应类型,
?atol()函数的功能:将字符串转换为长整型。
?atoi()函数的功能:将字符串转换为整型。
?atof()函数的功能:将字符串转换为浮点型。
例 L7-8 建立一简单学生信息库,程序提供两个功能,
输入 E或 e,输 入 记录 ; 输 入 L或 l,显示学生记录。
7-5 结构体指针变量
指向结构体变量的指针
指向结构体变量的指针是:指向结构体变量的首地
址的指针。
如果定义了一个 struct person 类型,则可用下面
的形式定义一个指向该类型的指针变量,
struct person *p
例 L7-9 用指向结构体变量的指针输出结构体变量的
各成员值。
P只能指向结构体变量,而不能直接指向结构体变
量的成员。
P=&student 是对的,但 p=&student.name是错的。
因为用 (*p)来引用结构体变量不直观、方便,C语
言中还可用以下方式表达,
p->name 等价于 (*p).name
因为,,”与, ->”的优先级最高,所以
p->num+1 相当于 (p->num)+1
p->num++ 相当于 (p->num)++
注意,
指向结构体数组的指针
一个指针指向一个结构体数组,就是将该结构体
数组的首地址赋给此指针变量。
如果指针 p指向结构体数组,则 p++表示下一个
结构体变量的首地址。
例 L7-10 用结构体数组指针来输出结构体数组
的各元素。
用指向结构体数组的指针变量作函数的参数
例 L7-11 将 L7-10改为用函数实现,将用指向
结构体数组的指针作为函数的参数。
7-6动态存储分配 ——链表
动态存储分配的概念
以前学习的程序中,变量在整个程序或函数的执行过
程中都占据所有的分配存储单元,造成了资源浪费。
动态存储分配是指根据需要,临时分配存储单元,当
用完后,又随时释放存储单元。
简单链表
head 1249 1356 1475 1021
1249 A 1356 B 1475 C 1021
D
Null
链表有一个“头指针”变量,图中以 head表示,
它存放一个地址,该地址指向一个元素,链表中
每一个元素称为“结点”它包括,
(1)为用户需要用的实际数据,
(2) 为下一个结点的地址,
由图可知链表中各结点的地址并不一定是连
续的,只要知道其地址,就可访问该结点
称为空地址
结点的删除
head 1249 1356 1475 1021
1249
A
1356
B
1475
C
1021
D
Null 1475
结点的插入
head 1249 1356 1475 1021
1249
A
1356
B
1475
C
1021
D
Null
E
1356
1980
1980
结点的构成
C语言中用包含指针的结构体变量表示一个结点。
如,struct stud
{ long num;
float score;
struct stud *next;
};
其中 num存放学号,score存放成绩,next存
放下一个结点的首地址。如下图所示。
num
score
next
学号
成绩
下一个结点
建立简单链表
struct stud
{long num;
float score;
struct stud *next;};
main()
{struct stud stud1,stud2,stud3,*head;
stud1.num=20020101;stud1.score=89;
stud2.num=20020102;stud2.score=98;
stud3.num=20020103;stud3.score=82;
head=&stud1;
stud1.next=&stud2;
stud2.next=&stud3;
stud3.next=NULL;
}
赋值
形成“链”
实际上很少用此方法来形成链
表:不是动态进行内存分配,
缺乏灵活性。
用于动态存储分配的函数(包含在 malloc.h中)
void *malloc(unsigned int size ),
用于在内存中开辟指定大小 (size)的内存空间,将其首地址
带回。
void *calloc(unsigned int num,unsigned int
size ),
用于在内存中开辟 num个指定大小 (size)的内存空间,将其
首地址带回。
void free(void *ptr):将 ptr指向的存储空间释放。
void *realloc(void *prt,unsigned int size ),
对 ptr指向的内存重新分配指定大小 (size)的内存空间,将
其首地址带回。
收回 ptr指向的内存。
重新分配指定大小 (size)的内存空间,前后地址可能不同。
链表举例
例 L7-13 用链表存放学生数据。
例 L7-13-2,3 链表综合操作
7-7 共用体
共用体:是指将不同的数据项存放于同一段内存
单元的一种构造数据类型。
定义,
union exam
{ int a;
float b;
char c;
} x;
定义方法与结构体类似。
c 时刻 1
时刻 2
时刻 3
a
b
共用体变量的引用
引用方法与结构体相似。
直接用,,”引用其成员,x.a
如果指针 P指向共用体,则可用, ->”引用,p->a
与结构体一样,不能用共用体变量直接输入、输出其
成员。
例 L7-14 共用体定义、赋值、输出。
共用体的应用
一个数据域存放不同对象。例 L7-15
将一个整数按字节输出其内容。 例 L7-16
24897 0110001 0100001
(97)10 (65)10
高字节 低字节
(61)16 (41)16
7-8 枚举型
1,枚举类型的定义
enum 枚举类型名 {取值表 };
例如,enum weekdays {Sun,Mon,Tue,Wed,Thu,Fri,Sat};
2, 枚举变量的定义 ──与结构变量类似
( 1) 间接定义
例如, enum weekdays workday;
( 2) 直接定义
例如, enum [weekdays]
{Sun,Mon,Tue,Wed,Thu,Fri,Sat } workday;
3, 说明
( 1) 枚举型仅适应于取值有限的数据 。
例如, 根据现行的历法规定, 1周7天, 1年12个月 。
( 2) 取值表中的值称为枚举元素, 其含义由程序解释 。
例如, 不是因为写成, Sun”就自动代表, 星期天, 。 事实上, 枚举
元素用什么表示都可以 。
( 3) 枚举元素作为常量是有值的 ──定义时的顺序号
( 从0开始 ), 所以枚举元素可以进行比较, 比较规则是:
序号大者为大 !
例如, 上例中的 Sun=0,Mon=1,……, Sat=6,所
以 Mon>Sun,Sat最大 。
( 4) 枚举元素的值也是可以人为改变的:在定义时由程
序指定 。
例如, 如果 enum weekdays {Sun= 7,Mon =
1,Tue,Wed,Thu,Fri,Sat};则 Sun=7, Mon=1,
从 Tue=2开始, 依次增1 。
7-9 定义已有类型的别名
除可直接使用C提供的标准类型和自定义的类型(结
构、共用、枚举)外,也可使用 typedef定义已有类型的
别名。该别名与标准类型名一样,可用来定义相应的变量。
定义已有类型别名的方法如下,
( 1)按定义变量的方法,写出定义体;
( 2)将变量名换成别名;
( 3)在定义体最前面加上 typedef。
[例 ] 给实型 float定义 1个别名 REAL。
( 1)按定义实型变量的方法,写出定义体,float f;
( 2)将变量名换成别名,float REAL;
( 3)在定义体最前面加上 typedef,typedef
float REAL;
给如下所示的结构类型 struct date定义 1个别名 DATE。
struct date
{ int year,month,day;
};
( 1) 按定义结构变量的方法, 写出定义体,
struct date {…… } d;
( 2) 将变量名换成别名,
struct date {…… } DATE;
( 3) 在定义体最前面加上 typedef,
typedef struct date {…… } DATE;
说明,
用 typedef只是给已有类型增加1个别名, 并不能创造1个
新的类型 。 就如同人一样, 除学名外, 可以再取一个小名 ( 或
雅号 ), 但并不能创造出另一个人来 。
枚举类型
所谓“枚举”是指将变量的值一一列举出来,变量的值只限
于列举出来的值的范围内。
定义枚举类型用 enum开头,例如,
enum weekday
{ sun,mon,tue,wed,thu,fri,sat};
定义了一个枚举类型 enum weekday,可以用此类型来定义变量。如,
enum.weekday workday,week_end;
workday和 week_end被定义为枚举变量,它们的值只能是 sun到 sat之一,例如,
workday=mon;
week_end=sun;
是正确的,
当然,也可以直接定义枚举变量,如
enum {sun,mon,tue,wed,thu,fri,sat}workday,week_end;
说明
1.在 C编译中,对枚举元素按常量处理,故称枚举常量,它们不是
变量,不能对它们赋值,例如,
sun=0;mon=1;
是错误的,
2.枚举元素作为常量,它们是有值的,C语言编译按定义时的顺
序使它们的值为 0,1,2,.....,
3.枚举值可以用来作判断比较,如
if (workday= =mon)..,
if(workday>sun)..,
枚举值的比较规则是,按其在定义时的顺序号比较,如果定义
时未人为指定,则第一个枚举元素的值认作 0.故 mon大于
sun,sat>fri,
4.一个整数不能直接赋给一个枚举变量,如
workday=2;
是不对的,它们属于不同的类型,应先进行强制类型转换才能
赋值,如
workday=(enum weekday)2;
它相当于将顺序号为 2的枚举元素赋给 workday,相当于
workday=tue;
甚至可以是表达式,如
workday=(enum weekday) (5-3);
例题分析
例, 口袋中有红,黄,蓝,白,黑五种颜色的球若干个,每次从口袋
中取出 3个球,问得到三种不同色的球的可能取法,打印出每种
组合的三种颜色,(算法可以图如下,)
n=0
i从 red变到 black
j从 red变到 black
i!=j
真 假
k从 red变到 black
k!=i和 k!=j
真 假
输出一种取法
n=n+1
输出取法的总数 n
main()
{enum color {red,yellow,blue,white,black};
enum color i,j,k,pri;
int n,loop;
n=0;
for(i=red;i<=black;i++)
for(j=red;j<=black;j++)
if( i!=j)
{ for (k=red;k<=black,k++)
if((k!=i)&&(k!=j))
{n=n+1;
printf(“% -4d”,n);
for(loop=1,loop<=3;loop++)
{ switch(loop)
{ case 1,pri=i;break;
case 2,pri=j;break;
case 3,pri=k;break;
default,break;
}
switch(pri)
{ case red,printf(“% -10s”,”red”); break;
case yellow,printf(“% -10s”,”yellow”);break;
case blue,printf(“% -10s”,”blue”);break;
case white,printf(“% -10s”,”white”);break;
case black,printf(“% -10s”,”black”);break;
default,break; } }
printf(“\ntotal:%5d\n”,n);
}
用 typedef定义类型
用 typedef定义新的类型名来代替已有的类型名,如,
typedef int INTEGER;
typedef float REAL;
指定用 INTEGER代表 int类型,REAL代表 float.这样,以下两行
等价,
(1) int i,j;float a,b;
(2) INTEGER i,j;REAL a,b;
还可以定义结构体类型,
typedef sturct
{ int month;
int day;
int year;
} DATE;
定义新类型名 DATE,它代表上面定义的
一个结构体类型,
1.typedef int NUM[100]; (定义 NUM为整型数组类型 )
NUM n; ( 定义 n为整型数组变量 )
2.typedef char *STRING; (定义 STRING为字符指针类型 )
STRING p,s[10]; (p为字符指针变量,s为指针数组 )
3.typedef int (*POINTER) ( ) (定义 POINTER为指向函数的指
针类型,该函数返回整型值 )
POINTER p1,p2; (p1,p2为 POINTER类型的指针变
量 )
举例说明,
归纳起来,定义一个新的类型名的方法是,
(1) 先按定义变量的方法写出定义体 (如,int i;)
(2) 将变量名换成新类型名 (如,将 i换成 COUNT)
(3) 在最前面加 typedef(如, typedef int COUNT)
(4)然后可以用新类型名去定义变量,
例如, 定义字符指针类型,
char *p;
char *STRING;
typedef *STRING;
STRING p,s[10];
习惯上常把用 typedef定义的类型名用大写字母表示,以便与系统
提供的标准类型标识符相区别,
说明
1.用 typedef可以定义各种类型名,但不能用来定义变量,用 tylpedef
可以定义出数组类型,字符串类型,使用比较方便,如定义数组,原
来是用
int a[10],b[10],c[10],d[10];
由于都是一维数组,大小也相同,可以先将此数组类型定为一个名
字, typedef int ARR[10];
然后用 ARR去定义数组变量,
ARR a,b,c,d;
可以看到,用 typedef可以将数组类型和数组变量分离开来,利用数
组类型可以定义多个数组变量,同样可以定义字符串类型,指针类
型等,
2.用 typedef只是对已经存在的类型增加一个类型名,而汉有创
造新的类型,
3.typedef与 #define有相似之处,如
typedef int COUNT;
和 #define COUNT int
的作用都是用 COUNT 代表 int..但事实上,它们二者是不同
的,#define是在预编译时处理的,它只能作简单的字符串替换,
而 typedef是在编译时处理的,实际上它并求是作简单的字符
串替换
4.当不同源文件中用到同一类型数据时,常用 typedef定义一些
数据类型,把它们单独放在一个文件中,然后在需要用到它们
的文件中用 #include命令把它们包含进来,
5.使用 typedef有利于程序的通用与移值,有时程序会依赖于硬
件特性,用 typedef便于移值,