570 likes | 710 Vues
C 语言程序设计. 第十一章 结构体与共用体. 莆田学院 《C 语言程序设计 》 精品课程组 2005 年 6 月 制作. 目录. 一、结构体 二、内存分配函数 三、共用体 四、枚举类型 五、类型定义 作业. C(C++) 数据类型. 一、结构体. 1 .概述 数 组 是构造类数据,其 数组元素 必须是同一数据类型的。 结构体 也是构造类数据,但其 成员 可以是任何类型的。 构造类型使用户可以象处理单个变量一样来处理复杂的数据结构。. 诸如学生花名册、通讯录之类的数据,最适合用结构体来处理。因为这类数据具有如下特点:
E N D
C语言程序设计 第十一章 结构体与共用体 莆田学院《C语言程序设计》精品课程组 2005年6月 制作
目录 • 一、结构体 • 二、内存分配函数 • 三、共用体 • 四、枚举类型 • 五、类型定义 • 作业
一、结构体 1.概述 • 数 组 是构造类数据,其数组元素必须是同一数据类型的。 • 结构体 也是构造类数据,但其成员可以是任何类型的。 构造类型使用户可以象处理单个变量一样来处理复杂的数据结构。 • 诸如学生花名册、通讯录之类的数据,最适合用结构体来处理。因为这类数据具有如下特点: • 每个人信息都是一个复合的构造数据,如由姓名、学号、性别、年龄、家庭住址、联系电话等“成员”组成。 • 不同的人,数据的值不同,但都有共同的成员组成。
使用结构体的一般步骤 • 根据问题的要求定义一个结构体类型 • 用自己定义的结构体类型定义结构体变量 • 在程序中使用结构体变量处理问题 比较普通变量的情况: • 用系统给定的数据类型定义变量 • 在程序中使用变量处理问题
如何定义结构体类型? 关键字 用户指定 struct 结构体名 { …… 类型标识符 成员名; …… } ; /*struct 结构体名合称“结构类型标识符”*/ /*成员表列*/ /*此处分号不能省略*/
一个示例 【例一】 main() { struct student { int number; char name[6]; char sex; int age; char address[20]; } ; …… } 本质上,它定义了一个名为”student”的“结构体类型”(表头)。
小结:什么是“结构体类型”? • 用户自己定义的构造型数据类型 • 由若干数据项(成员)组成 • 同一结构体中的成员可以具有不同的数据类型 • 注意成员定义与普通变量定义的区别: 成员定义时——不为其分配内存 变量定义时——为其分配内存
结构体类型的特点: • 组成结构体的成员本身必须是一种已有定义的数据: • 基本类型成员(整型/字符型/实型) • 指针类型成员 • 数组类成员 • 其他构造类成员(包括已定义的另一种结构体)P262 注意:成员≠变量,故成员名可与变量名同名 P263 • 结构体类型可以有千千万万种,表示由若干不同数据项组 成的复合类型。 • 定义结构体类型时,系统不会为该结构体分配内存(只是定义类型,而非变量声明)
2、结构体类型变量的定义 定义了以上结构体类型后,struct student相当于标准数据类型关键字char, int ,float…我们可以用它来定义“结构体变量”。 ① 在结构体类型定义后,用 struct 结构体名 复合词定义 struct 结构体名 { … }; struct 结构体名 变量名1,变量名2,… 变量名n; 如:struct student a,b[30],*p; a为struct student类型的变量 b为struct student类型的数组(每个元素都是一个结构 体变量,都有众成员) p 为指向struct student类型的指针变量
还有两种合二为一方法…… ② 在定义结构体类型的同时定义结构体类型变量 struct 结构体名 { … }变量名1,变量名2,… 变量名n; ③ 直接定义结构体类型变量 struct { … }变量名1,变量名2,… 变量名n; 实际使用中,还可以使用以下形式: 先定义 #define STU struct student 尔后 STU student1,student2;
【例三】不定义结构体类型,直接定义结构体类型变量。【例三】不定义结构体类型,直接定义结构体类型变量。 main() { struct { int number; char name[6]; char sex; int age; char address[20]; } a,b[30],*p; …… } 【例二】定义结构体类型的同时定义结构体类型变量。 main() { struct student { int number; char name[6]; char sex; int age; char address[20]; } a,b[30],*p; …… }
其他有关知识… • 实际使用中,还可以使用以下形式: #define STU struct student STU stu1,stu2; • 比较一下两种变量定义方式的异同: • int a,b,c; 定义三个整型变量,每个变量占二个字节,可单独赋值。 • struct studenta,b,c; 定义三个结构体类型变量,每个变量下有若干“成员”。其占用的内存长度等于各成员项长度之和。
示例 【例四】 #include <stdio.h> int main() { struct student { int number; char name[6]; char sex; int age; char address[20]; } ; printf("%d\n ",sizeof(struct student)); return 0; } 结果: 31 (TC下) 36 (VC下)
示例 【例五】若有以下定义,则正确的赋值语句为。 struct complex { float real; float image; }; struct value { int no; struct complex com; }val1; A) com.real=1; B) val1.complex.real=1; C) val1.com.real=1; D) val1.real=1; 答案:C)val1.com.real=1
3、结构体变量的初始化和赋值 • 使一个结构体变量获得数据“值”(实际上是给其各个成员赋值)有三种方法: • ① 定义时初始化之 • ② 用赋值语句对各成员分别赋值 • ③ 同类型的结构体变量间相互赋值 如 student1=student2
示例: ① 定义时初始化之 【例六】 main() { struct { char name[15]; char class[12]; long num; } stu={"Wenli","Computer",200113}; printf("%s\n%s\n%ld\n",stu.name,stu.class,stu.num); } 结果:Wenli Computer 1 200113
示例: ② 用赋值语句对各成员分别赋值 【例七】 main(){ struct { char name[15]; char class[12]; long num; } stu={"Wenli","Computer",200113}; stu.name[0]='1'; stu.class[2]='A'; stu.num=1111; printf("%s,%s,%d\n",stu.name,stu.class,stu.num); } 结果: 1enli,CoAputer,1111
示例: ② 用赋值语句对各成员分别赋值 【例七】 main(){ struct { char name[15]; char class[12]; long num; } stu={"Wenli","Computer 1",200113}; stu.name[0]='1'; stu.class[2]='A'; stu.num=1111; printf("%s,%s,%d\n",stu.name,stu.class,stu.num); } 结果: 1enli,CoAputer,1111
正确编程: main() { struct { char name[15]; char class[12]; long num; } stu; scanf("%s",stu.name); scanf("%s",stu.class); scanf("%ld",&stu.num); printf("%s,%s,%ld\n",stu.name,stu.class,stu.num); } 但是如果改为stu.name=”wenli”是错误的。 为什么啊? 进行所谓“结构体变量赋值”只能逐个成员进行,不能将结构体变量作为一个整体进行输入和输出。如对结构体变量stu,以下语句是错误的: scanf(“%s,%s,%ld”,stu); printf(“%s,%s,%ld”,stu); 亦可用以下赋值语句: strcpy(stu.name,”wenli”); strcpy(stu.class, “Computer”); stu.num=200113;
4、结构体变量的引用 • 只能引用其成员变量 用圆点(成员运算符)——优先级最高 如 val1.no++ sum=val1.com.real+val1.com.image • 可以将成员变量按普通变量运算方式处理,包括取地址: &val1 (函数间传递用) &val1.no • 对多级结构体,只能对最低级的成员进行赋值、存取及运算处理。
示例 • 以下函数getdays( )计算某年某月某日是该年的第几天。如2001年2月5日是该年的第36天。闰年的二月有29天,表达式“(year%4==0&&year%100!=0)||(year%400)==0”值为真,即为闰年,其中year表示年号。
#include "stdio.h" struct datetp { unsigned year,month,day; }; unsigned months[ ]={0,31,28,31,30,31,30,31,31,30,31,30,31}; main() { struct datetp d; printf("请输入年 月 日:"); scanf("%u%u%u",&d.year,&d.month,【1】); if ((d.year%4==0&&d.year%100!=0)||(d.year%400)==0) months[2]=29; printf("%d年%d月%d日是该年的第%d天。\n", d.year,d.month, d.day, getdays(d)); } getdays(【2】date) { unsigned days=0,i; for(i=1;i<date.month;i++) days+=【3】; days+=date.day; return days; } 示例 答案:【1】&d.day 【2】struct datetp 【3】months[i]
5、结构体数组 定义结构体后定义 struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }; struct student stu[3]; • 定义 P265 • 定义结构体后定义 • 定义结构体时同时定义 定义结构体时同时定义 struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }stu[3];
5、结构体数组 定义后初始化 struct student { int num; char name[20]; char sex; int age; float score; }; struct student stu[3]={ {10101,"李宁",'M',18,87.5}, {10102,"张凡",'M',19,99}, {10103,"王敏",'F',20,78.5} } ; 一般初始化 struct student { int num; char name[20]; char sex; int age; float score; }stu[3]={ {10101,"李宁",'M',18,87.5}, {10102,"张凡",'M',19,99}, {10103,"王敏",'F',20,78.5} } ; • 初始化 P266 • 一般初始化 • 省略维数 [ ] • 定义后初始化
一般初始化示例 #include <stdio.h> int main() { struct student { int num; char name[20]; char sex; int age; float score; }stu[3]={ {10101,"李宁",'M',18,87.5}, {10102,"赵凡",'M',19,99}, {10103,"王敏",'F',20,78.5} }; int i; for (i=0;i<3;i++) printf("%s,学号%d,成绩:%.2f\n",stu[i].name,stu[i].num,stu[i].score); return 0; } 试一试运行结果…… 这里面的花括号能不能去掉?
6、指向结构体类型的指针 • 一个结构体变量的指针就是该变量所占据的内存段的起始地址。 如 struct student stu; struct student *p; p=&stu;
如果 struct student stu; struct student *p; p=&stu; 则以下三种形式等价: stu.age (结构体变量名.成员名) (*p).age (*指针变量名.成员名) p->age (指针变量名.成员名) 此时: p->age++等效于(p->age)++ 先得 到成员值,再使它加1; ++p->age等效于 ++(p->age) 先使成员 值加1,再使用之。
#include <stdio.h> #include <conio.h> #include <stdlib.h> struct tm { int hours,minutes,seconds;}; void delay() { long int t; for(t=1;t<=11128000;++t); } void update(struct tm *t) { (*t).seconds++; if ((*t).seconds==60) { (*t).seconds=0; (*t).minutes++; } if ((*t).minutes==60) { (*t).minutes=0; (*t).hours++; } if((*t).hours==24) (*t).hours=0; delay(); } void display(struct tm *t) { system("cls"); printf("%d:",(*t).hours); printf("%d:",(*t).minutes); printf("%d\n",(*t).seconds); } int main(){ struct tm time; time.hours=time.minutes=time.seconds=0; printf("Now, press any key to begin my clock..."); getch(); for(;;) { update(&time); display(&time); } return 0; } • 运行一下试试… • 再把例中的 • (*t). • 部分或全部替换为 • t-> • 结果有什么变化?
7、链表 • 特点: • 按需分配内存 • 不连续存放 • 有一个“头指针”(head)变量 • 每个结点中应包括一个指针变量,用它存放下一结点的地址。 • 最后一个结点的地址部分存放一个“NULL” (空地址)。
链表结点定义形式 • 定义形式: struct student { int number; char name[6]; struct student *next; };
链表操作常用技术语句 • p=p->next在链表结点间顺序移动指针 将p原来所指结点中next的值赋给p,而p->next值即下一结点起始地址,故p=p->next 的作用是使p指向下一结点起始地址。 • p2->next=p1 将新结点添加到现在链表中 如果p2是链表中的末结点,p1指新建结点,此句的功能是使p1所指新结点变成链表中的新的末结点。 • p2->next=NULL 让p2所在结点成为链表中最后结点
示例 答案:C 若已建立下面的链表结构,指针p指向某单向链表的首结点,如下图所示。 struct node { int data; struct node *next; } *p; 以下语句能正确输出该链表所有结点的数据成员data的是。 C) while (p) { printf(“%7d,”,(*p).data); p=p->next; } D) while (p!=NULL) { printf(“%7d,”, p->data); p++; } A) for ( ;p!=NULL;p++) printf(“%7d,”,p->data); B) for ( ;!p;p=p->next) printf(“%7d,”,(*p).data);
链表指针p++表示什么? 结果: FFDA FFE0 FFD6 FFEC 6 main() { struct stu { int num; char *name; int age; }st={12,"ABC",100},*p=&st; clrscr(); printf("%p\n",p++); printf("%p\n",p++); printf("%p\n",p++); printf("%p\n",p++); printf("%d\n",sizeof(st)); } 结论: 若p指向某个结构体变量,则 p++ 的功能是将指针p 移到本结点后的存储单元,而不是本结点的下一个成员处。 所以链表中不能用p++进行结点间的跳转。
#define NULL 0 struct student{ long num; float score; struct student *next; }; main() { struct student a,b,c,*head,*p; a.num=99101;a.score=89.5; b.num=99103;b.score=90; c.num=99107;c.score=85; head=&a; a.next=&b; b.next=&c; c.next=NULL; p=head; do { printf("%ld,%.1f\n",p->num,p->score); p=p->next; }while(p!=NULL); } 静态链表的建立P274 例11.7 • 注意有关技巧: • 结点是如何定义的? • 结点是如何建立的? • 如何使诸结点形成链表? • 最后一个结点如何建立? • 如何从一个结点转到下一结点? • 如何遍历所有结点?
二、内存分配函数 1、“动态内存分配”的概念 使用户程序能在运行期间动态地申请和释放内存空间,从而更有效地利用内存并提高程序设计的灵活性。 如,为了保证程序的通用性,最大需要建立一个1000个元素的字符数组,每个数组元素占30个字符,共需30000个字节存储空间。但程序某次运行时,可能只使用30个数组元素,于是就有29100个字节的已分配存储空间被浪费。 此时,可通过动态内存分配技术,将程序设计成运行时才向计算机申请内存,并在用完时立即释放占用的内存空间。 使用动态内存分配技术建立的链表称为“动态链表”。
2、动态内存分配函数 P275/P387 以下函数在malloc.h或stdlib.h中定义(n,x为无符号整数,p为指针变量): • void *malloc(x) 分配一个长度为x字节的连续空间,分配成功返回起始地址指针,分配失败(内存不足)返回NULL • void *calloc(n,x) 分配n个长度为x字节的连续空间(成败结果同上) • void *realloc(p,x) 将p所指的已分配空间大小调整为x个字节 • void free(p) 将由以上各函数申请的以p为首地址的内存空间全部释放
动态内存分配函数使用示例 #include "stdlib.h" main( ) { char *p; p=(char *)malloc(17); if (!p) { printf("内存分配出错"); exit(1); } strcpy(p,"This is 16 chars"); /*如果超过16个字符,可能破坏程序其他部分*/ p=(char *)realloc(p,18); if (p==NULL) { printf("内存分配出错"); exit(1); } strcat(p,"."); printf(p); free(p); } 结果:This is 16 chars.
动态链表的建立和遍历示例(后进先出的数据结构,即所谓“栈”)动态链表的建立和遍历示例(后进先出的数据结构,即所谓“栈”) #define NULL 0 struct info { int data; struct info *next; }; main() { struct info *base,*p; int n; base=NULL; for(n=0;n<10;n++) { p=(struct info *)malloc(sizeof(struct info)); p->data=n+1; p->next=base; base=p; } while (p!=NULL) { printf("%4d",p->data); p=p->next; } } 结果:10 9 8 7 6 5 4 3 2 1
动态链表的建立和遍历示例(以建立P274链表为例)动态链表的建立和遍历示例(以建立P274链表为例) head=p1=p2=(struct info *)malloc(sizeof(struct info)); printf("请输入第%d个同学的学号和成绩:",n++); scanf("%ld,%d",&p1->num,&p1->score); while(p1->num!=0) { p1=(struct info *)malloc(sizeof(struct info)); if(!p1){ printf("内存分配出错! "); exit(0); } printf("请输入第%d个同学的学号和成绩:",n++); scanf("%ld,%d",&p1->num,&p1->score); p2->next=p1; p2=p1; } p2->next=NULL; p1=head; while(p1->next!=NULL) { printf("%ld,%d\n",p1->num,p1->score); p1=p1->next; } } #define NULL 0 struct info { long num; int score; struct info *next; }; main() { struct info *head,*p1,*p2; int n=1; clrscr();
对链表的删除操作参见P279 struct student *del(struct student *head,long num) { struct student *p1,*p2; if (head==NULL) {printf("\n空链表!\n"); goto end;} p1=head; while (num!=p1->num&&p1->next!=NULL) { p2=p1;p1=p1->next;} /*非所找结点且非未结点后移*/ if(num==p1->num) /*找到了*/ { if (p1==head) head=p1->next; /*为首结点*/ else p2->next=p1->next; /*非首结点*/ printf("delete:%ld\n",num); n=n-1; } else printf(“%ld not been found!\n”,num); /*未找到*/ end: return(head); }
对链表的插入操作参见P282 struct student *insert(struct student *head,struct student *stud) { struct student *p0,*p1,*p2; p1=head; /*p1指向第一个结点*/ p0=stud; /*p0指向要插入的结点*/ if(head==NULL) /*如为空链表*/ {head=p0;p0->next=NULL;} else {while ((p0->num>p1->num)&&(p1->next!=NULL)) { p2=p1; p1=p1->next;} /*寻找位置*/ if(p0->num<=p1->num) { if(head==p1) head=p0; /*如最小插在第一个结点前*/ else p2->next=p0; /*否则插在p1所指结点前*/ p0->next=p1;} else {p1->next=p0;p0->next=NULL;}} /*未找到插在最后*/ n=n+1; return(head); }
三、共用体(联合体) • 1、概述P287 与结构体相似,共用体也是一种用户自己定义的构造型数据,其成员也可以具有不同的数据类型,但共用体将几种不同的数据项存放在同一段内存单元中。所以,每一时刻只能有一个成员存在——占用分配给该共用体的内存空间(新进旧出)。该共用体的数据长度等于最长的成员长度。
如何定义共用体类型? 关键字 用户指定 union 共用体名 { …… 类型标识符 成员名; …… } ; /*union 共用体名合称“共用类型标识符”*/ /*成员表列*/ 示例 union data { int i; char ch; float p; }; /*此处分号不能省略*/
3、共用体变量的声明 ① 用union 共用体名 复合词声明 union 共用体名 { … }; union 共用体名 变量名1,变量名2,… 变量名n; ② 在定义共用体类型的同时声明 union 共用体名 {… }变量名1,变量名2,… 变量名n; ③ 直接声明共用体类型变量 union {… }变量名1,变量名2,… 变量名n;
共用体变量的引用 • 共用体变量的引用与结构体相似 • 只能引用其成员变量,不能引用共用体变量本身 正确:printf(“%d”,data.i); 错误:printf(“%d”,data); • 不能对共用体变量赋值,不能初始化,不能作为 函数参数! 见P289示例 • 允许两个同类型共用体之间相互赋值。 • 可通过指针引用。
示例 main() { union u_type { int i; char ch[6]; long s; }; struct st_type { union u_type u; float score[3]; }; printf("%d\n",sizeof(struct st_type)); } 结果:18
示例 main() { union example { struct { int x; int y; } in; int a[2]; } e={0,0}; e.a[0]=1; e.a[1]=2; printf("%d,%d\n",e.in.x,e.in.y); } 结果:1,2
四、枚举类型 • 1、概述P291 所谓“枚举”,是指将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。 枚举类型也是用户自定义的数据类型,用此种类型声明的变量只能取指定的若干值之一。
2、定义枚举类型 一般形式 enum cn{red,yellow,blue,while,black}; enum day{sun,mon,tue,wed,thu,fri,sat}; 0 , 1 , 2 , 3, 4, 5 (有值常量) 花括号中间的数据项称“枚举元素”或“枚举常量”,是用户定义的标识符。