2020/1/211C语言程序设计2020/1/212第八章结构、联合、枚举主讲:计算机学院朱立华2020/1/213内容提要本章介绍3种用户自定义类型:结构、联合、枚举需要掌握这几种用户自定义类型的以下知识:•为什么要定义这种类型,是为了适合何种数据存储与处理需要的•该类型定义的形式•该类型的变量在内存中占用空间的形式及其访问方式重点是结构类型,对该类型还应掌握以下知识:•该类型的嵌套定义,其不同层次成员的访问方式•在结构中的同名问题•结构数组与结构指针的使用(处理一批记录)•结构类型在函数中的使用:形参的设定,返回值等问题•一些常用算法在结构中的实现(如排序)2020/1/214结构——类型定义为什么需要结构类型:•有的时候需要一组相关的信息表示同一个对象,例如一个学生的信息包括:学号、姓名、性别、成绩等,这些数据项的类型不一定一致,即使是同一种类型也会对应不同的意义,因此需要将它们作为一个变量的成员组成一个整体。•此前所学的标准类型及数组和指针类型都无法满足这一需求结构类型的定义格式:struct结构类型名{结构体成员表列;};此关键字为定义结构类型的起始标志结构类型名必须是用户自定义标识符一对大括号必须有,其间是该结构体类型的各个成员必须以分号结束成员列表的形式:成员类型名成员名;相同类型的成员名可以共用一个成员类型名2020/1/215结构——类型定义结构类型的定义举例:structDate//表示日期的结构{intyr;//年intmo;//月intda;//日};structStudent//表示学生信息的结构{longunsignedid;//学号doublegrades;//成绩};intyr,mo,da;//年月日类型相同可用一个类型标识符类型不一致的成员必须分别定义2020/1/216结构变量的定义结构类型定义后,struct结构类型名就作为结构类型名使用,可以定义该类型的变量,有3种形式:•(1)先定义结构类型,再定义结构变量•例:structDatedt;•structStudentst;•(2)在定义结构类型的同时定义结构变量,结构类型名不省略,将结构变量名写在结构类型定义结束的右大括号之后、分号之前•例:structDate//不省略结构类型名•{•intyr,mo,da;//年、月、日•}dt;//定义结构变量dt(3)结构类型名省略结构类型名2020/1/217用typedef定义结构类型别名用typedef可以为一个结构类型定义别名,以方便定义结构类型的变量,定义结构类型别名也有3种形式:•(1)先定义结构类型,再定义结构类型的别名:•例:typedefstructDateDate;•Datedt;•(2)在定义结构类型的同时定义其别名,结构类型名不省略,将结构类型别名写在结构类型定义结束的右大括号之后、分号之前•例:typedefstructDate//不省略结构类型名•{intyr,mo,da;//年、月、日•}Date;//定义结构类型别名Date(3)结构类型名省略结构类型名给已经定义的类型structDate定义一个类型别名Date直接用Date作为结构类型名定义一个结构变量dt2020/1/218结构变量的访问结构变量的初始化:与其他类型的变量类似,结构变量在定义时也可以进行初始化,常用两种方式:•(1)将初值用一对大括号括起,依次列出各个成员的值,所列出的值可以少于成员个数,默认用0填充•例:Datedt1={2005,8,16};•Datedt2={2009,4};•(2)两个类型一致的结构变量可以用一个为另一个初始化•例:Datedt4,dt3=dt1;结构变量在定义之后,可进行赋值,满足以下要求:•(1)两个类型一致的结构变量可以用一个为另一个赋值•(2)注意:结构变量定义之后,不能再用各个成员的值给结构变量作整体赋值相当于:Datedt2={2009,4,0};用dt1来初始化dt3,这里的dt4未初始化例:dt4=dt1;例:Datedt;dt={2005,8,16};X2020/1/219结构变量成员的访问结构变量占用内存的方式:•与数组很类似,一个结构变量的各个成员在内存中依次占用地址相邻的内存单元,因此一个结构变量所占的空间至少为其各个成员所需的内存空间之和结构变量与其成员的关系:•结构变量是一组(成员)变量的整体标志,是“外衣”,其中的每一个成员变量(简称成员)都是这个整体中的成分。引用结构变量成员的方式:•必须从结构变量名开始,在其后加成员引用运算符“.”,再加成员名:•例:Datedt1={2005,8,16};•相当于:dt1.yr=2005;dt1.mo=8;dt1.da=16;2020/1/2110结构变量成员的访问程序8.1结构变量成员的引用示例该例中用到了前面例子中的两个结构体类型:structStudent//表示学生信息的结构{longunsignedid;//学号floatgrades;//成绩};structDate//表示日期的结构{intyr,mo,da;};动态演示过程2020/1/2111结构指针结构指针的概念:•就是基类型为结构类型的指针,只能获得基类型一致的结构类型变量的地址.结构指针的定义形式:•结构类型名*结构指针变量名;通过结构指针引用结构成员的两种等价方式:•(1)(*结构指针).结构成员•(2)结构指针-结构成员•例:Students;•Student*ps=&s;//指向结构s的指针ps•ps-id=2005065;//相当于(*ps).id=2005065;•ps-grades=90.5;//相当于(*ps).grades=90.5;这里的Student是用typedef定义过的结构体类型别名2020/1/2112结构数组结构数组的概念:•就是基类型为结构类型的数组,因为数组类型并未规定其基类型必须是什么类型,事实上可以是任何类型结构数组的定义形式:•结构类型名结构数组名[整型常量表达式];通过结构数组引用结构成员的3种方式:•(1)结构数组名[下标].结构成员•(2)(*(结构数组名+下标)).结构成员•(3)(结构数组名+下标)-结构成员•例:Studentsa[3];•sa[i].id//第i+1个学生的学号•sa[i].grades//第i+1个学生的成绩这里的Student是用typedef定义过的结构体类型别名方式1(*(sa+i)).id(*(sa+i)).grades方式2(sa+i)-id(sa+i)-grades方式32020/1/2113结构数组结构数组可以初始化,方法是:•每个数组元素的各个分量用第二层括号的括起。•例:Studentsa[3]={{1001,80},{1002,89},{1003,90}};结构数组在内存中的存放方式:•占用一组地址连续的空间,数组元素相邻,元素的各成员相邻100180100289100390sa[0].idsa[0].gradessa[1].idsa[1].gradessa[2].idsa[2].grades程序8.2从键盘上输入3个学生记录,并在屏幕输出动态演示过程(请在VC++下运行)2020/1/2114结构的嵌套结构嵌套的概念:•结构的成员可以是任何类型,当然也可以是结构类型,这样就形成了结构的嵌套结构嵌套的定义形式:•(1)先定义”小”的结构类型,再在”大”的结构类型中用其作为某成中类型名•(2)将小结构类型的定义嵌套在大的结构类型定义中•例:structDate{•intyr,mo,da;•};•typedefstructDateDate;structStudent{longunsignedid;doublegrades;Datedt;//入学日期dt};typedefstructStudentStudent;方式1structDate{intyr,mo,da;}dt;方式22020/1/2115结构的嵌套程序8.3:用结构嵌套定义一个学生结构,从键盘上输入3个学生记录,并在屏幕输出。主函数输入部分主要代码:(输出的代码类似)•inti;•Studentsa[3];•printf(Enter3records:\n);•for(i=0;i3;i++)//输入3个学生的各项信息•{•scanf(%Lu%Lf,&sa[i].id,&sa[i].grades);•scanf(%d/%d/%d,&sa[i].dt.yr,&sa[i].dt.mo,&sa[i].dt.da);•}动态演示过程2020/1/2116结构型返回值和地址调用结构类型也可以作为返回类型根据第6章有返回值的函数调用过程中经过3次赋值的知识,结构类型作为函数返回类型显然是低效的•结构变量由很多成员组成,一般占用空间较大•这就意味着临时变量的空间较大,赋值也需要时间因此一般将返回值类型改为void型,而增设一个结构类型的指针形参,通过间接引用修改对应实参结构体变量的值。下面的动态演示展示了二者的区别与效率差异本例再一次体现出指针的高效性动态演示过程2020/1/2117结构应用举例---学生记录排序分析:一组学生记录,首先要定义一维数组,该数组的元素类型为结构类型,因此本题需定义一维结构数组排序算法:第6章所学的排序算法在这里都可以使用,只是在元素互相交换时,体现为结构体变量的交换函数参数的选择:由第6章的知识,本题形参用一级指针最高效,指针的基类型就是一维数组的基类型模块的划分:定义一个输入函数、排序函数、输出函数,主函数定义一维结构数组作为实参,依次调用这3个函数完成整个程序结构类型:本例的结构类型就是前面一直介绍的Student类型,包括学号和成绩两个域2020/1/2118结构应用举例---学生记录排序读入部分:见动态演示排序算法:voidSelectSort(Student*pa,intn)//选择排序{inti,j,min;Studenttemp;//定义结构临时变量for(i=0;in-1;i++)//控制n-1趟循环{min=i;//用min记下本趟分数最小元素的下标for(j=i+1;jn;j++)if(pa[j].gradespa[min].grades)min=j;if(min!=i)//如果分数最小元素未到位,交换元素{temp=pa[i];pa[i]=pa[min];pa[min]=temp;}}}动态演示过程完整程序请上机运行2020/1/2119结构应用举例---洗牌程序8.5洗牌程序。假设洗牌之前52张扑克牌按照红桃、方块、梅花和黑桃四种花色的顺序,每种花色又是按照从1到13的顺序排列。通过产生随机数模拟洗牌,每一张牌与随机选定的一张牌调换,从而打乱最初的次序。问题分析:•(1)每张扑克牌用哪些信息可以完整表示---花色+点数•结构类型的定义:•structCard•{charsuit;//记录花色•intpips;//记录点数•};•typedefstructCardCard;2020/1/2120结构应用举例---洗牌•(2)所有扑克牌信息的存储需要用一维结构数组•结构数组的定义:Carddeck[52];•(3)初始信息的表示:52张牌最初按红桃、方块、梅花和黑桃四种花色的顺序,每种花色又按照从1到13的顺序排列•for(i=0;i52;i++)•{deck[i].suit=i/13+3;//赋花色的ASCII码•deck[i].pips=i%13+1;//赋每张牌的点数•}•(4)关键的步骤,如何实现模拟洗牌?•编写洗牌函数Shuffle(structCard*p,intn)。通过一个循环,每一张牌与随机选定的一张牌调换。红桃、方块、梅花和黑桃的ASCII码分别是3、4、5和6调用随机函数产生0~51之间的随机数作为第几张牌序号2020/1/2121结构应用举例---洗牌•(2)