c语言第十一章结构体与共同体精品课件.ppt
结构体 与 共用体 结构体 共用体 枚 举,第十一章 结构体与共用体,本章要求:,、掌握结构体的概念,会使用结构体指针,结构体数组。、链表的概念,熟练掌握用指针处理链表。、了解共用体的概念。、了解枚举类型。、会用typedef定义新数据类型。,11,从基本数据类型到抽象数据类型,二进制数在早期的机器指令及汇编语言中,数据对象均用二进制数表示,没有类型的概念,基本数据类型在高级语言中引入了基本数据类型:整型、实型、字符型等基本数据类型不能方便的解决所有问题,,用户自己构造数据类型-复合数据类型表示复杂的数据对象,数组、指针也可算作此类,然而最典型的代表就是“结构体”,,抽象数据类型(Abstract Data Type,简称ADT)在复合数据类型基础上增加了对数据的操作类跨时代的进步,思考一个问题,在程序里表示一个人(姓名、年龄、性别、),怎么表示?想表示多个人呢?如何用计算机程序实现下述表格的管理?,表11-1 某学校学生成绩管理表,数组的解决方法,int stuId30; /* 最多可以管理30个学生, 每个 学生的学号用数组的下标表示*/charstuName3010;charstuSex302;int timeOfEnter30; /*入学时间用int表示*/int scoreCom30; /*计算机原理课的成绩*/int scoreEng30; /*英语课的成绩*/int scoreMath30; /*数学课的成绩*/int scoreMus30; /*物理课的成绩*/,数组的解决方法,数据的内存管理方式,分配内存不集中,寻址效率不高 结构显得比较零散,不容易管理,希望的内存分配图,结构体的解决方法,struct STUDENT int studID; /*每个学生的序号*/ char studName10; /*每个学生的姓名*/ char studSex4; /*每个学生的性别*/ int timeOfEnter; /*每个学生的入学时间*/ int scoreCom; /*每个学生的计算机原理成绩*/ int scoreEng; /*每个学生的英语成绩*/ int scoreMat; /*每个学生的数学成绩*/ int scoreMus; /*每个学生的物理成绩*/ ;struct STUDENT 是一个类型struct STUDENT students4;students0.studentIDstudents0.scoreComputer它们都是变量,一般称为结构的成员变量,一、定义一个结构的一般形式,在实际问题中,一组数据往往具有不同的数据类型。例如,学生登记表中,姓名应为学号可为年龄应为性别应为成绩可为另一种构造数据类型“结构(structure)”或叫“结构体” 它相当于其它高级语言中的记录。“结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。,显然不能用一个数组来存放这一组数据。因为数组中各元素的类型和长度都必须一致,以便于编译系统处理。为了解决这个问题,第十一章结构体与共用体,字符型;,整型或字符型;,整型或实型;,整型;,字符型;,在这个结构定义中,结构名为stu,该结构由4个成员组成。第一个成员为num,整型变量;第二个成员为name,字符数组;第三个成员为sex,字符变量;第四个成员为score,实型变量。应注意在括号后的分号是不可少的。结构定义之后,即可进行变量说明。凡说明为结构stu的变量都由上述4个成员组成。由此可见 结构是一种复杂的数据类型,是数目固定,类型不同的若干有序变量的集合。,定义一个结构的一般形式为:struct 结构名 成员表列;成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为: 类型说明符 成员名;,struct stu int num; char name20; char sex; float score; ;,例如:,复习:,二、结构类型变量的说明,说明结构变量有以下三种方法。以上面定义的stu为例来加以说明。、先定义结构,再说明结构变量如: 说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型。,struct stu int num; char name20; char sex; float score; ; struct stu boy1, boy2;,例如:#define STU struct stuSTU int num; char name20; char sex; float score; ;STU boy1, boy2;,2、在定义结构类型的同时说明结构变量struct stu int num; char name20; char sex; float score; boy1, boy2;这种形式的说明的一般形式为: struct 结构名 成员表列 变量名表列;,结构类型变量,例如:,还可以定义 struct stu boy3, boy4;,、直接说明结构变量例如:struct int num; char name20; char sex; float score;boy1, boy2;这种形式的说明的一般形式为:struct 成员表列变量名表列;,结构类型变量,第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量。,说明了boy1, boy2变量为stu类型后,即可向这两个变量中的各个成员赋值。在上述stu结构定义中,所有的成员都是基本数据类型或数组类型。,三种方法中说明的boy1, boy2变量都具有下图所示的结构:,成员也可以又是一个结构,即构成了嵌套的结构。例如,下图给出了另一个数据结构。,struct int num; char name20; char sex; struct date birthday; float score; boy1, boy2;,按图可给出以下结构定义:,struct date int month; int day; int year;,首先定义一个结构date,由month(月)、day(日)、year(年) 三个成员组成。,成员birthday被说明为data结构类型,成员名可与程序中其它变量同名,互不干扰。例:,struct yeardate int num; char name20; char sex; struct date birthday; float score; boy1, boy2; int score ;,三、结构变量成员的表示方法,在程序中使用结构变量时, 往往不把它作为一个整体来使用。在ANSI C中除了允许具有相同类型的结构变量相互赋值以外,一般对结构变量的使用,包括赋值、输入、输出、运算等都是通过结构变量的成员来实现的。表示结构变量成员的一般形式是: 结构变量名.成员名如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。 即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。,例如: boy1.num 即第一个人的学号 boy2.sex 即第二个人的性别,例如: boy1.birthday.month,四、结构变量的赋值,结构变量的赋值就是给各成员赋值。可用输入语句或赋值语句来完成。【例11.1】给结构变量赋值并输出其值。,main() struct stu int num; char *name; char sex; float score; boy1, boy2; boy1.num=102; boy1.name="Zhang ping" printf( “输入性别and分数n “ ); scanf("%c %f", ,用赋值语句给num和name两个成员赋值name是一个字符串指针变量用scanf函数动态地输入sex和score成员值然后把boy1的所有成员的值整体赋予boy2最后分别输出boy2的各个成员值本例表示了结构变量的赋值、输入和输出的方法,五、结构变量的初始化,和其他类型变量一样,结构变量可以在定义时进行初始化赋值。【例11.2】对结构变量初始化。main( ) struct stu /*定义结构*/ int num; char *name; char sex; float score; boy2, boy1= 102, "Zhang ping", 'M', 78.5 ; boy2=boy1; printf("Number=%dnName=%sn", boy2.num, boy2.name); printf("Sex=%cnScore=%fn", boy2.sex, boy2.score); ,六、结构数组的定义,数组的元素也可以是结构类型的。因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。 在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。方法和结构变量相似,只需说明它为数组类型即可。,struct stu int num; char *name; char sex; float score; boy5;,定义了一个结构数组 boy , 共有5个元素: boy0boy4每个数组元素都具有 struct stu 的结构形式,例如:,struct stu int num; char *name; char sex; float score; boy5= 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; ,对结构数组可以作初始化赋值。,例如:,当对全部元素作初始化赋值时,也可不给出数组长度。,【例11.3】计算学生的平均成绩和不及格的人数。,struct stu int num; char *name; char sex; float score; boy5= 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+=boyi.score; if (boyi.score<60) c+=1; printf("s=%fn", s ); ave=s/5; printf(“平均分=%fn人数=%dn”, ave, c);,定义了一个外部结构数组boy,共5个元素,并作了初始化赋值。在main函数中用for语句逐个累加各元素的score 成员值存于s之中,如score的值小于60(不及格)即计数器C加1,循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。,【例11.4】建立一个简易的同学通讯录,main() struct mem manNUM; int i; for(i=0; i<NUM; i+) printf(“输入姓名:n"); gets(mani.name); printf(“输入电话号码:n"); gets(mani.phone); printf("nametttphonenn"); for(i=0; i<NUM; i+) printf("%st t%sn", man i .name, man i .phone ); ,#include"stdio.h"#define NUM 3struct mem char name20; char phone15;,定义了一个结构mem,它有两个成员name和phone用来表示姓名和电话号码。在主函数中定义man为具有mem 类型的结构数组。在for语句中,用gets函数分别输入各个元素中两个成员的值。然后又在for语句中用printf语句输出各元素中两个成员值。,七、结构指针变量的说明和使用,1、指向结构变量的指针,一个指针变量当用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向的结构变量的首地址。通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相同的。结构指针变量说明的一般形式为: struct 结构名 *结构指针变量名 例如,在前面的例题中定义了stu这个结构,如要说明一个指向stu的指针变量pstu,可写为: struct stu *pstu;,结构指针变量也必须要先赋值后才能使用。 赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。如果boy是被说明为stu类型的结构变量,则: pstu=&boy 正确 而: pstu=&stu 错误,结构名和结构变量是两个不同的概念,不能混淆。结构名只能表示一个结构形式,编译系统并不对它分配内存空间。只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。因此上面&stu这种写法是错误的,不可能去取一个结构名的首地址。有了结构指针变量,就能更方便地访问结构变量的各个成员。,其访问的一般形式为:结构变量. 成员名引入结构指针变量以后,则访问一般形式是: (*结构指针变量).成员名 或为: 结构指针变量->成员名例如: (*pstu).num 或者: pstu->num注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。去掉括号写作*pstu.num*(pstu.num),意义就完全不对了。,下面通过例子来说明结构指针变量的具体说明和使用方法。,struct stu int num; char *name; char sex; float score; boy1= 102, "Zhang ping", 'M', 78.5 , *pstu;,【例11.5】,main() pstu=,定义了一个结构stu,定义了stu类型结构变量boy1并作了初始化赋值,还定义了一个指向stu类型结构的指针变量pstu。在main函数中,pstu被赋予boy1的地址,因此pstu指向boy1。然后在printf语句内用三种形式输出boy1的各个成员值。从运行结果可以看出: 结构变量.成员名 boy1.num (*结构指针变量).成员名 (*pstu).num 结构指针变量->成员名 pstu->num这三种用于表示结构成员的形式是完全等效的。,2、指向结构数组的指针,指针变量可以指向一个结构数组,这时结构指针变量的值是整个结构数组的首地址。结构指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址。 设ps为指向结构数组的指针变量 则 ps也指向该结构数组的0号元素 ps+1指向1号元素 ps+i则指向i号元素 【例11.6】用指针变量输出结构数组。,ps,ps+i,boy,struct stu int num; char *name; char sex; float score;boy5= 101, "Zhou ping", 'M', 45, 102, "Zhang ping", 'M', 62.5, 103, "Liou fang", 'F', 92.5, 104, "Cheng ling", 'F', 87, 105, "Wang ming", 'M', 58, ;,main() struct stu *ps; printf(“学号:姓名:性别:分数n"); for( ps=boy; psnum, ps->name, ps->sex, ps->score);,在程序中,定义了stu 结构类型的外部数组 boy并作了初始化赋值。在main函数内定义ps为指向stu类型的指针。在循环语句for的表达式1中,ps 被赋予 boy 的首地址,然后循环5次,输出boy数组中各成员值。 注意的是,一个结构指针变量虽然可以用来访问结构变量或结构数组元素的成员,但是,不能使它指向一个成员。也就是说不允许取一个成员的地址来赋予它。 如: ps= 赋予0号元素首地址,3、结构指针变量作函数参数,在ANSI C 标准中允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传向形参的只是地址,从而减少了时间和空间的开销。,【例11.7】计算一组学生的平均成绩和不及格人数。 用结构指针变量作函数参数编程。,struct stu int num; char *name; char sex; float score; boy5= 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 *ps; void ave( struct stu *ps ); ps=boy; ave(ps);,void ave( struct stu *ps ) int c=0, i; float ave, s=0; for(i=0; iscore; if(ps->score<60) c+=1; printf("s=%fn", s); ave=s/5; printf(“平均分数=%fn人数=%dn", ave, c);,关键!,程序中定义了函数 ave,其形参为结构指针变量ps。boy被定义为外部结构数组,因此在整个源程序中有效。在main函数中定义说明了结构指针变量ps, 并把boy的首地址赋予它, 使ps指向boy数组。然后以ps作实参调用函数ave。在函数ave中完成计算平均成绩和统计不及格人数的工作并输出结果。 由于本程序全部采用指针变量作运算和处理, 故速度更快,程序效率更高。,main()struct STUDENT *pt ;float sum4 =0.0, average4 = 0.0;int i;char*name = "Computer","English", "Math","Music“ ;pt = stu;/* pt指向结构体数组的第一个元素 */for(pt=stu;ptComputer; /*计算第一门课的成绩总和*/sum1 = sum1 + pt->English; /*计算第二门课的成绩总和*/sum2 = sum2 + pt->Math; /*计算第三门课的成绩总和*/sum3 = sum3 + pt->Music; /*计算第四门课的成绩总和*/ for(i=0;i<4;i+) averagei = sumi/30; /*求每门功课的平均成绩 */ printf("%20s : %4.2fn" , namei, *(average+i); /*输出四门课的平均成绩 */ ,八、动态存储分配,在数组一章中,曾介绍过数组的长度是预先定义好的,在整个程序中固定不变。语言中不允许动态数组类型。例如: int n; scanf("%d", 在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题,用数组的办法很难解决。为了解决上述问题,语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。,用变量表示长度,想对数组的大小作动态说明,这是错误的。,常用的内存管理函数有以下四个:(1)分配内存空间函数malloc (见P387) 函数原形式: void *malloc( unsigned size) 功能:在内存的动态存储区中分配一块长度为"size“ 字节的连续区域。函数的返回值为该区域的首 地址。如内存不够, 返回0。 调用形式: (类型说明符*)malloc(size) “类型说明符”表示把该区域用于何种数据类型。 (类型说明符*)表示把返回值强制转换为该类型指针。 “size”是一个无符号数。 例如: pc=(char *)malloc(100);,表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。,说明:如果函数未能成功地执行(如自由内存不足等),则不能获 得需要的存储空间,将返回一个空指针NULL,表示分配 失败。格式 (类型说明符*)malloc(size)中的 (类型说明符*) 通常为void类型的指针,表示如需要引用此地址,可用类 型转换成需要类型的指针。应使用的头部文件有“malloc.h”,例: float *p; p=(float*) malloc(sizeof(float);,例: #include ”malloc.h” #include “string.h” main( ) char *p; unsigned size=20; p=(char*) malloc(size); strcpy( p, “Very good!”); printf(“%s”, p); free(p); ,由于计算机可供用动态分配的内存空间是有限的,内存动态分配失败将导致指针变量值为NULL,因而一般在调用函数时进行内存动态分配时须检查其返回值是否为NULL。,char *p;p=malloc(20);if(!p) printf(“out of memoryn”); exit(1); ,if(p=NULL),申请一个结构体的内存,通常:struct temp int data; char name10; char sex; float score; ; struct temp stu, *pt= ,而今:struct temp int data; char name10; char sex; float score; ; struct temp *pt;,(2)分配内存空间函数 calloc 函数原形式 :void * calloc(unsigned n, unsigned size) 调用形式:(类型说明符*)calloc(n, size) 功能:在内存动态存储区中分配n块长度为“size”字节 的连续区域。函数的返回值为该区域的首地址。 (类型说明符*)用于强制类型转换。 calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。 例如: ps=(struct stu*) calloc( 3, sizeof(struct stu); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配3块连续区域,强制转换为stu类型, 并把其首地址赋予指针变量ps。,(3)释放内存空间函数free 调用形式:free(void *ptr); 功能:释放ptr所指向的一块内存空间,ptr是一个任意类 型的指针变量, 它指向被释放区域的首地址。被释 放区应是由malloc或calloc函数所分配的区域。,【例11.8】分配一块区域,输入一个学生数据。main() struct stu int num; char *name; char sex; float score; *ps;,ps=(struct stu*)malloc(sizeof(struct stu); ps->num=102; ps->name="Zhang ping" ps->sex='M' ps->score=62.5; printf(“学号=%dn姓名=%sn", ps->num, ps->name); printf(“性别=%cn分数=%fn”, ps->sex, ps->score); free(ps);,#include " malloc.h"main( ) float *ps, *p, i=1.0 ps=( float*)malloc( 5*sizeof (float) ); p=ps; for(j=0; j<5; j+) *ps+=i+; ps=p; for(j=0; j<5; j+) printf( "%fn", *ps+ ); free(ps);,运行结果:1.00000 2.00000 3.00000 4.00000 5.00000,*(ps+),问题: 是否定义了数组来存放数据?,定义了结构stu,定义了stu类型指针变量ps。然后分配一块stu大内存区,并把首地址赋予ps,使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值,并用printf输出各成员值。最后用free函数释放ps指向的内存空间。free( )函数无返回值。整个程序包含了三个步骤,申请内存空间使用内存空间释放内存空间 实现存储空间的动态分配,归纳:,struct stu int num; char *name; char sex; float score; ; struct stu stuend, *pt= 结构变量. 成员名 (*结构指针变量). 成员名 结构指针变量->成员名,#define NULL 0 #define TYPE struct stu #define LEN sizeof (struct stu),TYPE boy,*p; p=(TYPE*) malloc(LEN);,stuend.sex=f , pt->sex=f (*pt). Sex= f,struct类型的特点,一个struct的类型可以定义该类型的变量、数组、指针可以做函数的参数类型和返回值类型它的成员可以是任意类型基本类型、数组、指针、结构体、共用体struct类型的变量两个结构体变量之间可以相互赋值所以做为函数的参数时,是传值调用可以取地址&不可能直接参与算术和比较运算面向对象和数据库是struct的思想的发展,思考,下面的结构是什么意思? struct temp int data; struct temp pt;下面的结构是什么意思呢? struct temp int data; struct temp *pt;,用于存放地址的成员,常把它称为指针域。,九、链表的概念,采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据,我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间,也就是说要建立多少个结点。当然用结构数组也可以完成上述工作,但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。,用动态存储的方法可以很好地解决这些问题。有一个学生就分配一个结点, 无须预先确定学生的准确人数, 某学生退学, 可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。另一方面, 用数组的方法必须占用一块连续的内存区域。而使用动态分配时, 每个结点之间可以是不连续的(结点内是连续的)。结点之间的联系可以用指针实现。即 在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。,可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接, 其指针域可赋为0。这样一种连接方式, 在数据结构中称为“链表”。,