第十一章 复杂数据类型结构体、线性链表、共用体与枚举本章学习指导在日常生活中,我们常会遇到一些需要填写的登记表,如住宿表,成绩表,通讯地址等 。 在这些表中,填写的数据是不能用同一种数据类型描述的,
在住宿表中我们通常会登记上姓名,性别,身份证号码等项目;在通讯地址表中我们会写下姓名,邮编,邮箱地址,电话号码,E-mail等项目 。 这些表中集合了各种数据,无法用前面学过的任一种数据类型完全描述 。
复杂数据类型 —结构体,线性链表,共用体和枚举类型,包括它们的定义,变量的说明和应用 。
11.1 结构体
【 问题 】 什么是结构体类型?什么是结构体变量?结构体类型与数组类型有什么区别和联系?
姓名 年龄 性别 身份证号民族 住址 电话号码
(字符数组 ) (整型 ) (字符 ) (长整型 ) (字符 ) (字符数组 ) (长整型 )
11.2.1 结构体类型的定义结构体类型的定义形式为:
struct 类型 名
{成员项表列 };
例如包含上图中全部类型数据的结构体类型的定义如下:
struct person /* 结构体类型名 */
{ char name[20]; /* 以下定义成员项的类型和名字 */
int age;
char sex;
long num;
char nation;
char address[20];
long tel;
};
11.2.2 结构体变量的定义和引用结构体变量的说明有三种方法:
1.用已定义的结构体类型名定义变量 。
例如:
struct person student,worker;
/* 定义了两个结构体变量 student和 worker */
用上面的结构体类型还可再定义变量:
struct person men,women;
2.在定义结构体类型的同时定义结构体变量。例如:
struct person
{
char name[20];
int age;
……
long tel;
}student,worker;
一般的形式为:
struct 类型名
{
成员项表列
}变量名表列;
这样定义的结构体类型也还可多次使用,如:
struct person men,women;
3.不定义结构体类型名,直接定义结构体变量。
一般形式为:
struct
{成员项表列
}变量名表列;
结构体变量的引用
1,无嵌套的情况引用结构体变量成员的形式为:
结构体变量名 ·成员名其中的,.”叫“结构体成员运算符”,这样引用的结构体成员相当于一个普通变量,例如:
student.num
/* 结构体变量 student的成员 num,相当于一个长整型变量 */
student.name
/* 结构体变量 student的成员 name,相当于一个字符数组名 */
在有嵌套的情况下,访问的应是结构体的基本成员,因为只有基本成员直接存放数据,且数据是基本类型或上面介绍的数组类型,引用形式为:
结构体变量名 ·结构体成员名 ·… ·结构体成员名 ·基本成员名即从结构体变量开始,用成员运算符,,”逐级向下连接嵌套的成员直到基本成员,不能省略
,例如:
student.birthday.year
/* 基本成员 year,相当于一个整型变量 */
2.有嵌套的情况
11.2.3 结构体变量的赋值
1 结构体变量的初始化在以上结构体变量的三种定义的同时都可以进行初始化赋值,例如:
struct person stud1={"Wang Li",18,'M',34011,
'h',12,"13 Bejing Road",2098877},
stud2={"Yu Ping",19,'F',34082,'h',12,"25 Hefei
Road",5531678};
注意初始化数据应与类型中的各个成员在位置上一一对应。对于嵌套的结构体类型变量,初始化是对各个基本类型的成员赋初值,例如:
struct person student={"Wang Li",12,5,1974,
'M',340201,'h',12,"13 Bejing Road",2098877};
1.结构体变量的赋值、输入和输出由于结构体各个成员的类型不同,对结构体变量赋值也只能对其成员进行结构体变量的输入和输出也都只能对其成员进行
2.同一类型的结构体变量可相互赋值同类型的两个结构体变量之间可以整体赋值
(请比较 数组之间不能整体赋值)
stud1=stud2;
2 结构体变量的赋值
【 例 11-2】 输出结构体数据
#include <stdio.h>
void main()
{
struct stu
{
int num;
char *name;
char sex;
float score;
} boy1,boy2;
boy1.num=102;
boy1.name="Zhang ping";
printf("input sex and score\n");
scanf("%c %f",&boy1.sex,&boy1.score);
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
程序分析:
本程序中用赋值语句给 num和 name两个成员赋值,name是一个字符串指针变量。用 scanf函数动态地输入 sex和 score成员值,然后把 boy1的所有成员的值整体赋予 boy2。最后分别输出 boy2
的各个成员值。本例表示了结构变量的赋值、输入和输出的方法。
11.2.4 结构体数组
1.结构体数组的定义有三种方法 。
(1) 先定义结构体类型,用结构体类型名定义结构体数组,如:
struct stud_type
{char name[20];
long num;
int age;
char sex;
float score;
};
struct stud_type student[50];
9.1.5 结构体数组
(2) 定义结构体类型名的同时定义结构体数组,如:
struct stud_type
{……
}student[50];
(3) 不定义结构体类型名,直接定义结构体数组,如:
struct
{……
}student[50];
2.结构体数组的初始化结构体数组的一个元素相当于一个结构体变量,结构体数组初始化即顺序对数组元素初始化。如:
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Li ping","M",45},
{102,"Zhang ping","M",62.5},
{103,"He fang","F",92.5},
{104,"Cheng ling","F",87},
{105,"Wang ming","M",58};
}
9.1.5 结构体数组
3.结构体数组的引用
(1) 除初始化外,对结构体数组赋常数值,输入和输出,各种运算均是对结构体数组元素的成员
( 相当于普通变量 ) 进行的 。 结构体数组元素的成员表示为:
结构体数组名 [下标 ].成员名在嵌套的情况下为:
结构体数组名 [下标 ]·结构体成员名 ·…· 结构体成员名 ·成员名
(2) 结构体数组元素可相互赋值例如,student[1]=student[2];
对于结构体数组元素内嵌的结构体类型成员,情况也相同 。 如:
student[2].birthday=student[1].birthday;
(3) 其他注意事项也与结构体变量的引用相同,例如:
不允许对结构体数组元素或结构体数组元素内嵌的结构体类型成员整体赋(常数)值;不允许对结构体数组元素或结构体数组元素内嵌的结构体类型成员整体进行输入输出等。
在处理结构体问题时经常涉及字符或字符串的输入,
这时要注意:
① scanf()函数用 %s输入字符串遇空格即结束,因此输入带空格的字符串可改用 gets函数 。
②在输入字符类型数据时往往得到的是空白符(空格、回车等),甚至运行终止,因此常作相应处理
,即在适当的地方增加 getchar();空输入语句,以消除缓冲区中的空白符。
【 例 11-3】 计算学生的平均成绩和不及格的人数。
#include <stdio.h>
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Li ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"He fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
void main()
{
int i,c=0;
float ave,s=0;
for(i=0;i<5;i++)
{
s+=boy[i].score;
if(boy[i].score<60) c+=1;
}
printf("s=%f\n",s);
ave=s/5;
printf("average=%f\ncount=%d\n",ave,c);
}
程序分析:
本例程序中定义了一个外部结构数组
boy,共 5个元素,并作了初始化赋值。在
main函数中用 for语句逐个累加各元素的
score 成员值存于 s之中,如 score的值小于
60(不及格 )即计数器 C加 1,循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。
【 例 11-4】 建立同学通讯录 。
#include"stdio.h"
#define NUM 3
struct mem
{
char name[20];
char phone[10];
};
void main()
{
struct mem man[NUM];
int i;
for(i=0;i<NUM;i++)
{
printf("input name:\n");
gets(man[i].name);
printf("input phone:\n");
gets(man[i].phone);
}
printf("name\t\t\tphone\n\n");
for(i=0;i<NUM;i++)
printf("%s\t\t\t%s\n",man[i].name,man[i].phone);
}
程序分析:
本程序中定义了一个结构 mem,它有两个成员 name和 phone用来表示姓名和电话号码。在主函数中定义 man为具有 mem
类型的结构数组。在 for语句中,用 gets函数分别输入各个元素中两个成员的值。然后又在 for语句中用 printf语句输出各元素中两个成员值。
11.3 线性链表
1.动态内存分配和链表的概念
(1) 固定内存分配与动态内存分配的概念
① 固定内存分配在 C语言程序中用说明语句定义的各种存储类型 ( 自动,静态,寄存器,外部 ) 的变量或数组,均由系统分配存储单元,
程序员无法在函数的执行部分干预存储单元的分配和释放 。 这样的存储分配叫固定内存分配,又叫系统存储分配 。
② 动态内存分配
C语言允许程序员在函数执行部分的任何地方使用动态存储分配函数(本节 2.)开辟或回收存储单元,这样的存储分配叫动态内存分配。动态内存分配使用自由、节约内存。利用动态内存分配建立的链表是一种十分重要的数据结构。
动态存储分配函数
Turbo C动态存储分配函数有,malloc,calloc,free、
realloc,这些函数的定义均包含在头文件 stdlib.h中 。
( 1) malloc函数 void *malloc(unsigned int size)
调用形式为:
(类型 *) malloc(size)
例如:
int *pi; float *pf;
pi=(int *)malloc(2);
pf=(float *)malloc(4);
(2) calloc函数 void*calloc(unsigned n,unsigned size)
调用形式为:
(类型 *) calloc(n,size)
如:
char *ps;
ps=(char *)calloc(10,sizeof(char));
(3) realloc函数 void *realloc(void *ptr,unsigned newsize)
调用形式为:
(类型 *) realloc(ptr,newsize)
(4) free函数 void free(void *)
调用形式为:
free(p);
其中指针变量 p必须是由 malloc,calloc,realloc函数分配存储单元存放首地址的指针变量,该函数的作用是收回由 p指向的动态分配的存储单元 。 该函数无返回值 。
11.3.2 链表的概念
① 结点组成链表的基本存储单元叫结点,该存储单元存有若干数据和指针,由于存放了不同数据类型的数据,它的数据类型应该是结构体类型 。 在结点的结构体存储单元中,存放数据的域叫数据域,存放指针的域叫指针域,简单结点的形式如图所示 。
数据域 指针域结点类型定义的一般形式为:
struct 类型名
{数据域定义;
struct 类型名 * 指针域名 ;
};
例如有如下结点类型的定义:
struct student
{int num;
float score;
struct student *next;
}
② 链表若有一些结点,每一个结点的指针域存放下一个结点的地址,因此就指向下一个结点,这样就首尾衔接形成一个链状结构,称为链表。用上面的结构体类型建立的有 4个结点的链表如图所示。
(2) 链表的概念头结点,指向链表中第一个包含有用数据的结点,
本身不包含有用数据,用于对链表的访问 。
尾结点,不指向其他结点的结点。尾结点的指针域存放的地址为 NULL(或 0,或 '\0')。
(2) 链表的概念相同点,它们均由同类型的存储单元组成 。
不同点,数组由固定分配的连续的存储单元组成,
定义后存储单元不可增加或减少,对数组元素的访问为随机访问。链表可由不连续的存储单元(结点
)组成,结点一般为动态分配存储单元,可随时增
、删。只能顺序访问链表中的结点。
链表与数组
③ 建立一个简单链表
c.data=15; c.next=NULL;
p=head;
while(p!=NULL) {
printf("%d-->",p->data);
p=p->next;
}
printf("NULL\n");
}
【 例 9-11】 建立一个简单的链表并输出
struct node
{int data;
struct node *next;
};
main()
{struct node a,b,c,*head,*p;
head=&a; a.data=5;
a.next=&b;
b.data=10; b.next=&c;
程序分析:
本程序定义了一个简单的链表,一共三个结点 a,b,c,这三个链表结点的赋值是静态赋值,前一个结点都指向下一个结点。
11.3.3.动态存储分配链表的基本操作由包含一个指针域的结点组成的链表为单向链表。
① 建立并初始化链表
② 遍历访问链表 ( 包括查找结点,输出结点等 )
③ 删除链表中的结点
④ 在链表中插入结点
(1) 建立单向链表建立单向链表的步骤如下:
① 建立头结点 ( 或定义头指针变量 ) ;
② 读取数据;
③ 生成新结点;
④ 将数据存入结点的数据域中;
⑤ 将新结点连接到链表中 ( 将新结点地址赋给上一个结点的指针域 ) 。
⑥ 重复步骤 ② ~ ⑤,直到输入结束 。
3.动态存储分配链表的基本操作
【 例 11-6】 建立带有头结点的单向链表,当输入 -1
时结束。
#include <stdio.h>
struct node
{int data;
struct node *next;
};
main()
{
int x;
struct node *h,*s,*r;
h=(struct node *)malloc(sizeof(struct node));
r=h;
scanf("%d",&x);
while(x!=-1)
{
s=(struct node *)malloc(sizeof(struct node));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next= NULL ;
}
(2) 输出链表输出链表即顺序访问链表中各结点的数据域,方法是:从头结点开始,不断地读取数据和下移指针变量,直到尾结点为止。
【 例 】 编写单向链表的输出函数 。
void print_slist(struct node *h)
{
struct node *p; p=h->next;
if(p== NULL) printf("Linklist is null!\n");
else
{ printf("head");
while(p!= NULL)
{printf("->%d",p->data); p=p->next; }
printf("->end\n");
}
}
( 3)删除单向链表中的一个结点删除单向链表中一个结点的方法步骤如下,参见
① 找到要删除结点的前驱结点;
② 将要删除结点的后继结点的地址赋给要删除结点的前驱结点的指针域;
③ 将要删除结点的存储空间释放 。
【 例 】 编写函数,在单向链表中删除值为 x的结点
void delete_node(struct node *h,int x)
{
struct node *p,*q;
q=h; p=h->next; /* 工作指针初始化,q在前,p在后指向第一个结点 */
if(p!= NULL) /* 表非空 */
{while((p!=NULL) && (p->data!=x)) /* 未到表尾,查找 x的位置 */
{q=p; p=p->next;} /* p,q下移,q指向 p的前趋结点 */
if(p->data==x)
{q->next=p->next; free(p);} /* 删除值为 x的结点 */
}
}
( 4)在单向链表的某结点前插入一个结点在单向链表的某结点前插入一个结点的步骤如下:
① 开辟一个新结点并将数据存入该结点的数据域;
② 找到插入点结点;
③将新结点插入到链表中:将新结点的地址赋给插入点上一个结点的指针域,并将插入点的地址存入新结点的指针域
【 例 】
分析,本例结合了查找和插入两种功能,可能遇到三种情况:
① 链表非空,值为 x的结点存在,则插在值为 x的结点之前;
② 链表非空,值为 x的结点不存在,则插在表尾;
③ 链表为空,也相当于值为 x的结点不存在,则插在表尾 。
编写函数,在单向链表中值为 x的结点前插入值为 y
的结点,若值为 x的结点不存在,则插在表尾。
函数编写如下:
void insert_node(struct node *h,int x,int y)
{
struct node *s,*p,*q;
s=(struct node *)malloc(sizeof(struct node));
s->data=y; /* 向新结点存入数据 */
q=h; p=h->next; /* 工作指针初始化,p指向第一个结点 */
while((p!=NULL) && (p->data!=x))
{q=p; p=p->next;} /* p,q下移,q指向 p的前趋结点 */
q->next=s; s->next=p;
}
11.4 共用体问题,什么是共用体类型?什么是共用体变量?共用体类型与结构体类型有什么区别和联系?
11.4.1 共用体类型的定义共用体类型的定义与结构体类型的定义类似,但所用关键字不同,共用体类型用关键字 union定义
,具体形式为:
union 类型名
{成员项表列 };
union exam
{
int a;
float b;
char c;
};
11.4.2 共用体变量的定义和引用与结构体变量的说明类似,也有三种方式:
1,先定义共用体类型,再用共用体类型定义共用体变量
union 类型名
{成员表列 };
union 类型名 变量名表;
例如用 union exam类型定义共用体变量 x,y:
union exam x,y;
2,定义共用体类型名的同时定义共用体变量
union 类型名
{成员表列
}变量名表;
union exam
{
int a;
float b;
char c;
}x,y;
3.不定义类型名直接定义共用体变量
union
{成员表列
}变量名表 ;
定义了共用体变量后,系统为共用体变量开辟一定的存储单元 。 由于共用体变量先后存放不同类型的成员,系统开辟的共用体变量的存储单元的字节数为最长的成员需要的字节数 。 例如对上面定义的共用体类型 union exam或变量 x,
表达式 sizeof(union exam)和 sizeof(x)的值均为 4。
另外,先后存放各成员的首地址都相同,即共用体变量
、共用体变量的所有成员它们的首地址都相同。
共用体变量的引用
1.共用体变量的引用引用共用体变量的形式以及注意事项均与引用结构体变量相似,其要点如下:
一般只能引用共用体变量的成员而不能整体引用共用体变量,尽管它同时只有一个成员有值 。 共用体变量的一个基本类型成员相当于一个普通变量,
可参与该成员所属数据类型的一切运算 。 例如对上面定义的共用体变量 x可以引用的成员有:
x.a
x.b
x.c
【 例 11-7】 定义一个结构体和共同体类型 。 程序如下:
#include
<stdio.h>
union data /*共用体 */
{
int a;
float b;
double c;
char d;
}mm;
struct stud /*结构体 */
{
int a;
float b;
double c;
char d;
};
void main( )
{
struct stud student;
printf("%d,%d",sizeof(struc
t stud),sizeof(union data));
}
程序分析:
程序的输出说明结构体类型所占的内存空间为其各成员所占存储空间之和。而形同结构体的共用体类型实际占用存储空间为其最长的成员所占的存储空间。
(1) 与结构体类似,同一类型的共用体变量可相互赋值 。 例如有赋值语句 x.a=3; 共用体变量 x有值,则可有下面的赋值语句:
y=x;
这样 y.a的值也为 3。
(2) 在赋值和输入输出方面也与结构体类似,即不允许对共用体变量整体赋(常数)值;共用体的输入和输出也只能对共用体变量的成员进行,
不允许直接对共用体变量进行输入和输出,尽管它同时只存有一个成员!
11.4.3 共用体变量的赋值
11.5 枚举类型
11.5.1 枚举类型的定义枚举类型定义的形式为,
enum 类型名 {标识符序列 };
例如,定义枚举类型 color_name,它由 5个枚举值组成:
enum color_name{red,yellow,blue,white,black};
说 明
(1) enum是定义枚举类型的关键字。
(2) 枚举值标识符是常量不是变量,系统自动给予它们
0,1,2,3… 值,因此枚举类型是基本数据类型。
(3) 枚举值只能是一些标识符(字母开头,字母、数字和下划线组合),不能是基本类型常量。虽然它们有值 0,1,2,3…,但如果这样定义类型
enum color_name{0,1,2,3,4}; 是错误的。
(4) 可在定义枚举类型时对枚举常量重新定义值,如:
enum color_name{red=3,yellow,blue,white=8,black};
此时 yellow为 4,blue为 5,black为 9,即系统自动往后延续。
11.5.2 枚举变量的说明及引用
1.枚举变量的说明可以用定义过的枚举类型来定义枚举变量,形式为:
enum 类型名 变量名表;
例如定义枚举类型 color_name的变量 color:
enum color_name color;
也可以在定义类型的同时定义变量,形式为:
enum 类型名 {标识符序列 } 变量名表;
例如定义枚举类型 color_name的变量 color:
enum color_name{red,yellow,blue,white,black} color;
或者省略类型名直接定义变量,形式为:
enum {标识符序列 } 变量名表;
2.枚举变量的引用
(1) 枚举变量定义以后就可以对它赋枚举常量值或者其对应的整数值,例如变量 color可以赋 5个枚举值之一,
color=red; 或 color=0;
color=blue;或 color=2;
但 color=green;或 color=10;均不合法,因为 green不是枚举值,而 10已超过枚举常量对应的内存值 。
(2) 因枚举常量对应整数值,因此枚举变量,常量和常量对应的整数之间可以比较大小,如:
if(color==red) printf("red");或 if(color==0) printf("red");
if(color!=black) printf("The color is not block!");
if(color>white) printf("It is block.");
2.枚举变量的引用
(3) 枚举变量还可以进行 ++,--等运算。
(4) 枚举变量不能通过 scanf或 gets函数输入枚举常量,
只能通过赋值取得枚举常量值。但是枚举变量可以通过
scanf("%d",&枚举变量 );输入枚举常量对应的整数值。
(5) 枚举变量和枚举常量可以用 printf(“%d”,…) ;输出对应的整数值,若想输出枚举值字符串,则只能间接进行,
如:
color=red;
if(color==red) printf("red");
由于枚举变量可以作为循环变量,因此可以利用循环和
switch语句打印全部的枚举值字符串。
【 例 11-8】 输出全部的枚举值字符串
main()
{
enum {red,yellow,blue,white,black}color;
printf("All enum strings are:\n");
for(color=red;color<=black;color++)
switch(color)
{case red,printf("red\n");break;
case yellow:printf("yellow\n");break;
case blue,printf("blue\n");break;
case white,printf("white\n");break;
case black,printf("black\n");break;
}
}
程序分析:
只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如:
a=sum;b=mon;是正确的。而,a=0;b=1;
是错误的。
枚举类型的应用
main()
{ enum day{mon=1,tue,wed,thu,fri,sat,sun}x;
printf("Input a integer:\n");
scanf("%d",&x);
switch(x)
{ case mon:
case tue:
case wed:
case thu:
case fri:printf("Work day\n");break;
case sat:
case sun:printf("Rest day\n");break;
default,printf("Input error!\n");
}
}
【 例 11-9】 输入星期几的整数,输出“工作日”或
“休息日”的信息。
程序分析:
如一定要把数值赋予枚举变量,则必须用强制类型转换。如,a=(enum
weekday)2;其意义是将顺序号为 2的枚举元素赋予枚举变量 a,相当于,a=tue;还应该说明的是枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
11.6 用户定义类型
1.定义“替代”类型名定义的形式为:
typedef 类型名 标识符;
注意:,类型名,必须是系统提供的数据类型或用户已定义的数据类型 。 定义替代类型名的作用是:给已有的类型起个别名标识符,例如:
typedef int INTEGER;
2.定义“构造”类型名定义的形式为:
typedef 类型名 标识符及,构造,;
注意:“类型名”必须是系统提供的数据类型或用户已定义的数据类型。定义构造类型名的作用是:自己定义新
“构造”类型名标识符。
举 例
(1) 定义字符型指针类型名 CHARP
typedef char * CHARP;
以后可用它来定义指针变量,例如:
CHARP p,q;
即等价于,char * p,*q;
(2) 定义具有 3个元素的整型数组名 NUM
typedef int NUM[3];
以后可用它来定义有三个元素的 int型数组,如:
NUM a,b,c;
就相当于 int a[3],b[3],c[3];
(3) 定义某结构体类型名 STUDENT
typedef struct
{
int num;
char name[10];
char sex;
float score[3];
}STUDENT;
以后可用它来定义该种结构体类型变量、指针变量等,例如:
STUDENT stu1,stu2,*st;
就定义了该结构体类型的变量和指针变量。
举 例
3.定义新类型名的一般步骤及说明
(1) 先按定义变量或数组的方法写出定义 ;
如,char a[10];
(2) 将定义的名字换成新类型名 ;
如,char NAME[10];
(3) 在前面加上 typedef;
如,typedef char NAME[10];
(4) 然后可以用新类型名定义变量 ;
如,NAME c,d; c,d即是有 10个元素的字符数组
11.7 复杂数据类型应用综合举例
【 例 11-10】 有 5个学生,每个学生的信息有学号,
姓名和三门课的成绩,求每个学生的平均成绩并按平均成绩从大到小对所有学生的信息进行排序然后输出 。
分析,在定义结构体类型时可以设计一个存放平均成绩的成员,排序交换位置时应将结构体数组元素整体交换,编程如下:
【 例 11-10】
main()
{
struct student
{ long num;
char name[10];
int score[3];
float evr;
}t,
st[5]={{1001,"wang",67,75,88},{1002,"li",83,92,95},
{1003,"zhao",56,82,79},{1004,"han",78,87,79},
{1005,"qian",69,79,81}};
int i,j;
for(i=0;i<5;i++)
{st[i].evr=0;
for(j=0;j<3;j++)
st[i].evr+=st[i].score[j];
st[i].evr/=3;
}
for(i=0;i<4;i++)
for(j=i+1;j<5;j++)
if(st[i].evr<st[j].evr){t=st[i];st[i]=st[j];st[j]=t;}
printf("No,Name scor1 scor2 score3 evr\n");
for(i=0;i<5;i++)
{ printf("%ld%8s",st[i].num,st[i].name);
for(j=0;j<3;j++)printf("%8d",st[i].score[j]);
printf("%8.1f\n",st[i].evr);
}
}
【 例 11-10】
程序分析:
在定义结构体类型时可以设计一个存放平均成绩的成员,排序交换位置时应将结构体数组元素整体交换。
【 例 11-11】
有 5个学生,每个学生的信息有学号,姓名和三门课的成绩,输出三门课的总平均分以及所有成绩中最高成绩所对应学生的全部信息 。
【 分析 】 三门课的总平均分可以定义一个数组,
找出最高成绩时应记录是哪个学生才能输出该学生的全部信息,编程如下:
main()
{
struct student
{ long num;
char name[10];
int score[3];
}st[5]={{1001,"wang",67,75,88},{1002,"li",83,92,95},
{1003,"zhao",56,82,79},{1004,"han",78,87,79},
{1005,"qian",69,79,81}};
int i,j,max,maxi;
float aver[3]={0};
for(j=0;j<3;j++)
{ for(i=0;i<5;i++)
aver[j]+=st[i].score[j];
aver[j]/=5;
}
【 例 11-11】
【 例 11-11】
max=st[0].score[0];
for(i=0;i<5;i++)
for(j=0;j<3;j++)
if(st[i].score[j]>max){max=st[i].score[j];maxi=i;}
printf("The averages of courses are:\n");
for(i=0;i<3;i++) printf("%6.1f",aver[i]);
printf("\n");
printf("The informations of the student with maximal score:\n");
printf("No,Name scor1 scor2 score3\n");
printf("%ld%8s",st[maxi].num,st[maxi].name);
for(j=0;j<3;j++)printf("%8d",st[maxi].score[j]);
printf("\n");
}
程序分析:
三门课的总平均分可以定义一个数组,
找出最高成绩时应记录是哪个学生才能输出该学生的全部信息。
【 例 11-12】
已知函数 bioskey(0)返回用户按键对应的一个 16
位二进制数,其中若是控制键 ( 含移动光标,组合控制键等 ) 则高 8位是该键的扫描码,低 8位是 0;
若是可见字符 ( 含空格 ) 则低 8位是字符的 ASCII
码 。 利用共用体编程获得用户按键的 ASCII码或扫描码 。
#include <bios.h>
main()
{ union
{int a;
char c[2];
}key;
do
{ printf("Please prees a key:");
key.a=bioskey(0);printf("%c\n",key.a);
if(key.c[0]==0)
printf("The scanning code of the key is:%d\n",key.c[1]);
else
printf("The ASCII of the character is:%d\n",key.c[0]);
}while(key.c[0]!='q');
}
【 例 11-12】
程序分析:
一般只能引用共用体变量的成员而不能整体引用共用体变量,尽管它同时只有一个成员有值。共用体变量的一个基本类型成员相当于一个普通变量,可参与该成员所属数据类型的一切运算。注意:上述程序只能在
Turbo上运行,因为 VC++没有 bios.h文件。
【 例 11-13】 口袋中有红、黄、绿、蓝颜色的球各一些,现从口袋中每次摸出 3个球,要求颜色均不同,考虑摸出的顺序,输出所有可能的取法的排列及取法的数量。
main()
{
enum color{red,yellow,green,blue}c,x,y,z;
int i,n=0;
for(x=red;x<=blue;x++)
for(y=red;y<=blue;y++)
if(x!=y) /* 避开 x=y的情况,提高了效率 */
for(z=red;z<=blue;z++)
if(z!=x && z!=y) /* x,y,z均不相同 */
{n++;printf("%-5d",n); /* 打印序号 */
for(i=1;i<=3;i++) /* 轮流对 x,y,z打印颜色 */
{switch(i)
{case 1:c=x;break;
case 2:c=y;break;
case 3:c=z;
}
【 例 11-13】
switch(c)
{case red,printf(“%-8s”,“red”);break;
case yellow:printf(“%-8s”,“yellow”);break;
case green,printf(“%-8s”,“green”);break;
case blue,printf(“%-8s”,“blue”);
}
}
if (n%2==0) printf(“\n”);
}
printf(“n=%d\n”,n);
}
程序分析:
这是枚举类型的一个小例子。用以说明一些程序可以在 VC++上运行,但是在
Turbo上却运行不了,需要改其中的程序,
主要问题出在枚举类型变量的自增上。如果要在 Turbo上运行,需要将 x=color(x+1)改为 x++。
11.8 本章小结及常见错误列举本章学习了 C语言的用户定义类型,包括结构体,共用体和枚举类型三种,其中结构体和共用体是构造类型,枚举类型是基本数据类型,重点学习了结构体类型 。 本章还学习了用户定义类型名的方法 。 现小结如下 。
1,结构体与共用体有很多相似的地方:
(1)类型定义的形式相同 。 通过定义类型说明了结构体或共用体所包含的不同数据类型的的成员项,同时确定了结构体或共用体类型的名称 。
(2)变量说明的方法相同。都有三种方法说明变量,第一种方法是先定义类型,再说明变量;第二种方法是在定义类型的同时说明变量;第三种方法是利用结构直接说明变量。数组
、指针等可与变量同时说明。
本章小结
(3)结构体与共用体的引用方式相同 。 除了同类型的变量之间可赋值外,均不能对变量整体赋常数值,输入,输出和运算等,都只能通过引用其成员项进行,嵌套结构只能引用其基本成员,如:
变量,成员 或 变量,成员,成员 … 基本成员 。
结构体或共用体的 ( 基本 ) 成员是基本数据类型的,可作为简单变量使用,是数组的可当作一般数组使用 。
(4)无论结构体还是共用体其应用的步骤是基本相同的,
都要经过三个过程,① 定义类型 。 ② 用定义的类型说明变量,说明后编译系统会为其开辟内存单元存放具体的数据
。 ③ 引用结构体或共用体的成员 。
2,了解结构体与共用体的区别非常重要,他们的主要区别有:
(1)在结构体变量中,各成员均拥有自己的内存空间,它们是同时存在的,一个结构变量的总长度等于所有成员项长度之和;在共用体变量中,所有成员只能先后占用该共用体变量的内存空间,它们不能同时存在,一个共用体变量的长度等于最长的成员项的长度。这是结构体与共用体的本质区别。
本章小结
(2)在说明结构体变量或数组时可以对变量或数组元素的所有成员赋初值,由于共用体变量同时只能存储一个成员,因此只能对一个成员赋初值 。 对共用体变量的多个成员赋值则逐次覆盖,只有最后一个成员有值 。
3,定义结构体与共用体类型时可相互嵌套 。
4,对于结构体类型,如果其中的一个成员项是一个指向自身结构的指针,则该类型可以用作链表的结点类型 。
实用的链表结点必须是动态存储分配的,即在函数的执行部分通过动态存储分配函数开辟的存储单元 。 链表的操作有建立,输出链表,插入,删除结点等 。
本章小结本章小结
5,枚举类型的数据就是用户定义的一组标识符(枚举常量
)的序列,其存储的是整型数值,因此枚举类型是基本数据类型。由于枚举常量对应整数值,因此枚举类型数据与整数之间可以比较大小,枚举变量还可以进行 ++,-- 等运算。枚举类型不能直接输入输出,只能通过赋值取得枚举常量值,输出也只能间接进行。