C语言程序设计P7【结构体和共用体】——定义和使用结构体、使用结构体数组、结构体指针、链表、共用体、枚举类型
目录
任务一:使用结构体比较学生成绩
任务二:使用结构体数组统计不及格人数
任务三:使用结构体指针求最高成绩
任务四:利用链表录入及输出学生信息
任务五:利用共用体处理学生和教师信息
任务六:利用共用体处理学生和教师信息
任务一:使用结构体比较学生成绩
知识要点:定义和使用结构体
一、任务分析
输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩(用结构体来完成)。1.定义两个结构相同的结构体变量student1和student2;
2.分别输入两个学生的学号、姓名和成绩;
3.比较两个学生的成绩,如果学生1的成绩高于学生2的成绩,则输出学生1的全部信息,如果学生2的成绩高于学生1的成绩,就输出学生2的全部信息。如果二者相等,输出两个学生的全部信息。
二、必备知识与理论
1. 结构体类型的概念
前面学习了一些基本类型(也称为简单类型),如整型、实型、字符型等,这些类型都是系统定义好的,程序员可以直接拿来定义变量。
C语言提供了自定义数据类型的方法,通过自定义类型将不同类型的数据组合成一个有机的整体,以便访问。这些数据在这个整体中是互相联系的,这种自定义的数据类型称为结构体(structure)。
如果程序要用到如表7-1所表示的数据结构,可以在程序中自己建立一个结构体类型。
声明一个结构体类型的一般形式为:
struct 结构体名
{
成员表列
};
声明结构体类型时需要注意以下几个问题:
(1)结构体类型名为struct Student,其中struct是定义结构体类型的关键字,它和系统提供的基本类型具有相同的地位和作用,都是可以用来定义变量的类型。
(2)在{}中定义的变量我们叫做成员,其定义方法和前面变量定义方法一样,只是我们不能忽略最后的分号。
(3)成员可以属于另一个结构体类型。
2.结构体变量的定义
为了能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据,可以采取以下3种方法定义结构体类型变量。
(1)先声明结构体类型,再定义该类型的变量
我们已经申明了一个结构体类型struct Student,可以用它来定义变量。例如:
struct Student student1,student2;
(2)在声明类型的同时定义变量
(3)不指定类型名而直接定义结构体类型变量
一般形式为:
struct
{
成员表列
}变量名表列;
第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量。在三种定义方法中,经常使用的是第一种方法。
说明:
①结构体类型与结构体变量是不同的概念,不要混同。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
②结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象。
③对结构体变量中的成员,可以单独使用,它的作用与地位相当于普通变量。
3.结构体变量的初使化
结构体类型与其他的基本类型一样,也可以在定义结构体变量时指定初始值,然后可以引用这个变量,例如输出它的成员的值。
4.结构体变量的引用
(1)可以引用结构体变量中成员的值,引用方式为:
结构体变量名.成员名
“.”是成员运算符,它在所有的运算符中优先级最高,因此可以将student1.num当作一个整体,相当于一个变量。
三、任务实施
任务要求用结构体来完成,也就是说完成对两个学生成绩比较大小这个功能需要定义包含学号、姓名和成绩成员的结构体。
程序代码如下:
#include <stdio.h>
void main()
{
struct Student
{ int num;
char name[20];
float score;
}student1,student2;
scanf("%d%s%f",&student1.num,student1.name,&student1.score);
scanf("%d%s%f",&student2.num,student2.name,&student2.score);
printf("较高成绩是:\n");
if(student1.score>student2.score)
printf("学号:%d 姓名:%s 成绩:%f\n",
student1.num,student1.name,student1.score);
else if(student1.score<student2.score)
printf("学号:%d 姓名:%s 成绩:%f\n",
student2.num,student2.name,student2.score);
else
{ printf("学号:%d 姓名:%s 成绩:%f\n",
student1.num,student1.name,student1.score);
printf("学号:%d 姓名:%s 成绩:%f\n",
student2.num,student2.name,student2.score); }
}
运行结果如下:
201363301 张三 86
201363302 王五 97
较高成绩是:
学号:201363302 姓名:王五 成绩 97.000000
用scanf函数输入结构体变量时,必须分别输入它们的成员的值,不能在scanf函数中使用结构体变量名一揽子输入全部成员的值。在成员student1.num和student1.score的前面都有地址符&,而在student1.name前面没有&,这是由于name是数组名,本身代表地址,无须再加一个&。
根据student1.score与student2.score的比较结果,输出不同的结果信息,可以发现结构体变量的好处:由于student1是一个组合项,内放有关联的一组数据,student1.score是属于student1变量的一部分,因此如果确定了student1.score是成绩较高的,则输出student1的全部信息是轻而易举的,因为它们本身是互相关联的。如果用普通变量则难以方便地实现这一目的。
任务二:使用结构体数组统计不及格人数
知识要点:使用结构体数组
一、任务分析
使用结构体数组,计算学生的平均成绩和统计不及格的人数。
1.定义结构数组student,数组中有5个元素,并作初始化赋值;
2.在main函数中用for语句逐个累加各元素的score 成员值存于s之中,如score的值小于60(不及格)即计数器C加1,循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。
二、必备知识与理论
1.结构体数组的定义
数组的元素也可以是结构类型的。因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。
定义结构体数组的一般形式为:
(1)struct 结构体名
{
成员表列
}数组名[数组长度];
(2)先声明一个结构体类型(如struct Person),然后再利用此类型定义结构体数组:
结构体类型 数组名[数组长度];
如:struct Person leader[3];
对结构体数组初始化的形式是在定义数组的后面加上:={初值表列};
如:struct Person leader[3]={“Li”,0,”Zhang”,0,”Wang”,0};
【例7.2】建立同学通迅录。
#include <stdio.h>
#define NUM 3
struct mem
{
char name[20];
char phone[10];
};
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);
}
运行结果:
input name:
Li Ping
input phone:
63636253
input name:
Zhang Wei
input phone:
66666736
input name:
Wang Xin
input phone:
67893652
name phone
Li Ping 63636253
Zhang Wei 66666736
Wang Xin 67893652
本程序中定义了一个结构mem,它有两个成员name和phone用来表示姓名和电话号码。在主函数中定义man为具有mem 类型的结构数组。在for语句中,用gets函数分别输入各个元素中两个成员的值。然后又在for语句中用printf语句输出各元素中两个成员值。
三、任务实施
计算学生的平均成绩和统计不及格的人数。
1.定义结构数组student,数组中有5个元素,并作初始化赋值;
2.在main函数中用for语句逐个累加各元素的score 成员值存于s之中,如score的值小于60(不及格)即计数器c加1,循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。
程序代码如下:
#include <stdio.h>
struct stu
{
int num;
char *name;
char sex;
float score;
}student[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},
};
main()
{
int i,c=0;
float ave,s=0;
for(i=0;i<5;i++)
{
s+=student[i].score;
if(student[i].score<60) c+=1;
}
ave=s/5;
printf("平均分=%f\n不及格人数=%d\n",ave,c);
}
运行结果如下:
平均分=69.000000
不及格人数=2
任务三:使用结构体指针求最高成绩
知识要点:结构体指针
一、任务分析
有 n 个结构体变量,内含学生学号、姓名和 3 门课程成绩,要求输出平均成绩高 的学生信息(利用结构体指针实现)。
1.将 n 个学生的数据表示为结构体数组,按照功能函数化的思想,分别用 3 个函数来实现不同的功能: ① 用 input()函数来输入数据和求各学生的平均成绩。 ② 用 max()函数来找平均成绩高的学生。 ③ 用 print()函数来输出成绩高学生的信息。
2.在主函数中先后调用上述 3 个函数,用指向结构体变量的指针作为实参,得到结果。
二、必备知识与理论
1.指向结构体变量的指针
一个指针变量用来指向一个结构体变量时,称之为结构体指针变量。结构体指针变 量中的值是所指向的结构体变量的首地址。通过结构体指针即可访问该结构体变量,这 与数组指针和函数指针的情况是相同的。 结构指针变量说明的一般形式为:
struct 结构名 *结构指针变量名;
例如,在前面的例题中定义了 Student 结构体,如要说明一个指向 Student 的指针变 量 pstu,可写为:struct Student *pstu;
当然也可在定义 Student 结构体时说明 pstu。与前面讨论的各类指针变量相同,结 构体指针变量也必须要先赋值才能使用。 赋值是把结构体变量的首地址赋予该指针变量,不能把结构体名赋予该指针变量。 如果 student1 是被说明为 Student 类型的结构体变量,则:
pstu=&student1;
是正确的,而
pstu=&Student;
是错误的。 结构体名和结构体变量是两个不同的概念,不能混淆。结构体名只能表示一个结构 形式,编译系统并不为它分配内存空间。只有当某变量被说明为这种类型的结构时,才 对该变量分配存储空间。因此&Student 这种写法是错误的,不可能去取一个结构体名的 首地址。有了结构体指针变量,就能更方便地访问结构变量的各个成员。 其访问的一般形式为:
(*结构体指针变量).成员名 或 结构体指针变量->成员名
例如:(*pstu).num 或pstu->num
注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。如去掉括号写作*pstu.num,则等效于*(pstu.num),这样,意义就完全不对了。
下面通过例子来说明结构体指针变量的具体说明和使用方法。
【例 7.3】通过指向结构体变量的指针变量输出结构体变量中成员的信息。
#include <stdio.h>
struct stu
{ int num;
char *name;
char sex;
float score;
} boy1={102,"Zhang ping",'M',78.5},*pstu;
main()
{ pstu=&boy1;
printf("Number=%d\nName=%s\n",boy1.num,boy1.name); printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score); printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name); printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score); printf("Number=%d\nName=%s\n",pstu->num,pstu->name); printf("Sex=%c\nScore=%f\n\n",pstu->sex,pstu->score); }
2.指向结构体数组的指针
结构体指针变量不但可以指向一个结构体变量,还可以指向结构体数组,此时指针 变量的值就是结构体数组的首地址。 结构体指针变量也可以指向结构体数组中的元素,这时指针变量的值就是该结构体 数组元素的首地址。例如定义一个结构体数组 student[5],使用结构体指针指向该数组:
struct Student *pstu; pstu=student;
注意:由于数组不使用下标时表示的是数组的第一个元素的地址,所以指针指向数组的首地址。如果想利用指针指向第5个元素,则在数组名后附加下标,然后在数组名前使用取地址符号&,例如:pstu=&student[4];
【例 7.4】使用结构体指针变量指向结构体数组。
#include <stdio.h>
struct stu
{
int num;
char *name;
char sex;
float score;
}student[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},};
main()
{ struct stu *pstu;
pstu=student;
for(int i=0;i<5;i++,pstu++)
{
printf("Number=%d\nName=%s\nSex=%c\nScore=%f\n",pstu->num, pstu->name,pstu->sex,pstu->score);
}
}
利用 for 语句,对数组进行循环操作。在循环语句中,pstu 刚开始指向数组的首地 址,也就是第一个元素的地址,因此使用 pstu->引用的是第一个元素的成员。当第一次 循环结束之后,循环变量进行自加操作,同时 pstu 也执行自加操作。
3.结构体作为函数参数
函数是有参数的,结构体变量的值可以作为一个函数的参数,使用结构体作为函数 的参数有 3 种形式:使用结构体变量作为函数的参数;使用结构体变量的成员作为函数 的参数;使用指向结构体变量的指针作为函数的参数。
【例 7.5】使用结构体变量作为函数的参数。
#include <stdio.h>
struct stu
{ int num;
char *name;
float score[3];
}student={101,"Li ping",78,89,96};
void display(struct stu student)
{ printf("Number=%d\nName=%s\nScore[0]=%.2f\nScore[1]=%.2f\nScore[2]=%.2f\n",
student.num,student.name, student.score[0],student.score[1],student.score[2]);
}
main()
{
display(student);
}
运行结果:
Number=101
Name=Li ping
Score[0]=78.00
Score[1]=89.00
Score[2]=96.00
使用结构体变量作为函数的实参时,采取的是“值传递”,会将结构体变量所占内 存单元的内容全部顺序传递给形参,形参也必须是同类型的结构体变量。特别是成员为 数组时将会使传送的时间和空间开销很大,严重降低了程序的效率。因此好的办法就 是使用指针,即用指针变量作函数参数进行传送。这时由实参传向形参的只是地址,从 而减少了时间和空间的开销。
【例 7.6】使用结构体变量指针作为函数的参数。
本实例对例 7.5 做了一些小的改动,使用结构体变量的指针作为函数的参数,并且在参数中改动结构体成员的数据。
#include <stdio.h>
struct stu
{
int num;
char *name;
float score[3];
}student={101,"Li ping",78,89,96};
void display(struct stu* pstudent)
{
printf("Number=%d\nName=%s\nScore[0]=%.2f\nScore[1]=%.2f\n Score[2]=%.2f\n",
pstudent->num,pstudent->name,pstudent->score[0],pstudent->score[1],pstudent->score[2]);
pstudent->score[1]=57;
}
main()
{
struct stu *pstu;
pstu=&student;
display(pstu);
printf("修改后成绩\nscore[1]=%.2f\n",pstu->score[1]);
}
注意:由于传递的是变量的地址,如果在函数中改变成员中的数据,那么返回函数的时候变量会发生改变。
三、任务实施
1.将n个学生的数据表示为结构体数组,按照功能函数化的思想,分别用3个函数来实现不同的功能:
①用input函数来输入数据和求各学生的平均成绩。
②用max函数来找平均成绩最高的学生。
③用print函数来输出成绩最高学生的信息。
2.在主函数中先后调用上述3个函数,用指向结构体变量的指针作为实参,最后得到结果。
程序代码如下:
#include <stdio.h>
#define N 3
struct Student
{
int num;
char name[20];
float score[3];
float aver;
};
void main()
{
void input(struct Student stu[]);
struct Student max(struct Student stu[]);
void print(struct Student stu);
struct Student stu[N],*p=stu;
input(p);
print(max(p));
}
void input(struct Student stu[])
{
int i;
printf("录入学生的学号、姓名、三门课成绩\n");
for(i=0;i<N;i++)
{
scanf("%d %s %f %f %f",&stu[i].num,stu[i].name,
&stu[i].score[0],&stu[i].score[1],&stu[i].score[2]);
stu[i].aver=(stu[i].score[0]+stu[i].score[1]+stu[i].score[2])/3;
}
}
struct Student max(struct Student stu[])
{
int i,m=0;
for(i=0;i<N;i++)
if(stu[i].aver>stu[m].aver) m=i;
return stu[m];
}
void print(struct Student stud)
{
printf("\n成绩最高的学生是:\n");
printf("学号:%d\n姓名:%s\n三门成绩:%5.1f,%5.1f,%5.1f\n平均成绩:%6.2f\n",stud.num,stud.name,stud.score[0],stud.score[1],stud.score[2],stud.aver);
}
运行结果如下:
录入学生的学号、姓名、三门课成绩
2013633001 Liyi 66 70 90
2013633002 Wangwu 80 87 78
2013633003 Zhaoliu 90 87 85
成绩最高的学生是:
学号:2013633003
姓名:Zhaoliu
三门成绩:90.0,87.0,85.0
平均成绩:87.33
程序分析:
(1)调用input函数时,实参是指针变量p,形参是结构体数组,传递的是结构体元素地址,函数无返回值。
(2)调用max函数时,实参是指针变量p,形参是结构体数组,传递的是结构体元素的地址,函数的返回值是结构体类型数据。
(3)调用print函数时,实参是结构体变量,形参是结构体变量,传递的是结构体变量中各成员的值,函数无返回值。
任务四:利用链表录入及输出学生信息
知识要点:链表
一、任务分析
利用链表录入及输出学生的信息。
1.声明一个结构体类型,其成员包括num(学号),(score(成绩),next(指针变量)。
2.将第1个结点的起始地址赋给头指针head,将第2 个结点的起始地址赋给第1个结点的next成员,第3个成员的起始地址赋给第2个结点的next成员,以此类推,将最后一个结点的next成员赋予NULL,形成链表。
3.从头指针开始,设一个指针变量p,先指向第1个结点,输出p所指的结点,然后使p后移一个结点,再输出,直至链表的尾结点。
二、必备知识与理论
1.链表的概念
链表是一种常见的重要的数据结构,它是动态进行存储单元分配的一种结构。每一次分配一块空间可用来存放一个学生的数据,我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间,也就是说要建立多少个结点。当然用结构数组也可以完成上述工作,但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。
用动态存储的方法可以很好地解决这些问题。有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学,可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。另一方面,用数组的方法必须占用一块连续的内存区域。而使用动态分配时,每个结点之间可以是不连续的(结点内是连续的)。结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。
可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。
2.建立动态链表及输出(主要针对单向链表)
建立单向链表的主要步骤如下:
(1)读取数据。
(2)生成新结点。
(3)将数据存入结点的成员变量中。
(4)将新结点插入到链表中,重复上述操作直至输入结束。
在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题,用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。
在C语言的头文件<stdlib.h>中,提供的内存管理函数有:
(1)分配内存空间函数malloc
调用形式:
(类型说明符*) malloc (size);
功能:在内存的动态存储区中分配一块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。“类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。
例如:
pc=(char *)malloc(100);
表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。
(2)释放内存空间函数free
调用形式:
free(void*ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc函数所分配的区域。
【例7.7】建立和输出单向动态链表。
编写函数creatlist(),建立带有头结点的单向链表。节点数据域中的数值从键盘输入,以-1作为输入结束标志。链表的头节点的地址由函数值返回。
编写函数print(),利用工具指针p从头到尾依次指向链表的每个结点,当指向某个结点时,就输出该结点的内容,直至遇到链表结束标志为止。
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data;
struct node * next;
};
struct node *creatlist()
{
int i;
struct node * begin, * end, * current;
begin=(struct node *)malloc (sizeof(struct node));
end=begin;
scanf("%d",&i);
while(i!=-1)
{
current=(struct node *)malloc (sizeof(struct node));
current->data=i;
end->next=current;
end=current;
scanf("%d",&i);
}
end->next='\0';
return begin;
}
void print(struct node *head)
{
struct node *p;
p=head->next;
if(p=='\0')
printf("Linklist is null\n");
else
{
printf("head");
do
{
printf("->%d",p->data);
p=p->next;
}while(p!='\0');
}
printf("->end\n");
}
void main()
{
struct node *head;
head=creatlist();
print(head);
}
三、任务实施
1.声明一个结构体类型,其成员包括num(学号),(score(成绩),next(指针变量)。
2.将第1个结点的起始地址赋给头指针head,将第2 个结点的起始地址赋给第1个结点的next成员,第3个成员的起始地址赋给第2个结点的next成员,以此累推,将最后一个结点的next成员赋予NULL,形成链表。
3.从头指针开始,设一个指针变量p,先指向第1个结点,输出p所指的结点,然后使p后移一个结点,再输出,直至链表的尾结点。
程序代码如下:
#include<stdio.h>
#include<stdlib.h>
#define LEN sizeof(struct Student)
struct Student
{
long num;
float score;
struct Student * next;
};
int n;
struct Student * creat()
{
struct Student *head;
struct Student *p1,*p2;
n=0;
p1=p2=(struct Student *)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
{
n++;
if(n==1)head=p1;
else p2->next=p1;
p2=p1;
p1=(struct Student *)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
}
p2->next=NULL;
return(head);
}
void print(struct Student *head)
{
struct Student *p;
printf("\nNow,These %d records are:\n",n);
p=head;
if(head!=NULL)
do
{
printf("%ld %5.1f\n",p->num,p->score);
p=p->next;
}while(p!=NULL);
}
void main()
{
struct Student * head;
head=creat();
print(head);
}
程序的运行结果如下:
1001,86
1002,67
1003,98
0,0
Now,These 3 records are:
1001 86.0
1002 67.0
1003 98.0
任务五:利用共用体处理学生和教师信息
知识要点:共用体
一、任务分析
同一表格处理学生和教师信息。
学生的信息包括:姓名、号码、性别、职业、班级;
教师的信息包括:姓名、号码、性别、职业、职务。
可以看出,学生和教师的信息项目大多数是相同的,但有一项是不同的,要求把它们放在同一表格中,显然可以采用共用体来处理不同项,即将班级和职务放在同一段存储单元中。
二、必备知识与理论
共用体看起来很像结构体,只不过关键字由struct变成了union。
1.共用体的概念
共用体也称为联合体,使几个不同的变量共占同一段内存的结构称为 “共用体”类型的结构。因此,共用体在同一时刻只能有一个值,它属于某一个数据成员,由于所有成员位于同一块内存,共用体的大小就等于最大成员的大小。
定义共用体类型变量:
union 共用体名
{
成员表列
}变量表列;
例如:
union data
{
int i;
char ch;
float f;
}a,b,c;
结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。而共用体变量所占的内存长度等于最长的成员的长度。例如,上面定义的“共用体”变量a,b,c各占4个字节(因为一个float型变量占4个字节),而不是4+1+4=9个字节。
2.共用体变量的引用
只有先定义了共用体变量才能引用它,而且不能引用共用体变量,而只能引用共用体变量中的成员。例如,上面定义了a,b,c为共用体变量,下面的引用方式是正确的。
a.i (引用共用体变量中的整型变量i)
a.ch (引用共用体变量中的字符变量ch)
a.f (引用共用体变量中的实型变量f)
不能只引用共用体变量,例如下面是错误的:
printf(“%f”,a);
因为a的存储区可以按不同的类型存放数据,有不同的长度,仅写共用体变量名a,系统无法知道究竟应输出哪一个成员的值。
【例7.8】引用共用体变量。
程序代码如下:
#include <stdio.h>
union Demo
{
char a;
int b;
int c;
};
main()
{
union Demo d;
d.a = 'H';
d.b = 63;
d.c = 97;
printf("size: %d\n", sizeof(d));
printf("%c\t%d\t%d\n", d.a, d.b, d.c);
}
运行结果如下:
size:4
a 97 97
说明:
共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中值就取代。例7.7中,d.a,d.b,d.c先后被赋值,那么最终起作用的是最后被赋值的d.c=97,原来的‘H’和63都被覆盖了,对d.a按“%c”进行输出,40是字符’a’的ASCⅡ码,因此输出字符’a’。
3.共用体类型数据的特点
(1)同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种。
(2)共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。
(3)共用体变量的地址和它的各成员的地址都是同一地址。
(4)不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,不能在定义共用体变量时对它初始化。
(5)不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针。
(6)共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。
三、任务实施
先录入前4项数据,然后用if语句检查刚才录入的职业(job),如果是‘S’,表示是学生,则第5项应输入一个班级号,用输入格式符%d把一个整数送到共用体数据元素中的成同category.clas中。如果职业是‘T’,表示是教师,则输入第5项时就用输入格式符%S把一个字符串(职位)送到共用体数组元素中的成员category.position中。处理后,结构体数组元素person[0]中的共用体成员category的存储空间中,存放的是整数,而person[1]中的共用体成员category的存储空间中,存放的是字符串。
程序代码如下:
#include <stdio.h>
struct
{
int num;
char name[10];
char sex;
char job;
union
{
int clas;
char position[10];
}category;
}person[2];
void main()
{ int i;
for(i=0;i<2;i++)
{
printf("请录入人员信息:\n");
scanf("%d %s %c %c",&person[i].num,&person[i].name,&person[i].sex, &person[i].job);
if(person[i].job == 'S')
scanf("%d", &person[i].category.clas);
else if(person[i].job == 'T')
scanf("%s", person[i].category.position);
else
printf("Input error!");
}
printf("\n");
printf("No. name sex job class/position\n");
for(i=0;i<2;i++)
{
if (person[i].job == 'S')
printf("%-6d%--10s%-5c%-5c%-6d\n",person[i].num,person[i].name,
person[i].sex, person[i].job, person[i].category.clas);
else
printf("%-6d%-10s%-5c%-5c%-6s\n",person[i].num,person[i].name,
person[i].sex, person[i].job, person[i].category.position);
}
}
运行结果如下:
请录入人员信息:
001 Liyi M S 301
请录入人员信息:
002 Zhang W T prof
No. Name sex job class/position
1 Liyi M S 301
2 Zhang W T prof
注意:
代码中班级成员为clas(由于class是C++的关键字)。
任务六:利用共用体处理学生和教师信息
知识要点:枚举类型
一、任务分析
利用枚举类型模拟机器人控制系统中的指令,并控制机器人在平面内的移动。
机器人在移动过程中,可以接受上、下、前、后、左、右指令,利用枚举类型enum Direction{up,down,forward,back,left,right}定义机器人可以处理的指令集合。函数int move(enum Direction command,int *px,int *py)描述了翻译与执行系统,从而实现机器人根据控制指令进行移动。
二、必备知识与理论
在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
1.枚举类型的定义
枚举的定义枚举类型定义的一般形式为:
enum 枚举名{ 枚举值表 };
在枚举值表中应罗列出所有可用值,这些值也称为枚举元素。
例如:enum weekday{sun,mon,tue,wed,thu,fri,sat};
该枚举名为weekday,枚举值共有7个,即一周中的七天。凡被说明为weekday类型变量的取值只能是七天中的某一天。
2.枚举变量的说明
如同结构和联合一样,枚举变量也可用不同的方式说明,即先定义后说明,同时定义说明或直接说明。设有变量a,b,c被说明为上述的weekday,可采用下述任一种方式:
enum weekday{ sun,mon,tue,wed,thu,fri,sat };
enum weekday a,b,c;
或者为:enum weekday{ sun,mon,tue,wed,thu,fri,sat }a,b,c;
或者为:enum { sun,mon,tue,wed,thu,fri,sat }a,b,c;
3.枚举类型的引用
枚举类型在使用中有以下规定:
枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。
例如对枚举weekday的元素再作以下赋值:
sun=5;
mon=2;
sun=mon;
都是错误的。
枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…。如在weekday中,sun值为0,mon值为1,…,sat值为6。
【例7.9】枚举元素的序号数值。
main()
{ enum weekday
{ sun,mon,tue,wed,thu,fri,sat } a,b,c;
a=sun;
b=mon;
c=tue;
printf("%d,%d,%d",a,b,c);
}
输出结果为:0,1,2
说明:只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如:
a=sum;
b=mon;
是正确的。而:
a=0;
b=1;
是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换。
如:a=(enum weekday)2;
其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于:a=tue;
还应该说明的是枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
也可以人为地指定枚举元素的数值,在定义枚举类型时显式地指定,例如:
enum weekday{ sun=7,mon=1,tue,wed,thu,fri,sat }workday,week_end;
指定枚举常量sun的值为7,mon的值为1,以后顺序加1,sat为6。
【例7.10】循环显示枚举类型。
#include <stdio.h>
void main()
{ enum body{a,b,c,d } month[31];
int i,j;
j=a;
for(i=1;i<=30;i++){
month[i]=j;
j++;
if (j>d) j=a;
}
for(i=1;i<=30;i++){
switch(month[i])
{ case a:printf(" %2d %c\t",i,'a'); break;
case b:printf(" %2d %c\t",i,'b'); break;
case c:printf(" %2d %c\t",i,'c'); break;
case d:printf(" %2d %c\t",i,'d'); break;
default:break;
}
}
printf("\n");
}
运行结果如下:
1 a 2 b 3 c 4 d 5 a 6 b 7 c 8 d 9 a 10 b
11 c 12 d 13 a 14 b 15 c 16 d 17 a 18 b 19 c 20 d
21 a 22 b 23 c 24 d 25 a 26 b 27 c 28 d 29 a 30 b
4.类型定义符typedef
C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。例如,有整型量a,b,其说明如下:int a,b;
其中int是整型变量的类型说明符。int的完整写法为integer,为了增加程序的可读性,可把整型说明符用typedef定义为:
typedef int INTEGER
这以后就可用INTEGER来代替int作整型变量的类型说明了。
例如:INTEGER a,b;
它等效于:int a,b;
用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。
例如:typedef char NAME[20]; 表示NAME是字符数组类型,数组长度为20。然后可用NAME 说明变量,如:NAME a1,a2,s1,s2;
完全等效于:char a1[20],a2[20],s1[20],s2[20]
又如:typedef struct stu
{ char name[20];
int age;
char sex;
}STU;
定义STU表示stu的结构类型,然后可用STU来说明结构变量:
STU body1,body2;
typedef定义的一般形式为:
typedef 原类型名 新类型名
其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。
三、任务实施
机器人在移动过程中,可以接受上、下、前、后、左、右指令,利用枚举类型enum Direction{up,down,forward,back,left,right}定义机器人可以处理的指令集合。函数int move(enum Direction command,int *px,int *py)描述了翻译与执行系统,从而实现机器人根据控制指令进行移动。
程序代码如下:
#include <stdio.h>
enum Direction{up,down,forward,back,left,right};
void main()
{ enum Direction commands[10]={forward,right,forward,right,forward, right, forward, right,forward,right};
int move(enum Direction command,int *px,int *py);
int x=0,y=0;
int i=0;
for (i=0;i<10;i++)
{ move(commands[i],&x,&y);
printf("\nPosition[%d] is (%d,%d)",i+1,x,y);
}
printf("\n");
}
int move(enum Direction command,int *px,int *py)
{
int nRet=1;
static int x=0,y=0 ;
switch(command)
{
case left:
x-=1; break;
case right:
x+=1; break;
case forward:
y+=1; break;
case back:
y-=1; break;
default:
nRet=0; break;
}
*px=x;
*py=y;
return nRet;
}
运行结果如下:
Position[1] is (0,1)
Position[2] is (1,1)
Position[3] is (1,2)
Position[4] is (2,2)
Position[5] is (2,3)
Position[6] is (3,3)
Position[7] is (3,4)
Position[8] is (4,4)
Position[9] is (4,5)
Position[10] is (5,5)