720 likes | 937 Views
第 8 章 结构体、枚举和共用体类型. 10.1 结构体类型 10.2 结构体和函数 10.3 动态数据结构 —— 链表 10.4 枚举类型 10.5 共用体类型 10.6 用户自定义类型. 10.1 结构体类型. 结构体类型的定义 结构体类型中可以根据需要包含若干相同或不同类型的成员,这些成员所代表的信息必须是相关的信息。 结构体类型定义的格式为: struct 结构体名 { 类型名 成员名 1 ; 类型名 成员名 2 ; …… 类型名 成员名 n ; }; 例如, struct date
E N D
第8章 结构体、枚举和共用体类型 10.1 结构体类型 10.2结构体和函数 10.3 动态数据结构——链表 10.4枚举类型 10.5共用体类型 10.6 用户自定义类型
10.1 结构体类型 • 结构体类型的定义 • 结构体类型中可以根据需要包含若干相同或不同类型的成员,这些成员所代表的信息必须是相关的信息。 • 结构体类型定义的格式为: struct 结构体名 {类型名 成员名1; 类型名 成员名2; …… 类型名 成员名n; }; 例如,struct date {int year; int month; int day; };
10.1 结构体类型 struct student {int number; /*学号*/ char name[8]; /*姓名*/ char sex; /*性别*/ float score[4]; /*成绩*/ }; • struct是结构体类型的标识,是关键字。struct和后面的结构体名共同构成结构体类型名。结构体名应符合标识符的命名规则。 • 结构体所有成员的定义用花括弧括起来。结构体成员的定义方式和变量的定义方式一样,成员类型可以是基本类型的,也可以是构造类型的。各成员之间用分号分隔。 • 结构体内的各个成员名不能重名,但成员名可以与结构体外其他标识符同名,并不产生冲突。
10.1 结构体类型 • 结构体变量的定义 • 用已经定义的结构体类型定义结构体变量。 struct student stu; • 定义结构体类型的同时定义结构体变量。stu1,stu2。 struct student {int number; /*学号*/ char name[8]; /*姓名*/ char sex; /*性别*/ float score[4]; /*成绩*/ } stu1,stu2;
10.1 结构体类型 • 定义无名结构体类型的同时定义结构体变量。这种定义形式省略了结构体名。不再需要定义此种类型变量的情况才可采用这种方式。 struct {int number; /*学号*/ char name[8]; /*姓名*/ char sex; /*性别*/ float score[4]; /*成绩*/ }stu • 结构体变量各个成员按照定义的顺序依次占用连续的空间。可以定义指针变量指向结构体类型变量。 结构体变量的地址虽与其第一个成员的地址的值是相同的,但它们的类型是不同的。它们之间不能直接相互赋值,但是可以先将其用强制类型转换,转换为同一类型指针后相互赋值。
10.1 结构体类型 • 结构体变量的初始化和引用 • 结构体变量的初始化 • 若在定义之后进行初始化操作,只能对每个成员单独进行赋值。 • 若在定义变量的同时进行初始化,则用一对花括弧括起各个成员值的列表并用赋值号和变量连接,成员值之间逗号分隔,具体格式为: 结构体类型名 结构体变量={成员值列表}; 例如, struct student stu={1001, "wang", ‘f’, 60.5,80,75,90}; • 结构体变量的引用 • 只能引用结构体变量的成员,不能引用整个结构体变量。结构体变量的成员引用形式为: 结构体变量名.成员名 • 其中“.”称为成员运算符。如果是通过指向结构体变量的指针引用结构体成员,形式为: (*指针变量名).成员名 或 指针变量名->成员名
10.1 结构体类型 • 如果结构体的成员仍然是构造类型,则需要逐级引用,直至最低级的成员,即只能对结构体变量最低级的成员进行存取和运算。 【例10.1】输入学生的各项信息,计算总分和平均成绩后输出。 #include "stdio.h" struct date {int year; int month; int day; }; /*定义结构体类型struct date*/ struct student {int number; /*学号*/ char name[10]; /*姓名*/ struct date birthday; /*生日, struct date类型*/
10.1 结构体类型 float score[4]; /*四门课成绩*/ float total; /*总分*/ float ave; /*平均成绩*/ }; /*定义结构体类型struct student*/ main( ) {struct student stu,*p; int k; printf("please enter number& name: "); scanf("%d %s",&stu.number ,stu.name); printf("please enter birthday(year,month,day):"); scanf("%d%d%d",&stu.birthday.year, &stu.birthday.month, &stu.birthday.day); printf("please enter the score(4):");
10.1 结构体类型 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);
10.1 结构体类型 printf("average: %6.2f \n ",stu.ave); } 某次程序运行结果为: please enter number& name: 1002 li↙ please enter birthday(year,month,day):1975 6 8↙ please enter the score(4):80 78.5 92 83.5↙ number :1002 name :li birthday :1975,6,8 score: 80.00 78.50 92.00 83.50 total:334.00 average: 83.50
10.1 结构体类型 • 结构体数组 • 结构体数组的定义、初始化 • 若数组元素的类型为结构体类型,数组为结构体数组。 • 定义结构体数组的同时也可对数组进行初始化操作。例如, struct student {int number; /*学号*/ char name[10]; /*姓名*/ float score[4]; /*四门课程成绩*/ float total; /*总分*/ float ave; /*平均成绩*/ }stu[3]={ {461,"liu",{80,78,67,80},0,0},{032,"geng",{98,78,86,90}, 0,0}, {103, "qian",{79,89,68,80},0,0} }; • 可以定义指向结构体数组元素的指针变量,然后通过指针对数组元素操作。
10.1 结构体类型 • 结构体数组的引用 • 结构体数组元素也是通过数组名和下标来引用,但要注意只能对最低级的成员进行存取和运算。引用的一般形式为: 数组名[下标].成员名 例如,stu[1].number 、stu[0].score[2]、 stu[2].ave • 通过指针引用结构体数组元素的形式和通过指针引用结构变量形式一样,为: (*指针变量名).成员名 或 指针变量名->成员名 例如,语句“p=&stu[1];”之后,(*p).number、(p-1)->score[2] 、p->ave;
10.1 结构体类型 【例10.2】计算某班期末考试中所有学生的总分和平均成绩。 #define N 50 #include "stdio.h" struct student {int number; /*学号*/ char name[10]; /*姓名*/ float score[4]; /*四门课程成绩*/ float total; /*总分*/ float ave; /*平均成绩*/ }; main( ) {struct student stu[N],*p; int i,k; for(i=0, p=stu;p<stu +N;p++,i++) /*输入学生的信息*/ { printf("the %d student\n ",i); printf("number& name: ");
10.1 结构体类型 scanf("%d %s",&p->number, p->name); printf("score(4): "); for(p->total=0,k=0;k<4;k++) { scanf("%f",&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("%6.2f" , stu[i].score[k]); printf("\ntotal=%10.2f average=%10.2f \n", stu[i].total, stu[i].ave); } }
10.2结构体和函数 • 结构体变量的成员作为函数参数 • 结构体变量的成员可作为函数的实际参数,但是不适合作函数的形式参数。 • 结构体变量的成员作函数实参的用法与普通变量作函数实参的用法相同,形参与实参之间仍然是“值传递”的方式。 【例10.3】重新编写例10.2。 #define N 50 #include "stdio.h" struct student {int number; /*学号*/ char name[10]; /*姓名*/ float score[4]; /*四门课程成绩*/ float total; /*总分*/ float ave; /*平均成绩*/ };
10.2结构体和函数 float total(float s[],int n) /*计算总成绩*/ {int k; float sum=0; for(k=0;k<n;k++) sum+=s[k]; return(sum); } float average( float x, int n ) {return(x/n) ; /*返回平均值*/ } main( ) {struct student stu[N]; int i,k; for(i=0;i<N;i++) /*输入学生的信息*/ { printf("the %d student\n ",i); printf("number& name: ");
10.2结构体和函数 scanf("%d %s",&stu[i].number, stu[i].name); printf("score(4): "); for(k=0;k<4;k++) scanf("%f",&stu[i].score[k]); stu[i]. total = total(stu[i].score,4); /*计算总成绩*/ stu[i].ave=average(stu[i].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("%6.2f" , stu[i].score[k]); printf("\ntotal=%10.2f average=%10.2f\n ", stu[i].total, stu[i].ave); } }
10.2结构体和函数 • 结构体指针变量作为函数参数 • 允许函数之间传递结构体变量,若形参是结构体变量,那么实参也应是同类型的结构体。被调用函数对形参结构体变量的修改不能传递给实参。形参结构体变量占用内存空间、接收实参数据带来空间和时间的巨大开销。因此语法上虽然允许,但一般很少采用这种方式。 • 若向函数传递结构体变量的地址,则可以通过指针对实参结构体变量的空间操作,从而改变实参的值。参数传递时只需传递一个地址,空间和时间的代价都很小。 • 一般采用结构体指针作为函数参数,而不采用结构体变量作为函数参数。
10.2结构体和函数 【例10.4】重新编写例10.3。 #define N 50 #include "stdio.h" struct student {int number; /*学号*/ char name[10]; /*姓名*/ float score[4]; /*四门课程成绩*/ float total; /*总分*/ float ave; /*平均成绩*/ }; void input(struct student *stu) /*输入学生信息*/ { int k; printf("number& name: "); scanf("%d %s",&stu->number,stu->name); printf("score(4): ");
10.2结构体和函数 for(k=0;k<4;k++) scanf("%f",&stu->score[k]); } void total_average(struct student *stu) /*计算总成绩和平均值*/ { int k; stu->total=0; for(k=0;k<4;k++) stu->total+= stu->score[k]; stu->ave= stu->total/4; } void output(struct student *stu) /*输出学生信息*/ { int k; printf("number:%6d\n ", stu->number); printf("name:%s\n", stu->name); printf("score: ");
10.2结构体和函数 for(k=0;k<4;k++) printf("%6.2f" , stu->score[k]); printf("\ntotal=%10.2f average=%10.2f \n", stu->total, stu->ave); } main( ) {struct student stu[N],*p; int i; for(i=0;i<N;i++) /*输入学生的信息*/ {printf("the %d student\n",i); input(&stu[i]); total_average(&stu[i]) ; /*计算平均成绩*/ } printf("\n"); for (i=0;i<N;i++) /*输出*/ output(&stu[i]); }
10.2结构体和函数 • 结构体数组作函数参数 • 向函数传递结构体数组实际也是传递数组的首地址。形参数组与实参数组使用共同的内存单元。 • 函数形参、实数是同类型的结构体数组名或结构体指针。 【例10.5】重新编写例10.4。 #define N 50 #include "stdio.h" struct student {int number; /*学号*/ char name[10]; /*姓名*/ float score[4]; /*四门课程成绩*/ float total; /*总分*/ float ave; /*平均成绩*/ };
10.2结构体和函数 void input(struct student stu[ ],int n)/*输入学生的信息*/ { int i,k; for(i=0;i<n;i++) { printf("the %d student\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]); } } void total_average(struct student *stu,int n) /*计算总成绩和平均值*/ {int k; struct student *p; p=stu+n;
10.2结构体和函数 for(;stu<p;stu++) {stu->total=0; for(k=0;k<4;k++) stu->total+= stu->score[k]; stu->ave= stu->total/4; } } void output(struct student *stu, int n) /*输出处理后的学生信息*/ { int k,i; for (i=0;i<n;i++) { printf("number:%6d\n ", (stu+i)->number); printf("name:%s\n", (stu+i)->name); printf("score: ");
10.2结构体和函数 for(k=0;k<4;k++) printf("%6.2f" , (stu+i)->score[k]); printf("\ntotal=%10.2f average=%10.2f \n", (stu+i)->total, (stu+i) ->ave); } } main( ) {struct student stu[N]; struct student *q=stu; input(q,N); total_average(stu,N); output(stu,N); }
10.3 动态数据结构——链表 • 内存空间的动态分配和释放 • free函数 • free函数的的格式: void free(void *p); • free函数的功能:释放指针变量p所指向的内存区,该函数没有返回值。一般p的值是调用malloc函数或calloc函数的返回值。 • malloc函数 • malloc函数的格式: void *malloc(unsigned int size); • malloc函数的功能:在内存的动态存储区中分配size个字节的连续空间,并返回分配空间的起始地址。若分配失败,则返回空指针。 • 注意:malloc函数的返回值一般是空类型的指针(有的编译系统将其处理成char类型的指针)。使用该函数时通常需要用强制类型转换,将其转换为所需要的类型。
10.3 动态数据结构——链表 【例10.6】注意内存空间的分配和释放。 main( ) {double *pointer; pointer=(double *)malloc(sizeof(double)); /*申请一个double类型变量所需内存空间*/ *pointer=8.234; printf(“%6.4f\n”,*pointer); *pointer=*pointer+10; printf("%7.4f\n",*pointer); *pointer=*pointer/2; printf("%6.4f",*pointer); free(pointer); /*释放空间*/ }
10.3 动态数据结构——链表 程序运行结果为: 8.2340 18.2340 9.1170 • calloc函数 • calloc函数的的格式: • void *calloc(unsigned int n,unsigned int size); • calloc函数的功能:分配内存的动态存储区中n个长度为size个字节的连续空间,并返回分配空间的起始地址。若分配失败,则返回一个空指针。对calloc的返回值同样也可进行强制类型转换后使用。 • 注意:calloc函数常用来为一维数组分配空间,其中n表示数组的长度,size表示数组元素所占字节数。 这种方式获得的数组一般称为动态数组。
10.3 动态数据结构——链表 【例10.7】注意内存空间的分配和释放。 main( ) {int *a,i; a=(int *)calloc(5,sizeof(int)); /*申请5个整型变量所需内存空间*/ for(i=0;i<5;i++) *(a+i)=i+1; for(i=0;i<5;i++) printf("%4d",*(a+i)); free(a); /*释放空间*/ } 程序运行结果为: 1 2 3 4 5
10.3 动态数据结构——链表 • 链表 • 链表由若干个结点构成。一个结点就是一个结构类型的变量。链表的每个结点需要占用连续内存空间,但是整个链表无须占用连续空间。因此,结点成员应该包括两部分:一部分存放实际需要的数据信息,称为数据域;一部分存放指针,用来链接各个结点,称为链接域或指针域。 • 在链表中,一个结点后面相邻的结点称为该结点的后继结点,该结点则称为其后继结点的前驱结点。 • 单链表:结点指针域只包含一个后继结点地址的链表。用一个指针变量保存单链表的首地址,即第一个结点的地址,这个指针变量被称为头指针。头指针是一个链表的标志。 注意:在链表的定义中,指向前驱结点或后继结点的指针的基类型是结点自身类型。
10.3 动态数据结构——链表 • 【例10.8】通过给结点的指针域赋值建立一个单链表。 #include "stdio.h" struct node { char c ; struct node *next; } main( ) { struct node *n1,*n2,*n3,*head,*p; /*申请三个结点空间*/ n1=(struct node* )malloc(sizeof(struct node)); n2=(struct node* )malloc(sizeof(struct node)); n3=(struct node* )malloc(sizeof(struct node)); /*建立链表*/ head=n1;
10.3 动态数据结构——链表 n1->c= 'A '; n1->next=n2; n2->c= 'B '; n2->next=n3; n3->c= 'C '; n3->next=NULL; for(p=head;p!= NULL;p=p->next) printf("%3c",p->c); }
10.3 动态数据结构——链表 • 链表的基本操作 为了简化链表的各种操作,一般在含有有效信息的所有结点之前再附加一个数据域不包含任何信息的结点。这个结点被称为“头结点”。头结点的存在是以一个结点空间为代价换取程序编写的简单明了。 下面的各项链表操作都是针对有头结点的链表编写的,结点的类型均为: struct node {int data; struct node *next; }; 并假定成员data的值不可能为负数。
10.3 动态数据结构——链表 • 创建链表 采用从表尾插入新结点的方式,则创建链表的主要步骤为: ① 生成只含有头结点的空链表,头指针head和尾指针tail(指向链表的最后一个结点)均指向头结点。 ② 读取数据信息,申请新的结点空间,将数据存于新结点。将新结点插入到链表最后一个结点的后面,tail指针指向新插入的结点,重复②的操作直到输入结束。 ③尾结点指针域赋NULL。 【例10.9】编写函数,在表尾插入结点生成单链表。 struct node *createtail( ) {struct node *head,*p,*tail; int data; head=(struct node* )malloc(sizeof(struct node)); /*生成头结点*/ tail=head; /*尾指针指向最后一个结点*/ scanf("%d",&data); /*读取数据*/
10.3 动态数据结构——链表 while(data>=0) { p=(struct node* )malloc(sizeof(struct node)); /*生成新结点p*/ p->data= data; /*读入的数据存放到结点p的数据域*/ tail->next=p; /*将新结点p插入到表尾*/ tail=p; /*修改尾指针tail指向当前的尾结点*/ scanf("%d",&data); /*读入下一个数据*/ } tail->next=NULL; /*将最后一个结点的指针域置为空*/ return(head); /*返回头指针*/ }
10.3 动态数据结构——链表 若采用从表头插入新结点的方式,则创建链表的主要步骤为: ① 生成只含有头结点的空链表,定义头指针head指向头结点。 ② 读取数据信息,申请新的结点空间,将数据存于新结点。将新结点插入到头结点后面。重复②的操作直到输入结束。 【例10.10】编写函数,在表头插入结点生成单链表。 struct node * createhead( ) {struct node *head,*p; int data; head=(struct node *)malloc(sizeof(struct node)); /*生成头结点*/ head->next=NULL; /*置为空表*/ scanf(“%d”,&data); /*读取数据*/ while(data>=0) {p=(struct node* )malloc(sizeof(struct node)); /*生成新结点p*/ p->data=data; /*读入的数据存放到结点p的数据域*/
10.3 动态数据结构——链表 p->next=head->next; /*将新结点插入到头结点之后*/ head->next=p; /*修改头结点的指针域指向新插入结点*/ scanf("%d",&data); /*读入下一个数据*/ } return(head); /*返回头指针*/ }
10.3 动态数据结构——链表 【例10.11】编写函数,输出单链表内容。 void output(struct node *head) /*输出链表*/ {struct node *p; printf("the linklist is:\n"); p=head->next; /*指针变量p获得头结点后继结点的地址*/ while (p!=NULL) /*判断指针变量p是否为NULL,为NULL表示链表结束*/ {printf("%6d",p->data); /*输出指针变量p结点的内容*/ p=p->next; /*指针变量p指向链表下一个结点*/ } return; }
10.3 动态数据结构——链表 • 删除结点 在单链表中删除指定结点的主要步骤为: ①找到要删除的p结点和p结点的前驱结点q(由于单向链表有头结点,所以前驱结点一定存在)。 ②删除该结点:修改前驱结点的指针域指向待删除结点的后继结点,并释放被删除结点所占用的空间。 注意:删除结点后,一定要释放被删除结点所占空间。否则,该结点会成为游离的、不可再用的空间。
10.3 动态数据结构——链表 【例10.13】编写函数,在单链表中删除值为x的结点。 struct node * delete(struct node *head, int x )/*从链表中删除值为x的结点*/ {struct node *p,*q; q=head; /*q指向头结点*/ p=head->next; /* p指向第一个结点*/ while(p&&(p->data!=x)) /*查找值为的结点p,查找过程中,q指向p结点的前驱*/ {q=p;p=p->next ;} if(p) {q->next =p->next ;free(p);} /*若找到,q结点的指针域指向p结点的后继,释放p*/ else {printf("not find! \n ");} /*若未找到,输出相应的信息*/ return(head); /*返回头指针*/ }
10.3 动态数据结构——链表 • 插入结点 插入到p结点之后,相应C语句为: ① s->next=p->next; s的指针域指向p的后继结点。 ② p->next=s; p的指针域指向新插入结点s。
10.3 动态数据结构——链表 在结点p之前插入新结点,首先要找到p的前驱结点q。相应C语句为: ① s->next=p; s的指针域指向p。 ② q->next=s; q的指针域指向新插入结点s。
10.3 动态数据结构——链表 【例10.14】编写函数,在单链表中值为x的结点之前插入一个值为y的结点。 struct node * insert(struct node *head,int x,int y)/*在链表值为x的结点前插入值为y的结点*/ {struct node *p,*q,*s; s=(struct node *)malloc(sizeof(struct node)); /*生成新结点s*/ s->data=y; /*将y存放在新结点中*/ q=head; /*q指向头结点*/ p=head->next; /* p指向第一个结点*/ while((p->data!=x)&&p) /*寻找值为x的结点,若表非空且未找到时循环*/ {q=p; p=p->next;} /*保持p指向当前结点,q指向p结点的前驱*/
10.3 动态数据结构——链表 if(p==NULL) /*未找值为x的结点,返回空*/ {printf("not find x\n"); free(s); return (head); } s->next=p; /*插入s结点到p之前*/ q->next=s; return(head); /*返回头指针*/ }
10.3 动态数据结构——链表 【例10.15】链表的综合操作。 #inlude "data.h" #inlude "func.c" #inlude "stdio.h" main( ) {struct node *head,*p; int x,a,b; char ch; do{ printf("\n1:create\n2:search\n3:insert\n4:delete\n5:output\n0:exit\n"); printf("please select: "); ch=getch( ); while(!(ch<='5'&&ch>='0')) ch=getch(); /*输入不在0到5之间无效*/ putchar(ch); printf("\n ");
10.3 动态数据结构——链表 switch(ch) { case '1': /*若选择1,创建链表*/ printf("please enter data: "); head=createhead( ); /*创建链表*/ output(head); /*输出创建后的链表*/ break; case '2': /*若选择2,在链表中查找数据*/ printf("please enter data: "); scanf("%d",&x); /*输入待查找的数据*/ p=search(head,x); /*进行查找*/ if(p!=NULL)printf("\nthe data is:%d ",p->data); else printf("\n not found"); break;
10.3 动态数据结构——链表 case '3': /*若选择3,在链表中插入数据*/ printf("please enter data(insert b before a)a,b: "); scanf("%d %d",&a,&b); /*输入插入位置及数据*/ head=insert(head,a,b); /*进行插入*/ output(head); /*输出插入后的链表*/ break; case '4': /*若选择4,在链表中删除数据*/ printf("please enter the data to delete: "); scanf("%d",&a); /*输入要删除的数据*/ head=delete(head,a); /*进行删除*/ output(head); /*输出删除后的链表*/ break; case '5': /*若选择5,输出链表数据*/ output(head); }