1 / 57

第十一章 结构体与共用体

C 语言程序设计. 第十一章 结构体与共用体. 莆田学院 《C 语言程序设计 》 精品课程组 2005 年 6 月 制作. 目录. 一、结构体 二、内存分配函数 三、共用体 四、枚举类型 五、类型定义 作业. C(C++) 数据类型. 一、结构体. 1 .概述 数 组 是构造类数据,其 数组元素 必须是同一数据类型的。 结构体 也是构造类数据,但其 成员 可以是任何类型的。 构造类型使用户可以象处理单个变量一样来处理复杂的数据结构。. 诸如学生花名册、通讯录之类的数据,最适合用结构体来处理。因为这类数据具有如下特点:

wenda
Télécharger la présentation

第十一章 结构体与共用体

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. C语言程序设计 第十一章 结构体与共用体 莆田学院《C语言程序设计》精品课程组 2005年6月 制作

  2. 目录 • 一、结构体 • 二、内存分配函数 • 三、共用体 • 四、枚举类型 • 五、类型定义 • 作业

  3. C(C++)数据类型

  4. 一、结构体 1.概述 • 数 组 是构造类数据,其数组元素必须是同一数据类型的。 • 结构体 也是构造类数据,但其成员可以是任何类型的。 构造类型使用户可以象处理单个变量一样来处理复杂的数据结构。 • 诸如学生花名册、通讯录之类的数据,最适合用结构体来处理。因为这类数据具有如下特点: • 每个人信息都是一个复合的构造数据,如由姓名、学号、性别、年龄、家庭住址、联系电话等“成员”组成。 • 不同的人,数据的值不同,但都有共同的成员组成。

  5. 使用结构体的一般步骤 • 根据问题的要求定义一个结构体类型 • 用自己定义的结构体类型定义结构体变量 • 在程序中使用结构体变量处理问题 比较普通变量的情况: • 用系统给定的数据类型定义变量 • 在程序中使用变量处理问题

  6. 如何定义结构体类型? 关键字 用户指定 struct 结构体名 { …… 类型标识符 成员名; …… } ; /*struct 结构体名合称“结构类型标识符”*/ /*成员表列*/ /*此处分号不能省略*/

  7. 一个示例 【例一】 main() { struct student { int number; char name[6]; char sex; int age; char address[20]; } ; …… } 本质上,它定义了一个名为”student”的“结构体类型”(表头)。

  8. 小结:什么是“结构体类型”? • 用户自己定义的构造型数据类型 • 由若干数据项(成员)组成 • 同一结构体中的成员可以具有不同的数据类型 • 注意成员定义与普通变量定义的区别: 成员定义时——不为其分配内存 变量定义时——为其分配内存

  9. 结构体类型的特点: • 组成结构体的成员本身必须是一种已有定义的数据: • 基本类型成员(整型/字符型/实型) • 指针类型成员 • 数组类成员 • 其他构造类成员(包括已定义的另一种结构体)P262 注意:成员≠变量,故成员名可与变量名同名 P263 • 结构体类型可以有千千万万种,表示由若干不同数据项组 成的复合类型。 • 定义结构体类型时,系统不会为该结构体分配内存(只是定义类型,而非变量声明)

  10. 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类型的指针变量

  11. 还有两种合二为一方法…… ② 在定义结构体类型的同时定义结构体类型变量 struct 结构体名 { … }变量名1,变量名2,… 变量名n; ③ 直接定义结构体类型变量 struct { … }变量名1,变量名2,… 变量名n; 实际使用中,还可以使用以下形式: 先定义 #define STU struct student 尔后 STU student1,student2;

  12. 【例三】不定义结构体类型,直接定义结构体类型变量。【例三】不定义结构体类型,直接定义结构体类型变量。 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; …… }

  13. 其他有关知识… • 实际使用中,还可以使用以下形式: #define STU struct student STU stu1,stu2; • 比较一下两种变量定义方式的异同: • int a,b,c; 定义三个整型变量,每个变量占二个字节,可单独赋值。 • struct studenta,b,c; 定义三个结构体类型变量,每个变量下有若干“成员”。其占用的内存长度等于各成员项长度之和。

  14. 示例 【例四】 #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下)

  15. 示例 【例五】若有以下定义,则正确的赋值语句为。 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

  16. 3、结构体变量的初始化和赋值 • 使一个结构体变量获得数据“值”(实际上是给其各个成员赋值)有三种方法: • ① 定义时初始化之 • ② 用赋值语句对各成员分别赋值 • ③ 同类型的结构体变量间相互赋值 如 student1=student2

  17. 示例: ① 定义时初始化之 【例六】 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

  18. 示例: ② 用赋值语句对各成员分别赋值 【例七】 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

  19. 示例: ② 用赋值语句对各成员分别赋值 【例七】 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

  20. 正确编程: 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;

  21. 4、结构体变量的引用 • 只能引用其成员变量 用圆点(成员运算符)——优先级最高 如 val1.no++ sum=val1.com.real+val1.com.image • 可以将成员变量按普通变量运算方式处理,包括取地址: &val1 (函数间传递用) &val1.no • 对多级结构体,只能对最低级的成员进行赋值、存取及运算处理。

  22. 示例 • 以下函数getdays( )计算某年某月某日是该年的第几天。如2001年2月5日是该年的第36天。闰年的二月有29天,表达式“(year%4==0&&year%100!=0)||(year%400)==0”值为真,即为闰年,其中year表示年号。

  23. #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]

  24. 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];

  25. 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 • 一般初始化 • 省略维数 [ ] • 定义后初始化

  26. 一般初始化示例 #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; } 试一试运行结果…… 这里面的花括号能不能去掉?

  27. 6、指向结构体类型的指针 • 一个结构体变量的指针就是该变量所占据的内存段的起始地址。 如 struct student stu; struct student *p; p=&stu;

  28. 如果 struct student stu; struct student *p; p=&stu; 则以下三种形式等价: stu.age (结构体变量名.成员名) (*p).age (*指针变量名.成员名) p->age (指针变量名.成员名) 此时: p->age++等效于(p->age)++ 先得 到成员值,再使它加1; ++p->age等效于 ++(p->age) 先使成员 值加1,再使用之。

  29. #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-> • 结果有什么变化?

  30. 7、链表 • 特点: • 按需分配内存 • 不连续存放 • 有一个“头指针”(head)变量 • 每个结点中应包括一个指针变量,用它存放下一结点的地址。 • 最后一个结点的地址部分存放一个“NULL” (空地址)。

  31. 链表结点定义形式 • 定义形式: struct student { int number; char name[6]; struct student *next; };

  32. 链表操作常用技术语句 • p=p->next在链表结点间顺序移动指针 将p原来所指结点中next的值赋给p,而p->next值即下一结点起始地址,故p=p->next 的作用是使p指向下一结点起始地址。 • p2->next=p1 将新结点添加到现在链表中 如果p2是链表中的末结点,p1指新建结点,此句的功能是使p1所指新结点变成链表中的新的末结点。 • p2->next=NULL 让p2所在结点成为链表中最后结点

  33. 示例 答案: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);

  34. 链表指针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++进行结点间的跳转。

  35. #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 • 注意有关技巧: • 结点是如何定义的? • 结点是如何建立的? • 如何使诸结点形成链表? • 最后一个结点如何建立? • 如何从一个结点转到下一结点? • 如何遍历所有结点?

  36. 二、内存分配函数 1、“动态内存分配”的概念 使用户程序能在运行期间动态地申请和释放内存空间,从而更有效地利用内存并提高程序设计的灵活性。 如,为了保证程序的通用性,最大需要建立一个1000个元素的字符数组,每个数组元素占30个字符,共需30000个字节存储空间。但程序某次运行时,可能只使用30个数组元素,于是就有29100个字节的已分配存储空间被浪费。 此时,可通过动态内存分配技术,将程序设计成运行时才向计算机申请内存,并在用完时立即释放占用的内存空间。 使用动态内存分配技术建立的链表称为“动态链表”。

  37. 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为首地址的内存空间全部释放

  38. 动态内存分配函数使用示例 #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.

  39. 动态链表的建立和遍历示例(后进先出的数据结构,即所谓“栈”)动态链表的建立和遍历示例(后进先出的数据结构,即所谓“栈”) #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

  40. 动态链表的建立和遍历示例(以建立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();

  41. 对链表的删除操作参见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); }

  42. 对链表的插入操作参见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); }

  43. 三、共用体(联合体) • 1、概述P287 与结构体相似,共用体也是一种用户自己定义的构造型数据,其成员也可以具有不同的数据类型,但共用体将几种不同的数据项存放在同一段内存单元中。所以,每一时刻只能有一个成员存在——占用分配给该共用体的内存空间(新进旧出)。该共用体的数据长度等于最长的成员长度。

  44. 如何定义共用体类型? 关键字 用户指定 union 共用体名 { …… 类型标识符 成员名; …… } ; /*union 共用体名合称“共用类型标识符”*/ /*成员表列*/ 示例 union data { int i; char ch; float p; }; /*此处分号不能省略*/

  45. 3、共用体变量的声明 ① 用union 共用体名 复合词声明 union 共用体名 { … }; union 共用体名 变量名1,变量名2,… 变量名n; ② 在定义共用体类型的同时声明 union 共用体名 {… }变量名1,变量名2,… 变量名n; ③ 直接声明共用体类型变量 union {… }变量名1,变量名2,… 变量名n;

  46. 共用体变量的引用 • 共用体变量的引用与结构体相似 • 只能引用其成员变量,不能引用共用体变量本身 正确:printf(“%d”,data.i); 错误:printf(“%d”,data); • 不能对共用体变量赋值,不能初始化,不能作为 函数参数! 见P289示例 • 允许两个同类型共用体之间相互赋值。 • 可通过指针引用。

  47. 示例 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

  48. 示例 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

  49. 四、枚举类型 • 1、概述P291 所谓“枚举”,是指将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。 枚举类型也是用户自定义的数据类型,用此种类型声明的变量只能取指定的若干值之一。

  50. 2、定义枚举类型 一般形式 enum cn{red,yellow,blue,while,black}; enum day{sun,mon,tue,wed,thu,fri,sat}; 0 , 1 , 2 , 3, 4, 5 (有值常量) 花括号中间的数据项称“枚举元素”或“枚举常量”,是用户定义的标识符。

More Related