1、第九章第九章结构结构和和其他数据机制其他数据机制介绍介绍C语言的其他数据描述机制,包括结构(语言的其他数据描述机制,包括结构(struct)、)、联合(联合(union)、枚举()、枚举(enum)等。)等。各种数据机制的概念、意义和用途。各种数据机制的概念、意义和用途。在写处理复杂数据的程序,在定义复杂数据类型和结在写处理复杂数据的程序,在定义复杂数据类型和结构时,这些机制应用广泛。构时,这些机制应用广泛。“数据结构数据结构”课程中将大课程中将大量使用这些机制。量使用这些机制。这里主要介绍概念,给出一些程序实例。并介绍一些这里主要介绍概念,给出一些程序实例。并介绍一些相关的概念和技术。相关的
2、概念和技术。后续课程和其他书籍中可以看到大量实例。后续课程和其他书籍中可以看到大量实例。9.1 结构(结构(struct)被处理的数据常形成一些逻辑整体,有紧密内在联系。被处理的数据常形成一些逻辑整体,有紧密内在联系。各成分的性质类似(同类型)时可以用数组。各成分的性质类似(同类型)时可以用数组。数据体成分类型不同的情况很普遍。如居民身份证,成数据体成分类型不同的情况很普遍。如居民身份证,成分有:姓名、性别、民族、出生日期、住址、身份证号分有:姓名、性别、民族、出生日期、住址、身份证号码、发证日期、有效期限和发证单位,照片。这组信息码、发证日期、有效期限和发证单位,照片。这组信息共同描述了一个
3、居民。无法用数组表示。共同描述了一个居民。无法用数组表示。许多语言提供了可将多个相同或不同类型的数据组合起许多语言提供了可将多个相同或不同类型的数据组合起来的机制,常称来的机制,常称记录记录(record)。C称为称为结构结构(structure)。结构是复合数据对象,其中的数据项称为结构的结构是复合数据对象,其中的数据项称为结构的成分成分或或成员成员。成员给定。成员给定名字名字,通过成员名访问结构成员。,通过成员名访问结构成员。结构说明与结构变量定义结构说明与结构变量定义结构说明由结构说明由struct引导,基本形式:引导,基本形式:struct 成员说明序列成员说明序列;成员说明成员说明形
4、式同变量定义:形式同变量定义:struct int n;double x,y;单独的结构说明只描述了结构本身(的结构)。单独的结构说明只描述了结构本身(的结构)。struct int n;double data100;一个结构里可以有任意多个成员,成员可为任何类型,一个结构里可以有任意多个成员,成员可为任何类型,如基本类型、指针或数组成员。成员也可以是结构。如基本类型、指针或数组成员。成员也可以是结构。一个结构里各个成员的名字不能相互冲突。不同结构完一个结构里各个成员的名字不能相互冲突。不同结构完全可以包含名字相同的成员,相互无关。全可以包含名字相同的成员,相互无关。定义结构变量定义结构变量把
5、结构说明当作类型,就可以定义结构变量:把结构说明当作类型,就可以定义结构变量:struct int n;double x,y;st1,st2;st1和和st2都是这种结构的变量,包含相应的成分。都是这种结构的变量,包含相应的成分。结构标志及其使用:结构标志及其使用:加结构标志可避免反复写同样结构描述。标识符表示。加结构标志可避免反复写同样结构描述。标识符表示。struct point double x,y;struct circle struct point center;double radius;/*定义结构成员时利用结构标志定义结构成员时利用结构标志*/struct rectangle
6、struct point lu;struct point rd;利用结构标志定义结构变量:利用结构标志定义结构变量:struct point pt1,pt2;struct circle circ1,circ2;与结构有关的函数的原型说明:与结构有关的函数的原型说明:double dist(stuct point p1,struct point p2);结构标志可用于定义指向结构的指针,做类型转换,结构标志可用于定义指向结构的指针,做类型转换,计算大小,建立存放结构的存储块:计算大小,建立存放结构的存储块:struct circle*p;p=(stuct circle*)malloc(sizeo
7、f(struct circle);.定义结构类型定义结构类型在程序里反复使用的结构最好是定义为类型:在程序里反复使用的结构最好是定义为类型:typedef struct double x,y;POINT;typedef struct POINT center;double radius;CIRCLE;typedef struct POINT lu;POINT rd;RECTANGLE;定义结构类型使程序更简短清晰。例:居民身份证信息定义结构类型使程序更简短清晰。例:居民身份证信息的结构类型定义的结构类型定义:typedef struct /*数据表示问题数据表示问题*/char name20;
8、/*名字字符串用多长?名字字符串用多长?*/int sex;/*.*/char nationality10;int born_in3;char address50;int date_of_issue3;int valid_years;char issued_by30;char id_number18;char photo10064;IDCARD;无效结构定义无效结构定义结构成员不能是被描述的结构本身。结构成员不能是被描述的结构本身。非法结构描述的例子:非法结构描述的例子:struct invalid int n;struct invalid iv;这种描述定义的结构里包含自身,引起了无穷嵌套,
9、不这种描述定义的结构里包含自身,引起了无穷嵌套,不合理,也不可能在计算机里实现。合理,也不可能在计算机里实现。结构的实现结构的实现一个结构占一块连续存储,各成员顺序存放。一个结构占一块连续存储,各成员顺序存放。CIRCLE对象的存储方式。对象的存储方式。typedef struct POINT center;double radius;CIRCLE;POINT 结构CIRCLE 结构yxradius对齐问题对齐问题结构成员类型可不同,引起结构成员类型可不同,引起对齐对齐问题:问题:struct exam char aa;int nn;double xx;基本数据存放有规定:基本数据存放有规定:
10、2字节整数从偶地址开始;字节整数从偶地址开始;8字字节节double可能可能从从4(或(或8)的倍数地址开始。)的倍数地址开始。1)结构成员的存放位置之间可能有空位;)结构成员的存放位置之间可能有空位;2)结构大小未必等于成员大小之和。)结构大小未必等于成员大小之和。应该用应该用sizeof计算计算结构大小(例如动态存储分配时)结构大小(例如动态存储分配时)。结构变量的初始化与使用结构变量的初始化与使用定义结构变量时可以直接初始化。例:定义结构变量时可以直接初始化。例:POINT pt1=2.34,3.28,pt2;CIRCLE circ1=3.5,2.07,1.25,circ2=12.35,
11、10.6,2.56;初值表达式应能静态求值,顺序赋给基本成员初值表达式应能静态求值,顺序赋给基本成员。嵌套结。嵌套结构可加括号。项数不得多于结构变量所需,不够补构可加括号。项数不得多于结构变量所需,不够补0。未提供初始值时外部和静态局部结构变量的成员初始化未提供初始值时外部和静态局部结构变量的成员初始化为为0;自动变量不初始化。;自动变量不初始化。初值式只能用于变量定义。初值式只能用于变量定义。结构变量的使用结构变量的使用整体赋值整体赋值同类型结构变量可整体赋值,效果是各成员分别赋值:同类型结构变量可整体赋值,效果是各成员分别赋值:pt2=pt1;结构不能做相等结构不能做相等/不等比较不等比较
12、。成员访问成员访问访问结构成员用圆点运算符(访问结构成员用圆点运算符(.),最高优先级,自左),最高优先级,自左向右结合。例子:向右结合。例子:pt2.y=pt1.y+2.4;circ1.center.x=2.07;circ1.center.y=pt1.y;将结构将结构circ2所表示的圆做平移,可以写:所表示的圆做平移,可以写:circ2.center.x+=2.8;circ2.center.y+=0.24;结构成员相当于相应类型的变量,操作由类型决定。结构成员相当于相应类型的变量,操作由类型决定。int main()POINT pt1=2.34,3.28,pt2;CIRCLE circ1=
13、3.5,2.07,1.25;double x,y;pt2.x=8.0;pt2.y=circ1.center.y+3.44;x=pt2.x-pt1.x;y=pt2.y-pt1.y;printf(distance:%fn,sqrt(x*x+y*y);return 0;结构、数组与指针结构、数组与指针结构可以有数组成员(身份证例子)。也可以定义以一结构可以有数组成员(身份证例子)。也可以定义以一组结构为元素的数组(结构数组)。组结构为元素的数组(结构数组)。例:用结构数组重构例:用结构数组重构C程序关键字统计程序。程序关键字统计程序。已讨论两种实现,已讨论两种实现,1)两维字符数组和计数器数组;)两
14、维字符数组和计数器数组;2)用字符指针数组和计数器数组。用字符指针数组和计数器数组。更合理方式是用一个结构表示与一个关键字有关的所更合理方式是用一个结构表示与一个关键字有关的所有信息。为此可定义结构类型。有信息。为此可定义结构类型。typedef struct char*key;int count;KEYC;/*“关键字计数器关键字计数器”类型类型*/程序数据结构就是一个程序数据结构就是一个KEYC数组,定义时初始化:数组,定义时初始化:KEYC keytable32=auto,0,break,0,.volatile,0,while,0;/*初始化中也可写嵌套的括号初始化中也可写嵌套的括号*/
15、程序的主循环可写为:程序的主循环可写为:int i;.while(getident(.,s)0)for(i=0;i 0)for(p=keytable;p:p-key 等价于等价于(*p).key-具有最高优先级(与圆点,函数调用具有最高优先级(与圆点,函数调用()及数组元素及数组元素访问访问一样一样),从左向右结合。),从左向右结合。while(getident(s,.)0)for(p=keytable;p key)=0)p-count+;break;已经讨论了已经讨论了C语言的所有运算符,附录语言的所有运算符,附录A列出这些运算列出这些运算符:意义符:意义/优先级优先级/结合方式。请自己查阅
16、。结合方式。请自己查阅。结构可作为函数参数和函数返回值。将结构数据传给函结构可作为函数参数和函数返回值。将结构数据传给函数有三种方式(返回值情况类似):数有三种方式(返回值情况类似):1)传结构成员的值,只要该成员能赋值。)传结构成员的值,只要该成员能赋值。2)传递整个结构的值。)传递整个结构的值。结构参数结构参数。3)传结构的地址(指针)。)传结构的地址(指针)。结构指针参数结构指针参数。结构参数:实参结构变量的值整个结构参数:实参结构变量的值整个赋给形参赋给形参,函数内对,函数内对形参的操作不会影响实参。形参的操作不会影响实参。结构指针参数:传结构指针可以避免复制整个结构。结构指针参数:传
17、结构指针可以避免复制整个结构。可以对实参结构做任何操作,包括修改。若结构很大,可以对实参结构做任何操作,包括修改。若结构很大,复制费时间,也可考虑用指针方式传递。复制费时间,也可考虑用指针方式传递。9.2 结构与函数结构与函数例:由参数值构造例:由参数值构造POINT类型的结构值,函数定义:类型的结构值,函数定义:POINT mkpoint1(double x,double y)POINT temp;temp.x=x;temp.y=y;return temp;返回值可赋给同类型变量。这种函数从成员值构造结构返回值可赋给同类型变量。这种函数从成员值构造结构值,可称为值,可称为结构值构造函数结构值
18、构造函数。使用:。使用:pt1=mkpoint1(3.825,20.7);pt2=mkpoint1(pt1.x,0.0);temp随函数结束而撤消,值返回,与局部变量的撤消随函数结束而撤消,值返回,与局部变量的撤消无关。返回大的结构值时需要复制大批数据无关。返回大的结构值时需要复制大批数据。例:由参数值构造例:由参数值构造CIRCLE类型的结构值,函数定义:类型的结构值,函数定义:CIRCLE mkcircle(POINT c,double r)CIRCLE temp;temp.center=c;temp.radius=r;return temp;函数有一个结构参数。用例:函数有一个结构参数。
19、用例:circ1=mkcircle(pt1,5.254);circ2=mkcircle(circ1.center,11.7);circ3=mkcircle(mkpoint1(2.05,3.7),3.245);第三个例子:第三个例子:mkpoint返回返回POINT值,传给值,传给mkcircle的的POINT类型参数是合理的。类型参数是合理的。定义计算两个点之间距离的函数,采用结构参数:定义计算两个点之间距离的函数,采用结构参数:double distance(POINT p1,POINT p2)double x=p1.x-p2.x,y=p1.y-p2.y;return sqrt(x*x+y*
20、y);复制整个结构,语义清楚。缺点是整体复制开销较大。复制整个结构,语义清楚。缺点是整体复制开销较大。例:打印身份证中身份证号和姓名的函数:例:打印身份证中身份证号和姓名的函数:void prtIDCard0(IDCARD ic)printf(%sn,ic.id_number);printf(%snn,ic.name);复制整个结构,大部分复制工作没有价值。复制整个结构,大部分复制工作没有价值。可考虑传递结构指针,下面函数完成同样工作:可考虑传递结构指针,下面函数完成同样工作:void prtIDCard(IDCARD*icp)printf(%sn%snn,icp-id_number,icp-
21、name);只复制一个指针,更合理。设只复制一个指针,更合理。设idc是是IDCARD变量:变量:prtIDCard(&idc);例,打印一个身份证结构数组中的各身份证的信息:例,打印一个身份证结构数组中的各身份证的信息:IDCARD idcs10000,*p;./*假设假设idcs的元素都有了值的元素都有了值*/for(p=idcs;p x=x;p-y=y;return p;调用时用指向调用时用指向POINT的指针接收函数返回值。的指针接收函数返回值。POINT*q;q=mkpoint2(2.22,1.784);pp1=mkpoint2(2.57,3.86);pp2=mkpoint2(pp1
22、-y+2.6,pp1-x 0.7);./*使用建立的结构使用建立的结构*/free(pp1);free(pp2);/*最后释放存储最后释放存储*/使用实例:使用实例:POINT*pp1,*pp2;.采用动态管理优点:所建立结构的存在期不受建立位采用动态管理优点:所建立结构的存在期不受建立位置约束,传递指针不必复制整个结构。这种方式在复置约束,传递指针不必复制整个结构。这种方式在复杂软件里用的很多。杂软件里用的很多。数据结构课程中大量采用这种方式。数据结构课程中大量采用这种方式。程序实例程序实例基于基于结构结构设计实现第设计实现第8章的账目管理系统。章的账目管理系统。需要记录每笔收支的日期、项目
23、简记和相应金额。负数需要记录每笔收支的日期、项目简记和相应金额。负数表示支出,正数表示收入。设程序输入按行进行,每行表示支出,正数表示收入。设程序输入按行进行,每行输入一笔账目。这里考虑相关数据结构和输入问题。显输入一笔账目。这里考虑相关数据结构和输入问题。显然,有关一笔账的信息是一个逻辑整体,应考虑用一种然,有关一笔账的信息是一个逻辑整体,应考虑用一种结构表示。为此设计下面的结构类型:结构表示。为此设计下面的结构类型:typedef struct int year,month,day;/*年月日年月日*/char outlineLINELEN;/*项目简记项目简记*/double amout
24、;/*金额金额*/ACCITEM;账本就是账本就是ACCITEM的数组。例如可以定义:的数组。例如可以定义:enum NACC=400;ACCITEM accbookNACC;作为程序里的基本数据表示。作为程序里的基本数据表示。考虑输入。应定义为函数,由它填充一个考虑输入。应定义为函数,由它填充一个ACCITEM的的数组。为使同一函数能适应各种输入源,可引进一个文数组。为使同一函数能适应各种输入源,可引进一个文件指针参数,用下面原型:件指针参数,用下面原型:int readall(FILE*fp,int limit,ACCITEM accbook);使定义方便,可以增加一个基本账目项输入函数:
25、使定义方便,可以增加一个基本账目项输入函数:int readitem(FILE*fp,ACCITEM*ip);需要用结构指针参数,将实际结构地址传递给函数。需要用结构指针参数,将实际结构地址传递给函数。readitem输入可能出现几种情况:输入可能出现几种情况:1)正常完成一项)正常完成一项读入;读入;2)出错;)出错;3)已读完所有数据。让)已读完所有数据。让readitem分分别返回别返回1,0和和-1。readall的读入循环可以写成:的读入循环可以写成:while(1)switch(readitem(fp,&accbooki)case 1:+i;continue;case 0:./*处
26、理输入出错处理输入出错*/continue;case-1:break;/*有许多其他写法,例如用有许多其他写法,例如用if语句语句*/readitem的最简单实现:的最简单实现:int readitem(FILE*fp,ACCITEM*ip)int n=fscanf(fp,%d%d%d%s%lf,&ip-year,&ip-month,&ip-day,ip-outline,&ip-amount);return n=5?1:n=EOF?-1:0;注意,假设了帐目条目对应于输入行,注意,假设了帐目条目对应于输入行,readitem里的里的输入错误处理也应反映输入行的概念。不应企图在输入错误处理也应反
27、映输入行的概念。不应企图在这里这里给出错误信息或重新输入(按照设计,处理这些事项是给出错误信息或重新输入(按照设计,处理这些事项是readall的责任)。的责任)。实现交互的主函数(驱动程序)实现交互的主函数(驱动程序):int main(void)int n,inum=0;ACCITEM accbkNACC;initialization();/*系统的初始化处理(可能有)系统的初始化处理(可能有)*/while(n=getcommand()=0)switch(n)case 0:/*要求账目文件名并读入要求账目文件名并读入*/inum=readfile(NACC,accbook);break;
28、case 1:/*计算最终余额计算最终余额*/balance(inum,accbook);break;default:errmessage();break;finalization();/*系统终止处理(可能有)系统终止处理(可能有)*/return 0;开始调用开始调用initialization完成启动准备,如输出一完成启动准备,如输出一些信息,介绍程序使用。最后些信息,介绍程序使用。最后finalization完成退出完成退出前的处理。如果增加有关一次执行所处理的文件总统计前的处理。如果增加有关一次执行所处理的文件总统计信息,可能就要增加一些全局变量,由上述函数完成其信息,可能就要增加一
29、些全局变量,由上述函数完成其初始化和最后统计输出。初始化和最后统计输出。作业提出了一些扩充建议。作业提出了一些扩充建议。getcommand应输出提示信息并读入用户命令,返回命应输出提示信息并读入用户命令,返回命令编码。可能采用各种方式。实现时要考虑处理用户错令编码。可能采用各种方式。实现时要考虑处理用户错误输入,这里用的方式是命令错误时返回某个大数,当误输入,这里用的方式是命令错误时返回某个大数,当用户要求结束时返回负数。用户要求结束时返回负数。readfile完成与输入有关的所有工作,返回实际输入完成与输入有关的所有工作,返回实际输入的账目条数。工作包括:要求账目文件名,处理文件无的账目条
30、数。工作包括:要求账目文件名,处理文件无法正常打开情况,调用法正常打开情况,调用readall实际读入等。实际读入等。errmessage()输出错误信息,应对本程序的使用方输出错误信息,应对本程序的使用方式给出一些提示。式给出一些提示。其他函数完成的工作很明确,写函数的工作作为练习。其他函数完成的工作很明确,写函数的工作作为练习。还可以根据自己的考虑扩充这个系统。还可以根据自己的考虑扩充这个系统。日期用日期用3 3个整数表示有缺点:比较不方便,用个整数表示有缺点:比较不方便,用3个整数表个整数表示也浪费空间。可考虑用如下结构:示也浪费空间。可考虑用如下结构:typedef struct un
31、signed long date;/*年月日年月日*/char outlineLINELEN;/*项目简记项目简记*/double amout;/*金额金额*/ACCITEM;帐目项读入函数改为:帐目项读入函数改为:int readitem(FILE*fp,ACCITEM*ip)int y,m,d;int n=fscanf(fp,%d%d%d%s%lf,&y,&m,&d,ip-outline,&ip-amount);if(n!=5)return n=EOF?-1:0;ip-date=y*10000+m*100+d;return 1;其中的年、月、日很容易提取。例:其中的年、月、日很容易提取。例
32、:m=(item.date/100)%100;9.3 联合(联合(union)联合是几个成员的组合,各成员有名字,所有成员共享联合是几个成员的组合,各成员有名字,所有成员共享同一存储位置。同一存储位置。联合变量每时只能保存一个成员的值。联合变量每时只能保存一个成员的值。联合说明形式上与结构说明类似,用联合说明形式上与结构说明类似,用union:union int n;double x;char c;u1,u2;union data fun10(int m,union data u);union data u1,u2,u3,u4,*up;up=(union data*)malloc(sizeof
33、(union data);data 联合变量的初始化和使用联合变量的初始化和使用联合变量可以在定义时直接初始化,但这个初始化只联合变量可以在定义时直接初始化,但这个初始化只能对第一个成员做。例:能对第一个成员做。例:union data u1=3,u2=5;联合变量使用形式与结构变量相同,可整体赋值、成联合变量使用形式与结构变量相同,可整体赋值、成员访问、取地址。几个例子:员访问、取地址。几个例子:n=u1.n;u1.c=n;m=u2.n+167;可可定义指向联合的指针,可从这种指针出发,通过定义指向联合的指针,可从这种指针出发,通过-运算符访问被指联合变量的成员。运算符访问被指联合变量的成员
34、。联合变量的存储实现联合变量的存储实现成员共用同一存储位置,存储区大小由大成员决定。对成员共用同一存储位置,存储区大小由大成员决定。对union data,n是整数,是整数,d是双精度数,是双精度数,c是字符。需是字符。需要足以存放双精度数的存储区要足以存放双精度数的存储区。成员安排如下图:。成员安排如下图:成员 c 的存储位置和范围 成员 n 的存储位置和范围 图 9.2 联合 union data 的表示 成员 x 的存储位置和范围 联合变量使用的基本规则联合变量使用的基本规则联合变量可看成能改变面目的变量,不同时刻可能以联合变量可看成能改变面目的变量,不同时刻可能以不同性质出现,表示不同
35、东西。不同性质出现,表示不同东西。使用原则:应该按所保存成员的方式使用:最近对本使用原则:应该按所保存成员的方式使用:最近对本变量用什么方式赋值(当作哪个成员),就用同样方变量用什么方式赋值(当作哪个成员),就用同样方式(通过同样成员)取值。遵受本规则的使用有效,式(通过同样成员)取值。遵受本规则的使用有效,取值与赋值的一致性由编程者保证,取值与赋值的一致性由编程者保证,C系统不检查。系统不检查。未遵守规则的使用(取值方式与前面赋值不一致),未遵守规则的使用(取值方式与前面赋值不一致),语言定义未规定效果,结果依赖于具体系统。语言定义未规定效果,结果依赖于具体系统。介绍枚举之后有一个程序例子。
36、介绍枚举之后有一个程序例子。9.4 枚举(枚举(enum)枚举用于定义一组命名常量,枚举用于定义一组命名常量,枚举常量枚举常量。描述形式:。描述形式:enum 枚举标志枚举标志 枚举常量名枚举常量名,.;枚举说明不但引进一组常量名,还为每个常量确定一枚举说明不但引进一组常量名,还为每个常量确定一个整数值。个整数值。第一个给值第一个给值0,顺序递增。枚举常量不能重,顺序递增。枚举常量不能重名,多个枚举说明的常量名也必须互不相同。名,多个枚举说明的常量名也必须互不相同。enum color RED,GREEN,BLUE;枚举标志可用于写变量定义等。例:枚举标志可用于写变量定义等。例:enum co
37、lor cr1,cr2;定义枚举类型(定义枚举类型(用用typedef):):typedef enum WHITE,BLACK COLOR1;COLOR1 c3,c4;下面是这种变量在程序里使用的几个例子:下面是这种变量在程序里使用的几个例子:cr1=RED;.if(cr2=GREEN).;.switch(cr1)case RED:.;break;case GREEN:.;break;case BLUE:.;break;枚举类型变量实际看作整型变量。主要是提高可读性。枚举类型变量实际看作整型变量。主要是提高可读性。枚举常量由编译处理,一次可定义一组常量,很方便。枚举常量由编译处理,一次可定义一
38、组常量,很方便。可给枚举常量指定值,随后的枚举常量递增取值。例:可给枚举常量指定值,随后的枚举常量递增取值。例:enum color RED=1,GREEN,BLUE,WHITE=8,GREY=12,BLACK,PINK=15;程序实例程序实例使用联合通常是为定义一些函数,使几种不同类型的数使用联合通常是为定义一些函数,使几种不同类型的数据都可以传递给它们处理。据都可以传递给它们处理。例:角度可以表示为弧度(例:角度可以表示为弧度(0 0到到2 2)或角度(度分)。)或角度(度分)。可定义类型可定义类型ANGLE,所有处理程序都基于该类型,所有处理程序都基于该类型。ANGLE数据有两种表示形式
39、,可定义为联合类型。但处数据有两种表示形式,可定义为联合类型。但处理时怎么能知道具体理时怎么能知道具体ANGLE变量用的是那种形式?变量用的是那种形式?解决方案是在解决方案是在ANGLE类型里加标签(表示具体类型里加标签(表示具体ANGLE变变量数据情况的成员)。标签可用整数或其他类型,采用量数据情况的成员)。标签可用整数或其他类型,采用枚举特别合适,因为枚举符是标识符,有助理解。枚举特别合适,因为枚举符是标识符,有助理解。可以定义:可以定义:enum ANGLETAG DEGREE,RADIAN;typedef struct enum ANGLETAG tag;union struct in
40、t deg,mnt degree;double radian;dt;ANGLE;tag是表示标签的枚举成员,是表示标签的枚举成员,dt是表示实际数据的联是表示实际数据的联合,或是结构合,或是结构degree,或者是双精度数,或者是双精度数radian。有了这个类型后就可以定义相关函数了。有了这个类型后就可以定义相关函数了。例:定义输出角度的函数:例:定义输出角度的函数:void prtdegree(FILE*fp,ANGLE a)switch(a.tag)case DEGREE:fprintf(fp,%d degs%d minutes,a.dt.degree.deg,a.dt.degree.m
41、nt);break;case RADIAN:fprintf(fp,%f radian,a.dt.radian);break;其他函数类似。其他函数类似。9.5 编程实例编程实例数据组排序:数据组排序:考虑用结构重实现学生成绩实例。还想介绍考虑用结构重实现学生成绩实例。还想介绍排排序序的概念及标准库排序函数的使用。的概念及标准库排序函数的使用。记录文件应包含学生姓名和成绩。下面采用如下形式:记录文件应包含学生姓名和成绩。下面采用如下形式:02001014 zhangsan 8602001016 lisi 77为在程序里处理这样的学生成绩,定义结构类型:为在程序里处理这样的学生成绩,定义结构类型:
42、enum MAXNUM=400;/*还可以根据需要增加其他常量定义还可以根据需要增加其他常量定义*/typedef struct unsigned long num;char name20;double score;StuRec;定义一个外部(学生记录)数组(也可用动态分配):定义一个外部(学生记录)数组(也可用动态分配):StuRec studentsMAXNUM;程序可参考第程序可参考第6 6章程序实例。章程序实例。现在想增加功能:按成绩排列输出学生记录。可采用多现在想增加功能:按成绩排列输出学生记录。可采用多次扫描结构数组的方法,从高到低或从低到高输出(自次扫描结构数组的方法,从高到低或
43、从低到高输出(自己想想可能怎么做,要什么假定)。己想想可能怎么做,要什么假定)。显然不好,效率太低。显然不好,效率太低。另一方法是把学生记录按成绩重排而后顺序打印。计算另一方法是把学生记录按成绩重排而后顺序打印。计算机应用中将一组数据按某种顺序重排是最常见的操作,机应用中将一组数据按某种顺序重排是最常见的操作,排序排序,人们开发了许多有趣的算法。,人们开发了许多有趣的算法。排序排序是是数据结构数据结构课的重要内容,这里只想介绍标准课的重要内容,这里只想介绍标准库库qsort,它能将各种数组的元素按指定顺序排列,它能将各种数组的元素按指定顺序排列。排序函数排序函数qsort在在里定义里定义。vo
44、id qsort(void*base,size_t n,size_t size,int(*cmp)(const void*,const void*);void指针是被排序数据组起始位置(指针是被排序数据组起始位置(qsort不知道数不知道数组元素类型)。组元素类型)。size_t是无符号整数类型,是无符号整数类型,n表示被排表示被排序的数据项数,序的数据项数,size表示被排序的数组元素的大小。表示被排序的数组元素的大小。用用qsort时必须通知所需排列方式(比较元素大小的准时必须通知所需排列方式(比较元素大小的准则),它把则),它把“较小较小”元素排在前面。元素排在前面。cmp是比较准则,是
45、比较准则,必须符合必须符合qsort对比较函数的类型要求和功能要求。对比较函数的类型要求和功能要求。要对数组要对数组students里的里的snum个学生记录排序,调用个学生记录排序,调用形式(形式(scrcmp是自定义的比较函数,下面讨论是自定义的比较函数,下面讨论):):qsort(students,snum,sizeof(StuRec),scrcmp);比较函数的类型在比较函数的类型在qsort原型的参数中描述得很清楚。原型的参数中描述得很清楚。将比较学生分数函数取名将比较学生分数函数取名scrcmp,其原型应是:,其原型应是:int scrcmp(const void*vp1,cons
46、t void*vp2);qsort对被排序元素调用比较函数。为正确写出比较函对被排序元素调用比较函数。为正确写出比较函数,需弄清数,需弄清qsort:1)通过参数传给函数什么;)通过参数传给函数什么;2)要要求函数以什么方式表示元素比较结果。求函数以什么方式表示元素比较结果。qsort传给函数的是指向被排序元素的指针。传给函数的是指向被排序元素的指针。qsort不不知道被排序元素的类型,只能用知道被排序元素的类型,只能用(void*)。我们应在函。我们应在函数里将指针转换为实际元素类型的指针后使用。数里将指针转换为实际元素类型的指针后使用。qsort要求比较函数在第一个元素要求比较函数在第一个
47、元素“大于大于”第二个元素第二个元素时返回正值,两元素相等时返回时返回正值,两元素相等时返回0,第一个元素较小时,第一个元素较小时返回负值。返回负值。“大小大小”关系应根据排序需要确定。关系应根据排序需要确定。为清晰,下面定义先转换到为清晰,下面定义先转换到StuRec指针并取出指针并取出score成员值后再比较大小:成员值后再比较大小:int scrcmp(const void*vp1,const void*vp2)double s1=(StuRec*)vp1)-score,s2=(StuRec*)vp2)-score;return s1 s2?1:s1=s2?0:-1;qsort“通用通用
48、”,按排序准则工作。对另一准则,按排序准则工作。对另一准则,qsort就能对同一组数据做另一种排序。例如希望将学就能对同一组数据做另一种排序。例如希望将学生记录按生记录按5分一段分段排列,那么可以采用下面比较函分一段分段排列,那么可以采用下面比较函数:数:int scr5cmp(const void*vp1,const void*vp2)int n1=(StuRec*)vp1)-score,n2=(StuRec*)vp2)-score;return n1/5-n2/5;要按姓名排序,可用标准要按姓名排序,可用标准strcmp函数。函数。qsort提供的提供的是指向学生记录的是指向学生记录的vo
49、id指针。这里要比较是其中的名指针。这里要比较是其中的名字字符串,用字字符串,用char*指针。可定义下面转接函数:指针。可定义下面转接函数:int scmp(const void*vp1,const void*vp2)char*p1=(StuRec*)vp1)-name,*p2=(StuRec*)vp2)-name;return strcmp(p1,p2);函数可简写为:函数可简写为:int scmp(const void*vp1,const void*vp2)return strcmp(StuRec*)vp1)-name,(StuRec*)vp1)-name);程序其他部分的修改完善请自己
50、做。程序其他部分的修改完善请自己做。一组数据可能有许多不同的排序要求。一组数据可能有许多不同的排序要求。例如一个超级市场的商品食品记录,可能需要将这些记例如一个超级市场的商品食品记录,可能需要将这些记录按商品名称排序,按食品出厂日期排序,按食品的过录按商品名称排序,按食品出厂日期排序,按食品的过期日期排序,按商品的单价排序,按一段时间的销售额期日期排序,按商品的单价排序,按一段时间的销售额排序,按出品厂商名称排序等。排序,按出品厂商名称排序等。只要提供适当比较函数,这些都可借助于只要提供适当比较函数,这些都可借助于qsort完成。完成。还可以用还可以用qsort对数组中的一段元素排序。对数组中