




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第7章结构体
7.1结构体类型与结构体变量
在实际编程中除了常常要处理相同类型的数据外,还经常要处理不同类型的相关数据。例如,一个学生的信息可能包括学号、姓名、性别和考试成绩四项信息。学号可定义为整型,姓名可定义为字符串类型,性别可定义为字符型,考试成绩可定义为实型数组。这些数据虽然类型不同,但是它们是相关的,共同表示某个学生的信息。显然,这些数据既不适合用基本类型处理也不适合用数组处理,这种数据需要用可包含不同类型成员的构造类型处理,为此,C语言提供了满足这种需求的数据类型——结构体类型。7.1.1结构体类型的定义
结构体类型中可以包含若干相同或不同类型的成员,这些成员代表相关的信息。例如,日期结构体类型可定义为:structdate{intyear;intmonth;intday;};学生结构体类型可如下定义:structstudent{intnumber;/*学号*/charname[8];/*姓名*/charsex;/*性别*/floatscore[4];/*成绩*/};定义的格式为:struct结构体名{类型名成员名1;类型名成员名2;
……
类型名成员名n;
};说明:①struct是结构体类型的标识,是关键字。struct和后面的结构体名共同构成结构体类型名。结构体名应符合标识符的命名规则。②结构体所有成员的定义用花括弧括起来。结构体成员的定义方式和变量的定义方式一样,成员类型可以是基本类型的,也可以是构造类型的。各成员之间用分号分隔。③结构体内的各个成员名不能重名,但可以与结构体外其他标识符同名。7.1.2结构体变量的定义结构体变量的定义可用三种方式:①用已经定义的结构体类型定义结构体变量。例如,用前面已经定义的structstudent定义变量stu:
structstudentstu;②定义结构体类型的同时定义结构体变量。例如,定义结构类型structstudent的同时定义变量stu1,stu2:structstudent{intnumber;/*学号*/charname[8];/*姓名*/charsex;/*性别*/floatscore[4];/*成绩*/}stu1,stu2;③定义无名结构体类型的同时定义结构体变量。因为这种定义形式省略了结构体名,只有不再需要定义此种类型变量的情况才可采用这种方式。例如,定义变量stu:struct{intnumber;/*学号*/charname[8];/*姓名*/charsex;/*性别*/floatscore[4];/*成绩*/}stu;结构体变量各个成员按照定义的顺序依次占用连续的空间。需要注意,结构体人员也可以是结构体变量,如:structdate{intmonth;intday;intyear;};struct{longintnum;charname[20];charsex;intage;
structdate
birthday;floatscore;charaddr[20];}student1,student2;7.1.3结构体变量的指针可以定义指针变量指向结构体类型变量。例如,语句 structstudent*p=&stu;定义了指向结构体的指针变量p,并且使其指向结构体变量stu。stu的存储结构和p指针的示意图如图7.1所示。图7.1结构体变量的存储结构及其指针例如,定义变量stu并初始化。如:structstudent{longintnum;charname[20];charsex;charaddr[20];}stu={89031,“lilin”,’M’,“123BeijingRoad”};7.1.4结构体变量的初始化结构体变量同样既可以在定义之后初始化,也可以在定义的同时进行初始化。若在定义之后进行初始化操作,只能对每个成员单独进行赋值。若在定义变量的同时进行初始化,则用一对花括弧括起各个成员值的列表并用赋值号和变量连接,成员值之间逗号分隔,具体格式为:
结构体类型名结构体变量={成员值列表};7.1.5结构体变量的引用对于结构体变量,只能引用结构体变量的成员,不能引用整个结构体变量。引用结构体变量成员的形式为:(1)通过结构体变量名引用:
结构体变量名.成员名其中“.”称为成员运算符。(2)通过指向结构体变量的指针引用:
(*指针变量名).成员名或指针变量名
成员名这两种表示形式完全等价。例如,语句“structstudent*p=&stu;”之后,变量stu的成员sex有以下三种引用形式:①stu.sex②(*p).sex③p
sex注意:如果结构体的成员仍然是构造类型,则需要逐级引用,直至最低级的成员,即只能对结构体变量最低级的成员进行存取和运算。例如,变量stu的成员score必须引用其最低级成员,如stu.score[0](*p).score[2]p
score[1]。结构体成员的运算规则与同类型的变量的运算规则相同。【例7.1】输入学生的各项信息,计算总分和平均成绩后输出。#include"stdio.h"structdate{intyear;intmonth;intday;};/*定义结构体类型structdate*/structstudent{intnumber;/*学号*/charname[10];/*姓名*/structdatebirthday;/*生日,structdate类型*/floatscore[4];/*四门课成绩*/floattotal;/*总分*/floatave;/*平均成绩*/};/*定义结构体类型structstudent*/main(){structstudentstu,*p;intk;printf("pleaseenternumber&name:");scanf("%d%s",&stu.number,);printf("pleaseenterbirthday(year,month,day):");scanf("%d%d%d",&stu.birthday.year,&stu.birthday.month, &stu.birthday.day);printf("pleaseenterthescore(4):");for(stu.total=0,k=0;k<4;k++){scanf("%f",&stu.score[k]);stu.total+=stu.score[k];}/*计算总成绩*/stu.ave=stu.total/4;/*计算平均成绩*/p=&stu;printf("number:%d\n",p
number);printf("name:%s\n",p
name);printf("birthday:%d,%d,%d\n",(*p).birthday.year,(*p).birthday.month, (*p).birthday.day);printf("score:");for(k=0;k<4;k++)printf("%6.2f",p
score[k]);printf("\ntotal:%6.2f\n",stu.total);printf("average:%6.2f\n",stu.ave);}7.2结构体数组7.2.1结构体数组的定义和初始化若数组元素的类型为结构体类型,则称该数组为结构体数组。定义结构体数组的同时也可对数组进行初始化,例如,structstudent{intnumber;/*学号*/charname[10];/*姓名*/floatscore[4];/*四门课程成绩*/floattotal;/*总分*/floatave;/*平均成绩*/}stu[3]={{461,"Liu",{80,78,67,80},0,0},{032,"Lin",{98,78,86,90},0,0},{103,"Chen",{79,89,68,80},0,0}};结构体数组stu的逻辑示意图如图7.2。7.2.2结构体数组的引用
结构体数组元素也是通过数组名和下标来引用,但要注意只能对最低级的成员进行存取和运算。引用的一般形式为:数组名[下标].成员名例如,stu[1].number、stu[0].score[2]、stu[2].ave等合法。
通过指针引用结构体数组元素的形式和通过指针引用结构变量形式一样,为:(*指针变量名).成员名或指针变量名
成员名例如,语句“p=&stu[1];”之后,(*p).number(第1个学生的学号)(p-1)
score[2](第0个学生的第2门课的成绩)p
ave;(第1个学生的平均成绩)是合法的引用形式。【例7.2】计算某班期末考试中所有学生的总分和平均成绩。#defineN5#include"stdio.h"structstudent{intnumber;/*学号*/charname[10];/*姓名*/intscore[4];/*四门课程成绩*/inttotal;/*总分*/floatave;/*平均成绩*/};main(){structstudentstu[N],*p;inti,k;for(i=0,p=stu;p<stu
+N;p++,i++)/*输入学生的信息*/{printf("the%dstudent\n",i);printf("number&name:");scanf("%d%s",&p->number,p->name);printf("score(4):");for(p->total=0,k=0;k<4;k++){scanf("%d",&p->score[k]);p->total+=p->score[k];/*计算总成绩*/}p->ave=p->total/4;/*计算平均成绩*/}for(i=0;i<N;i++)/*输出*/{printf("number:%6d\n",stu[i].number);printf("name:%s\n",stu[i].name);printf("score:");for(k=0;k<4;k++)printf("%4d",stu[i].score[k]);printf("\ntotal=%4daverage=%10.2f\n",stu[i].total,stu[i].ave);}}7.3结构体和函数7.3.1结构体指针变量作为函数参数
C语言允许函数之间传递结构体变量,但是形参结构体变量占用内存空间、接收实参数据带来空间和时间的巨大开销。因此语法上虽然允许,但一般很少采用这种方式,而采用结构体指针作为函数参数。参数传递时只需传递一个地址,空间和时间的代价都很小。而且,可以通过指针对实参结构体变量的空间操作,从而改变实参的值。【例7.3】重新编写例7.2。#defineN3#include"stdio.h"structstudent{intnumber;/*学号*/charname[10];/*姓名*/floatscore[4];/*四门课程成绩*/floattotal;/*总分*/floatave;/*平均成绩*/};voidinput(structstudent*stu)/*输入学生信息*/{intk;printf("number&name:");scanf("%d%s",&stu->number,stu->name);printf("score(4):");for(k=0;k<4;k++)scanf("%f",&stu->score[k]);}
voidtotal_average(structstudent*stu)/*计算总成绩和平均值*/{intk;stu->total=0;for(k=0;k<4;k++)stu->total+=stu->score[k];stu->ave=stu->total/4;}voidoutput(structstudent*stu)/*输出学生信息*/{intk;printf("number:%6d\n",stu->number);printf("name:%s\n",stu->name);printf("score:");for(k=0;k<4;k++)printf("%6.2f",stu->score[k]);printf("\ntotal=%10.2faverage=%10.2f\n",stu->total,stu->ave);}main(){structstudentstu[N],*p;inti;for(i=0;i<N;i++)/*输入学生的信息*/{printf("the%dstudent\n",i);input(&stu[i]);total_average(&stu[i]);/*计算平均成绩*/}printf("\n");for(i=0;i<N;i++)output(&stu[i]);/*输出*/}例7.3中各个函数的参数都是基类型为structstudent的指针类型,实参是结构体变量stu[j]的地址。发生函数调用时,将结构体变量的地址赋值给形参指针stu,这样形参指针就指向了结构体变量stu[j]。因此在input、total_average函数中对结构体变量的操作结果都在实参指向的结构体变量上得到体现。7.3.2结构体数组作函数参数向函数传递结构体数组与传递其它数组一样,实质上也是传递数组的首地址。形参数组与实参数组使用共同的内存单元。形参、实参是同类型的结构体数组名或结构体指针。【例7.4】重新编写例7.2。#defineN5#include"stdio.h"structstudent{intnumber;/*学号*/charname[10];/*姓名*/floatscore[4];/*四门课程成绩*/floattotal;/*总分*/floatave;/*平均成绩*/};voidinput(structstudentstu[],intn)/*输入学生的信息*/{inti,k;for(i=0;i<n;i++){printf("the%dstudent\n",i);printf("number&name:");scanf("%d%s",&stu[i].number,stu[i].name);printf("score(4):");for(k=0;k<4;k++)scanf("%f",&stu[i].score[k]);}}voidtotal_average(structstudent*stu,intn)/*计算总成绩和平均值*/{intk;structstudent*p;p=stu+n;for(;stu<p;stu++){stu->total=0;for(k=0;k<4;k++)stu->total+=stu->score[k];stu->ave=stu->total/4;}}voidoutput(structstudent*stu,intn)/*输出处理后的学生信息*/{intk,i;for(i=0;i<n;i++){printf("number:%6d\n",(stu+i)->number);printf("name:%s\n",(stu+i)->name);printf("score:");for(k=0;k<4;k++)printf("%6.2f",(stu+i)->score[k]);printf("\ntotal=%10.2faverage=%10.2f\n",(stu+i)->total,(stu+i)->ave);}}main(){structstudentstu[N];structstudent*q=stu;
floatarg,*point2=&arg;
/*告诉TC需要做浮点数输入转 换,以便TC就会把浮点转换格式安装 到可执行程序里*/input(q,N);total_average(stu,N);output(stu,N);}7.4链表链表是一种常见的重要的数据结构,可以根据需要动态地分配存储单元,因此不会浪费内存空间。●头指针(head)变量的地址指向链表的第一个元素(称 为“结点”)。●结点两个部分:一为用户的实际数据,二为下一个结点的 地址。●最后一个结点称为“表尾”,地址存放“NULL”,表示 地址 为空,即不再指向任何结点,因此链表到此结束。●链表的各个结点在内存中可以不是连续存放的。利用结构体变量来表示结点是最合适不过的。对于上图的链表结点,显然可以用以下结构体表示:
structstudent {intnum; floatscore; structstudent*next;};7.4.1静态链表的建立与输出【例7.5】建立图7.3所示的简单链表,并输出各结点中的数据。#defineNULL0#include<stdio.h>structstudent /*定义结点的结构体类型*/{longnum;/*学号一般很长,“数值”很大,所以用 长整形表示*/floatscore;structstudent*next;};main(){structstudenta,b,c,*head,*p;a.num=80101;a.score=91.5;/*对结点的num和score成员赋值*/b.num=80103;b.score=89.5;c.num=80107;c.score=76;head=&a;/*将结点a的起始地址赋给头指针,使头指针指向结点a*/a.next=&b; /*将结点b的起始地址赋给a结点的next成员*/b.next=&c; /*将结点c的起始地址赋给b结点的next成员*/ c.next=NULL; /*c结点的next成员存放空地址,c结点为表尾*/p=head;/*使p指针指向结点a*/do{printf(“%ld%5.1f\n”,p->num,p->score);/*输出p指向的结点的数据*/p=p->next; /*使p指向下一结点*/}while(p!=NULL); /*当p指针的值非空时继续循环*/}程序运行结果为:本程序中,所有结点都是在程序中定义的,不是动态申请空间建立的,不能用完后自动释放,这种链表称为静态链表。
7.4.2处理动态链表需要的函数动态链表的结点是动态分配的,即在需要的时候才开辟某个结点的存储单元。为了动态地开辟和释放存储单元,C语言编译系统提供了以下函数。1.malloc函数函数原形:void*malloc(unsignedintsize);
作用:在内存的动态存区分配一个长度为size个字节的连续存区,函数的返回值是一个指向该存区首址的指针(其类型为void型)。如果由于内存不足等原因使该函数未能成功执行,则返回空指针NULL。
2.calloc函数函数原形:void*calloc(unsignedn,unsignedsize);作用:在内存的动态存区在分配n个长度为size个字节的连续存区,函数的返回值是一个指向该存区首址的指针(其类型为void型)。如果由于内存不足等原因使该函数未能成功执行,则返回空指针NULL。使用calloc函数能够为一维数组开辟动态存储空间,n为数组元素的个数,每个元素的长度为size个字节。free函数函数原形:voidfree(void*p);
作用:释放p指向的内存区,使这部分内存区域能够 被其它变量使用,p为最近一次malloc/calloc的 返回值,即最近一次分配的空间。free函数没有返 回值。7.4.3建立动态链表所谓建立动态链表是指在程序执行过程中逐个建立链表的各个结点,即●逐个开辟结点的内存空间●再输入该结点的数据●然后建立起前后结点之间的相连关系。【例7.6】用建立单向动态链表的方法建立图7.3所示的链表。 为了建立一个单向动态链表,需要设置3个指针变量head、p1和p2,它们都指向structstudent类型数据。P1指向新开的结点,P2指向链表中最后一个结点,先使head的值为NULL,即不指向任何结点,链表为空。
图7.4建立第一个结点●先用malloc函数开辟第一个结点,即在内存的动态存区分配一个一定长度的连续存区,并且使p1指向它。●由于当前链表为空,所以p2也指向刚分配的连续存区。●当输入的学号不为0时,就开始为这个学生建立结点。●从键盘上读入第一个学生的数据给p1指向的结点。●在输入第一个学生的数据之前,链表中没有结点,所以头指针head为NULL。●当n=1时,表示是第一个结点,所以把这个结点的指针p1的值赋给head,head就指向了链表的头部。●输入的学号为0表示学生数据已经读完,链表建成,不再继续建立新结点的循环。●第1个结点建立以后,为了建立下一个结点,再用malloc函数开辟一个结点,并且用p1指向这个新结点,如图7.5(a)所示;图7.8建立第2个结点●接着读入第二个学生的数据,由于读入的学号非0,所以第二次进入建立结点的循环,由于结点数n不为1,所以将p1的值赋给p2->next,使第一个结点连到了第二个结点,如图7.7(b)所示。●然后,将p1的值赋给p2,使第二个结点的p1、p2两个指针又回到图7.7第一个结点刚建立的状态,准备建立下一个结点。●如此周而复始,建立更多节点。
图7.9建立第3个结点图7.7建立第4个结点
当读入的学号为0时,表示学生数据已经输入完毕,所以不再进入建立新结点的while循环,而置p2->next为NULL,表示刚才建立的结点是尾结点,此时,虽然p1指向新开辟的结点,但是这个“新结点”并不包含在链表之内,从链表中不可能找到这个结点。建立动态链表函数如下:#defineNULL0#defineLENsizeof(structstudent)/*sizeof是求字节运算符函数,所以本行定义的LEN代表structstudent类型数据的长度*/structstudent{longnum;floatscore;structstudent*next;};intn;/*全局变量,表示结点个数*/structstudent*creat(void) /*定义指针函数,建立单向动态链表,函数返回一个指向链表头的指针。其它函数中调用时只要写creat();即可*/{structstudent*head,*p1,*p2;/*设置3个指针变量head、p1和p2*/n=0; /*n是结点计数器*/p1=p2=(structstudent*)malloc(LEN);/*在动态存区分配长度为 LEN的存区,并返回首地址,指向structstudent类型数据的指针*/head=NULL;scanf(“%ld,%f”,&p1->num,*p1->score);while(p1->num!=0)/*当学号不为0时,进入建立结点循环*/{n=n+1;if(n==1)head=p1;/*把第一个结点的首地址赋给头指针*/elsep2->next=p1;/*使当前结点的p2->next指向下一个结点*/p2=p1; /*使新开辟结点的p1、p2回到结点刚建立的状态*/p1=(structstudent*)malloc(LEN);/*结点建成后,开辟新的结点*/scanf(“%ld,,%f”,&p1->num,&p1->score);/*读入下一学生*/}p2->next=NULL;/*置p2->next为NULL,表示刚建结点是尾结点*/return(head);/*本函数条用结束后,给主调函数返回链表的头指针*/}
7.4.4对链表的删除
所谓删除动态链表的某个结点,其实并不是真的从内存中把这个结点抹掉,只是撤销原来的连接关系,把这个结点从链表中分离出去。
如果删除制定学号(num)的节点,就从头结点开始,逐个比较每个结点的num是否等于该指定的学号,如果相等就将该结点删除,否则就比较下一个结点,直到检查到链表的表尾为止。●先设置2个指针p1和p2,使p1指向第1个结点。●首先检查第一个结点●如果要删除的结点不是第1个结点,首先将p1的值赋给p2,使p2指向刚才检查过的结点,然后使p1指向下一个结点(即p1=p1->next),如图7.11(b)●如此一次一次地使指针后移,直到查到要删除的结点或者一直查到表尾都没有找到要删除的结点为止。
查找需要删除的结点如果找到了要删除的结点,而且是第一个结点,则将p1->next赋给head,如图7.11(c),于是第1个结点就被删除了(实际是是和链表脱离了,不再是链表的一部分)。删除找到的结点如果要删除的结点不是第一个结点,譬如查到第2个结点时(所以现在p1指向第2个结点),发现它是要删除的结点,则将p1->next赋给则将p2->next,如图7.11(d),于是第1个结点不再指向第2个结点,而是指向第3个结点了,因此把第2个结点与链表脱离了。找不到要删除的结点如果直到查到表尾都没有找到要删除的结点,则输出“找不到要删除的结点”。空表如果所查照的链表一个结点都没有,即头指针位空,完全是个空表,则输出“空表”信息。删除结点的算法框图删除结点的函数如下:structstudent*del(structstudent*head,longnum)/*head 是头指针,num是要删除的结点学号*/{structstudent*p1,*p2;if(head==NULL){printf(“\nemptylist!\n”);gotoend;} /*输出空表信息*/p1=head;while(num!=p1->num&&p1->next!=NULL){p2=p1;p1=p1->next;} /*p1向后移一个结点*/if(num==p1->num) /*找到要删除结点*/{if{p1==head)head=p1->next;/*删除了第一结点*/elsep2->next=p1->next;/*不是第1结点*/printf(“%ldhasbeendeleted\n”,num);n=n-1;}elseprintf(“%ldhasnotbeenfound!\n”,num);/*显示找 不到信息*/end:return(head);}7.4.5对链表的插入操作
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 合伙销售茶叶合同范本
- 农业维护协议合同范本
- 办公耗材批发合同范本
- 医院保洁耗材合同范本
- 合同范本由谁出
- 售卖蛋糕合同范本
- 受托付款合同范例
- 员工社保合同范本
- 合同范本个可以获取
- 厨师劳务派遣服务合同范本
- 2025年榆林市公共交通总公司招聘(57人)笔试参考题库附带答案详解
- 医院培训课件:《多发性骨髓瘤》
- 【新】部编人教版小学4四年级《道德与法治》下册全册教案
- 2025年湖南省长沙市单招职业倾向性测试题库及参考答案
- 《产业转移》课件:机遇与挑战
- 十八项核心制度培训课件
- 2024年远程教育行业市场运营现状及行业发展趋势报告
- 2025年2月上海市高三联考高考调研英语试题(答案详解)
- 三好学生竞选12
- 2024-2025学年六年级上学期数学第三单元3.1-搭积木比赛(教案)
- DeepSeek从入门到精通
评论
0/150
提交评论