Presentation is loading. Please wait.

Presentation is loading. Please wait.

第11章 结构体与共用体 结构体 共用体 枚举类型 用typedef定义类型 Tc工具. 江南大学控制科学与工程研究中心

Similar presentations


Presentation on theme: "第11章 结构体与共用体 结构体 共用体 枚举类型 用typedef定义类型 Tc工具. 江南大学控制科学与工程研究中心"— Presentation transcript:

1 江南大学控制科学与工程研究中心 张正道(wxzzd@hotmail.com)

2 第11章 结构体与共用体 结构体 共用体 枚举类型 用typedef定义类型 Tc工具

3 11.1 结构类型变量的定义 有时需要将不同类型的数据组合成一个有机的整体以便于引用,如: 一个学生的学号,姓名,性别,年龄,成绩,地址等.分别定义简单变量难以反映它们之间的联系。学生的成绩、姓名、学号等是一组逻辑相关的数据,孤立地考虑这些属性,将导致操作的不便或逻辑错误。 应当将它们组成一个组合项,其中可以包含若干个类型不同的数据项.

4 解决以上问题的方法就是引入结构体类型,将逻辑相关的数据有机组合在一起,称之为结构体。 C提供的结构体相当于记录
“结构”是一种构造类型,它是由若干“成员”组成的。 每一个成员可以是一个基本数据类型或者又是一个构造类型。 结构既是一种“构造”而成的数据类型, 那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。

5 一. 结构体类型的定义 结构体类型的一般定义形式为: struct 结构体类型名 { 数据成员列表; }; 用户命名的标识符
定义结构体类型的标识符 结构体类型定义的结束符

6 成员表由若干个成员组成, 每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:
类型说明符 成员名; 成员名的命名应符合标识符的书写规定。

7 例:一个学生的数据信息包含有学号、姓名、性别、年龄、成绩、住址,可将其定义为一个结构体类型:
struct student { long ID; /*学生学号*/ char name[10]; /*学生姓名*/ char sex; /*学生性别*/ int age; /*学生年龄*/ float score; /*学生成绩*/ char addr[30]; /*学生住址*/ }; 结构体类型定义仅仅是定义了一个特定的复合数据类型,描述了这一类型数据的公共属性,在程序中使用该结构体类型的具体对象,还需要说明这种类型的变量。

8 struct student {long ID; char name[10]; char sex; int age;
关键字 不能省略 结构体名,和标准类型 名一样可以定义变量 struct student {long ID; char name[10]; char sex; int age; float score; char addr[30]; }; 成员列表,每个成员又称 分量或域. 成员名定名规则与变量同 作为语句,必须以分号结束

9 11.2定义结构体类型变量的方法 结构体类型变量定义的 一般形式: struct 结构体类型名 结构体变量名; 图:结构体变量的存储结构
ID(4字节) stu1 name sex(1字节) age(2字节) score(4字节) addr …… 共10个字节 共30个字节 ID stu2 图:结构体变量的存储结构 共51个字节 结构体类型变量定义的 一般形式: struct 结构体类型名 结构体变量名;

10 1. 先定义结构体类型再定义结构体变量 struct student { long ID; char name[10]; char sex; int age; float score; char addr[30]; } ; struct student stu1, stu2;

11 2. 定义结构体类型的同时定义变量 struct student { long ID; char name[10]; char sex; int age; float score; char addr[30]; }stu1,stu2;

12 3. 直接定义结构体变量 struct { long ID; char name[10]; char sex; int age; float score; char addr[30]; }stu1,stu2; 结构体变量的三种形式可以任意选用。但在不同函数中定义说明同一类型的结构体变量时,用第三种方法不太方便,一般用第一种和第二种定义形式。

13 结构体类型的嵌套是指结构体的成员是一个结构体类型。
4.结构体类型的嵌套 结构体类型的嵌套是指结构体的成员是一个结构体类型。 若定义学生信息为结构体,其成员分别为:学号、姓名、性别、出生年月、成绩。其中出生年月包括出生的年、月、日三个数据,这些数据可以用另一个结构体类型表示。 例如,定义student结构体。 (1)先定义date结构体: struct date {int year; int month; int day; }; (2)再定义student结构体: struct student { long ID; char name[10]; char sex; struct date birthday; float score; };

14 说明: 1.类型与变量是不同的, 变量可以赋值,存取和运算。对类型是不分配空间的.
2.对结构体中的成员,可以单独使用,它的作用与地位相当与普通变量. 3.成员也可以是一个结构体变量。如: struct date { int month; int day; int year; } 4.成员名可以与程序中的变量名相同 struct date birthday; char addr[30]; } student1,student2; Struct student {int num; char name[20]; char sex; int age;

15 1.结构成员的使用 使用结构变量时, 往往不作为一个整体来使用。除了允许具有相同类型的结构变量相互赋值以外, 一般对结构变量的使用,包括赋值、输入、输出、 运算等都是通过结构变量的成员来实现的。 表示结构变量成员的一般形式是: 结构变量名.成员名 例如: boy1.num 即第一个人的学号 boy2.sex 即第二个人的性别

16 11.3结构体类型变量的引用 一. 结构体类型变量的引用
使用结构变量时, 往往不作为一个整体来使用。除了允许具有相同类型的结构变量相互赋值以外, 对一个结构体类型变量的引用包括赋值、输入、输出、 运算等都是通过引用它的每一个结构成员来实现的。 结构体变量不能作为一个整体进行输入输出,只能对其成员分别输出 。

17 引用一个结构体变量的成员有两种方法: 结构体变量名、指向结构体的指针变量 引用运算符有两个: . -> 其中,“->”为结构体指针运算符, 结构体成员运算符“.”在所有运算符中优先级最高. 如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。例如: boy1.birthday.month 即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。

18 其中,“.”称为结构体成员运算符,将结构体变量名与成员名连接起来,它具有最高级别的优先级。
用结构体变量名引用其成员的一般形式: 结构体变量名.成员名 其中,“.”称为结构体成员运算符,将结构体变量名与成员名连接起来,它具有最高级别的优先级。 结构体变量可以单独引用其成员,也可作为一个整体引用,还可以引用结构体变量或成员的地址。 1. 单独引用结构体变量的成员 struct clock { int hour,minute,second;}; struct date today; today.year=2004; today.month=4; today.day=12; today.time.hour=16; today.time.minute=47; today.time.second=15; struct date { int year, month, day; struct clock time; };

19 2. 结构体变量作为一个整体引用 结构体变量不可以作为整体进行输入输出,但可以作为函数的参数或返回值而被整体引用,也可以将一个结构体变量作为一个整体赋给另一个具有相同类型的结构体变量。 struct date { int year, month, day;}; struct date nextday(day) struct date day; {struct date temp; ... return(temp); } 函数nextday的形参day为结构体类型,它将整体接受同类型实参的值

20 3. 引用结构体变量的地址或成员的地址 引用结构体变量的成员地址,要在结构体成员引用的前面再加“&”运算符. 结构体变量a的成员t赋值: scanf(”%d”,&a.t); 引用结构体变量的地址,在结构体变量的前面直接加“&”: printf("%X",&a);

21 二.结构变量的赋值 结构变量的赋值就是给各成员赋值。可用输入语句或赋值语句来完成。

22 【例】给结构变量赋值并输出其值。 main() { struct stu { int num; char *name; char sex; float score; } boy1,boy2; boy1.num=102; boy1.name="Zhang ping"; printf("input sex and score\n"); scanf("%c %f",&boy1.sex,&boy1.score); boy2=boy1; printf("Number=%d\nName=%s\n",boy2.num, boy2.name); printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score); }

23 本程序中用赋值语句给num和name两个成员赋值,name是一个字符串指针变量。用scanf函数动态地输入sex和score成员值,然后把boy1的所有成员的值整体赋予boy2。最后分别输出boy2 的各个成员值。本例表示了结构变量的赋值、输入和输出的方法。

24 说明 不能将一个结构体变量为一个整体进行输入输出 只能对最低级的成员进行赋值或存取以及运算 对成员变量可以象普通变量一样进行各种运算
可以引用成员的地址,也可以引用结构体变量的地址

25 11.4 结构体变量的初始化 结构体变量可以在说明的同时初始化。 struct clock
{ int hour,minute,second;}; struct date { int year,month,day; struct clock time; }; struct date today={2004,4,12,17,4,30}; struct date today={2004,4,12,{17,4,30}};

26 本例中,boy2,boy1均被定义为外部结构变量,并对boy1作了初始化赋值。在main函数中,把boy1的值整体赋予boy2, 然后用两个printf语句输出boy2各成员的值。

27 【例】外部结构变量初始化。 struct student {long int num; char name[20]; char sex; char addr[30]; }a={89031,"Li Lin",'M',"123 Beijing Road"}; main() {printf(“No.:%ld\nname:%s\nsex:%c\naddress:%s\n”,a.num,a.name,a.sex,a.addr);} No. :89031 name:Li Lin sex: M address: 123 Beijing Road

28 【例】静态结构变量初始化。 main() { struct stu /*定义结构变量*/ { int num; char *name; char sex; float score; }boy2,boy1={102,"Zhang ping",'M',78.5}; boy2=boy1; printf("Number=%d\nName=%s\n",boy2.num, boy2.name); printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score); } 本例是把boy1,boy2都定义为局部的 结构变量作初始化赋值。

29 11.5结构体数组 结构体数组与以前介绍的数值型数组不同之处在于每个数组元素都是一个结构体类型的数据 11.5.1 结构体数组的定义
方法和结构变量相似,只需说明它为数组类型即可。例如: 1. 先定义结构体类型,后定义结构体数组 2. 结构体数组与结构体类型同时定义 3. 不定义结构体类型名,直接定义结构体数组

30 方法2: struct student {int num; char name[20]; char sex; int age; float score; char addr[30]; }stu[3]; 方法3: struct {int num; char name[20]; char sex; int age; float score; char addr[30]; }stu[3]; 方法1: struct student {int num; char name[20]; char sex; int age; float score; char addr[30]; }; struct student stu[3];

31 11.5.2 结构数组的初始化 对结构数组可以作初始化赋值,例如: struct stu { int num; char *name;
char sex; float score; }boy[5]={{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}; } 当对全部元素作初始化赋值时,也可不给出数组长度。

32 struct person {long ID; char name[10]; char sex; struct date birthdate; char department[30]; float salary; char address[30]; }employee[]= {{1001,”张山”,’M’,1990,3,5,”计算中心”,545,”长沙”}, {1002,”李红”,’F’,1989,10,1,”信息学院”,643,”武汉”}, {1003,”王武”,’M’,1987,4,15,”人事处”,745,”广州”}};

33 图 结构体数组的存储结构 employee[1] 1002 ID(4字节) "李红" name(10字节) 10 sex(1字节) 1 birthdate(date类型,6字节) "信息学院" salary(4字节) 643 address(30个字节) 'F' 1989 "武汉" department(30个字节) 共85个字节 employee[2] 1003 "王武" 4 15 "人事处" 745 'M' 1987 "广州" employee[0] 1001 "张山" 3 5 "计算中心" 545 1990 "长沙"

34 一个结构体数组元素相当于一个结构体变量,元素成员的访问使用数组元素的下标来实现。
结构体数组元素的引用 一个结构体数组元素相当于一个结构体变量,元素成员的访问使用数组元素的下标来实现。 结构体数组元素成员的访问形式: 结构体数组名[元素下标].结构体成员名 employee[0].ID=1001; 可以将一个结构体数组元素整体赋给同一结构体数组的另一个元素,或赋给同一结构体类型变量。 employee[1]=employee[2]; 与结构体变量一样,结构体数组元素也不能作为整体进行输入输出,只能以单个成员的形式实现。

35 【例】计算学生的平均成绩和不及格的人数。
struct stu { int num; char *name; char sex; float score; }boy[5]={ {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+=boy[i].score; if(boy[i].score<60) c+=1; } printf("s=%f\n",s); ave=s/5; printf("average=%f\ncount =%d\n",ave,c);

36 本例程序中定义了一个外部结构数组boy,共5个元素, 并作了初始化赋值。在main函数中用for语句逐个累加各元素的score 成员值存于s之中,如score的值小于60(不及格)即计数器C加1, 循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。

37 【例】建立同学通讯录 #include"stdio.h" #define NUM 3 struct mem { char name[20]; char phone[10]; }; main() struct mem man[NUM]; int i; for(i=0;i<NUM;i++) printf("input name:\n"); gets(man[i].name); printf("input phone:\n"); gets(man[i].phone); } printf("name\t\t\tphone\n\n"); for(i=0;i<NUM;i++) printf("%s\t\t\t%s\n",man[i]. name,man[i].phone); }

38 本程序中定义了一个结构mem,它有两个成员name和phone 用来表示姓名和电话号码。在主函数中定义man为具有mem 类型的结构数组。在for语句中,用gets函数分别输入各个元素中两个成员的值。然后又在for语句中用printf语句输出各元素中两个成员值。

39 例11.2 3个候选人,输入得票人的名字,要求最后输出个人得票结果
#include "string.h" struct person {char name[20]; int count; } leader[3]={"li",0,"zhang",0,"fun",0}; main() {int i,j; char leader_name[20]; for (i=1;i<=10;i++) {scanf("%s", leader_name);

40 for (j=0;j<3;j++) if(strcmp(leader_name,leader[j].name)==0) leader[j].count++; } printf ("\n"); for (i=0;i<3;i++) printf ("%5s:%d\n",leader[i].name,leader[j].count);

41 一个结构体变量的指针就是该变量所占据的内存段的起始地址,可以设一个指向一个结构体变量的指针变量
指向结构体变量的指针 指向结构体数组的指针 用指向结构体的指针作函数参数

42 11.6指向结构体类型数据的指针 当结构体很大时,结构体变量的整体赋值效率是相当低的。
结构体变量名就是分配给该变量的内存空间的起始地址,我们可以利用指向结构体变量的指针,实现在不同程序段对同一内存区域的结构体变量的数据成员执行各种操作。

43 一. 指向结构体变量的指针 指向结构体变量的指针也称为结构体指针,它保存了结构体变量的存储首地址。 1. 结构体指针的定义形式:
struct 结构体类型名 *指针变量名; struct student stu,*p; p=&stu;

44 2. 结构体变量成员的三种访问方法 (1)结构体变量.成员名 stu.ID (2)(*结构体指针).成员名 (*p).ID (3)结构体指针->成员名 p->ID 结构体指针运算符“->” 结构体指针->结构体成员

45 例11.3 指向结构体变量的指针 #include <string.h> main() {struct student
例11.3 指向结构体变量的指针 #include <string.h> main() {struct student {long num; char name[20]; char sex; float score; }; struct student stu_1; struct student *p; p=&stu_1; stu_1.num=89101;

46 Strcpy(stu_1.name,"Li Lin");
stu_1.sex='M'; stu_1.score=89.5; printf ("No.: %ld\nname:%ssex:%c\nscore:%f\n", stu_1.num,stu_1.name,stu_1.sex,stu_1.score); printf ("No.: %ld\nname:%ssex:%c\nscore:%f\n", (*p).num,(*p).name,(*p).sex,(*p).score); } 三种等价形式: 1 结构体变量.成员名 2 (*p).成员名 3 p->成员名

47 p->n p->n++ ++p->n
struct student stu; struct student *p; p=&stu; 指向运算符 89101 “Li Lin” ‘M’ 89.5 stu.num stu.name stu.sex stu.score (*p).num (*p).name (*p).sex (*p).score p->num p->name p->sex p->score p 分析以下几种运算: p->n p->n++ 使用值后加1 ++p->n 先加1后使用值

48 二. 指向结构体数组的指针 对于已定义的结构体数组,若用一个变量来存放该结构体数组在内存中的首地址,则该变量为指向结构体数组的指针变量。
例如,定义结构体类型person和结构体指针变量p。 struct person { char name[10]; int age; }; struct person *p,s,boy[3]={”Zhang”, ,”Wang”,20,”Li”,17}; p=boy; 定义了结构体数组boy和结构体指针变量p,且p指向数组boy的首地址。

49 注意: 将结构体变量与结构体数组的首地址赋给结构体指针的不同之处: p=&s ; /*s为结构体变量*/ p=boy; /*boy为结构体数组,boy为数组的首地址*/ 结构体指针也可以指向结构体数组的一个元素,这时结构体指针变量的值是该结构数组元素的首地址。 若要将结构体成员的地址赋给结构体指针p,则必须使用强制类型转换操作,转换形式为: p=(struct 结构体类型名 *)&结构体数组元素.成员名

50 例:指向结构体数组的指针 struct student {int num; char name[20]; char sex; int age;}; struct student stu[3]={{10101,"Li Lin",'M',18},(10102,"Zhang Fun",'M',19},{10104, "Wang Min",'F',20}}; main(); {struct student *p; printf ("No. Name sex age\n");

51 for (p=stu;p<stu+3; p++)
printf ("%5d %-20s %2c %4d\n",p->num, p->name,p->sex;p->age); } p

52 已有一个结构体数组: struct student stu[4]={...};
再定义一个指针变量: struct student *p; p=stu; 如: for (p=stu;p<stu+3;p++) printf(“%5d,%-20s,%2c,%4d”,p->num, p->name,p->sex,p->age); 注: 先使p指向数组stu的起始地址,并于第一个循环输出stu[0] 的各个成员.然后p+1意味着增加的地址值为结构体类型 数组stu的一个元素所占的字节数.

53 注意区别: (++p)->num (p++)->num p已定义为指向struct student类型的数据:
p=stu;正确(数组名) p=&stu.name;错误(不能指向结构体成员) p=(struct student *)&stu.name

54 三. 用结构不了指向结构体数组的指针作参数 C语言中,在函数之间传递结构体变量有两种方法:
(1)以结构体变量作为参数,直接传递结构体变量的值. (2)以结构体指针作为参数,传递结构体变量的地址。 1. 结构体变量作为函数的参数 在ANSI C标准中允许用结构变量作为函数参数进行整体传送,即直接将实参结构体变量的各个成员的值逐个传递给形参结构体变量的对应成员。 注意,实参与形参必须是相同结构体类型的变量。

55 struct stu {int num; char *name; char sex; float score; } boy[5] ; 函数pass的参数传递是一个值传递过程 for(i=0;i<5;i++) {s+=boy[i].score; if(!pass(boy[i])) c+=1; /*以结构体数组元素作为实参*/ } int pass(struct stu p)/* 函数说明*/

56 2. 指向结构体变量的指针作为函数的参数 通过结构体变量的整体传递可以实现函数参数的传递,但这要将结构体变量的全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。 因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传递给函数形参的只是地址,从而减少了时间和空间的开销。

57 struct stu {int num; char *name; char sex; float score; } boy[5],*p ; ave(p); void ave(struct stu *p) 与普通数组的特性相同,结构体数组名也是该数组的首地址,在进行参数传递时也可直接将结构体数组名传递给作为函数形参的结构体指针变量。如以上函数调用语句可写成:ave(boy);

58 3. 结构体类型的函数 若函数的返回值仍是结构体类型,则称该函数为结构体类型函数。其一般定义形式: struct 结构体类型 函数名(形参表) { 函数体 } 其中结构体类型必须已定义。 例:每个学生有三门成绩,用函数输入学生的成绩并求每个学生的平均成绩。分析: (1)定义一个返回值为学生结构体类型的函数,用于输入学生信息; (2)在主函数main中调用结构体函数,得到学生结构体或学生结构体的地址。

59 11.7用指针处理链表 C语言中,变量存储空间的分配分为静态分配和动态分配。 静态存储分配:
先在程序说明部分进行变量的说明,然后在程序编译时分配适当的存储单元。这些存储单元一经分配,在它的生存期内是固定不变的。 动态存储分配: 在程序执行期间,通过“申请”分配指定的存储空间来存储数据,当有闲置不用的存储空间时,又可以随时将其释放。

60 用户可以通过调用C语言的标准库函数来实现动态存储分配,从而得到或释放指定数目的内存空间。
ANSI C标准为动态分配系统定义了四个函数:malloc、calloc、free和realloc。 这些函数在头文件alloc.h及stdlib.h中声明。

61 C语言中数组的长度是预先定义好的,在整个程序中固定不变。不允许动态数组类型。
例如: int n; scanf("%d",&n); int a[n]; 用变量表示长度,想对数组的大小作动态说明, 这是错误的。 但是在实际的编程中,往往会发生这种情况,即 所需的内存空间取决于实际输入的数据,而无法预先 确定。对于这种问题,用数组的办法很难解决。为了 解决上述问题,C语言提供了一些内存管理函数,可 以按需要动态地分配内存空间,也可把不再使用的空 间回收,为有效地利用内存资源提供了手段。

62 1、分配内存空间函数malloc 调用形式: (类型说明符*) malloc (size) 功能:在内存的动态存储区中分配一块长度为“size” 字 节的连续区域。函数的返回值为该区域的首地址。 “类 型说明符”表示把该区域用于何种数据类型。(类型说明 符*)表示把返回值强制转换为该类型指针。“size”是一 个无符号数。

63 2、分配内存空间函数 calloc calloc 也用于分配内存空间。 调用形式: (类型说明符*)calloc(n,size) 功能:在内存动态存储区中分配n块长度为“size”字节 的连续区域。函数的返回值为该区域的首地址。(类型 说明符*)用于强制类型转换。calloc函数与malloc 函数 的区别仅在于一次可以分配n块区域。 例如: ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语 句的意思是:按stu的长度分配2块连续区域,强制转换 为stu类型,并把其首地址赋予指针变量ps。

64 3、释放内存空间函数free 调用形式: free(void*ptr); 功能:释放ptr所指向的一块内存空间,ptr 是一个任 意类型的指针变量,它指向被释放区域的首地址。 被释放区应是由malloc或calloc函数所分配的区域:

65 (1)函数malloc 函数原型: void *malloc(unsigned int size); 函数功能: 在内存的动态存储区中分配一个长度为size的连续存储空间。其中,形参size为无符号整数,是函数malloc要求分配存储空间的字节个数。函数返回值为一个指针,它指向所分配存储空间的起始地址。若函数返回值为0,则表示未能成功申请到内存空间。函数类型为void,表示返回的指针不指向任何具体的类型.

66 例如: malloc(8); 通过函数malloc向系统申请8个字节的内存空间,其起始地址通过函数值返回。若要求用一个指针变量(具有某种类型)指向这个起始地址,则需要显式进行类型转换。 例如: pc=(char *) malloc (100); 表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。

67 (2) 函数calloc 函数原型: void calloc(unsigned n,unsigned size); 函数功能: 在内存的动态存储区域中分配n个长度为size的连续存储空间。函数的返回值为分配域的起始地址;如果分配不成功,则返回值为0。 例如: 分配3个8字节的的连续存储空间,并将其起始地址赋给整型指针p。 int *p; p=(int *)calloc(3,8);

68 (3) 函数free 函数原型: void free(void *ptr); 函数功能: 释放由指针变量ptr为所指示的内存区域。其中,ptr一个指针变量,指向最近一次调用函数malloc或calloc时所分配的连续存储空间的首地址。通过函数free将已分配的内存区域交还系统,使系统可以重新对其进行分配。 例如: ... free(p); long *p; p=(long *)malloc(8);

69 (4) 函数realloc 函数原型: void *realloc(void *ptr,unsigned int size); 函数功能: 将ptr所指向的存储空间重新分配大小为size的存储空间,并返回重新分配后的存储空间的首地址。其中,ptr指向原先用函数malloc分配的内存区域的起始地址。函数realloc将ptr指向的存储区的大小改为size个字节,可以使原先分配的存储区域扩大或缩小。其函数返回值是一个指针,即为新的存储区域的首地址。 注意:新的首地址不一定与原先定义的首地址相同,因为为了增加空间,存储区会进行必要的移动。

70 [例]分配一块区域,输入一个学生数据。 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("Number=%d\nName =%s\n",ps->num,ps->name); printf("Sex=%c\nScore=%f\n", ps->sex,ps->score); free(ps); }

71 本例中,定义了结构stu,定义了stu类型指针变量ps。
然后分配一块stu大内存区,并把首地址赋予ps,使ps 指向该区域。再以ps为指向结构的指针变量对各成员 赋值,并用printf 输出各成员值。最后用free函数释放 ps指向的内存空间。 整个程序包含了申请内存空间、 使用内存空间、释放内存空间三个步骤, 实现存储空 间的动态分配。

72 11.7.2 链表概述 在上例中采用了动态分配的办法为一个结构分配内 存空间。每一次分配一块空间可用来存放一个学生
的数据, 我们可称之为一个结点。有多少个学生就 应该申请分配多少块内存空间, 也就是说要建立多 少个结点。当然用结构数组也可以完成上述工作, 但如果预先不能准确把握学生人数,也就无法确定 数组大小。 而且当学生留级、退学之后也不能把该 元素占用的空间从数组中释放出来。

73 用动态存储的方法可以很好地解决这些问题。
有一个学生就分配一个结点,无须预先确定学 生的准确人数,某学生退学, 可删去该结点, 并释放该结点占用的存储空间。从而节约了宝 贵的内存资源。 另一方面,用数组的方法必须 占用一块连续的内存区域。 而使用动态分配时, 每个结点之间可以是不连续的(结点内是连续的)。

74 结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。可在第一个结点的指针域内存入第二个结点的首地址, 在第二个结点的指针域内又存放第三个结点的首地址, 如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。

75 数组 数组的致命弱点: 顺序存储结构 逻辑关系上相邻的两个元素在物理位置上也相邻 随机存取
(1)在对数组进行插入或删除操作时,需移动大量数组元素 (2)在数组的长度是固定的而且必须预先定义,数组的长度难以缩放,对长度变化较大的数据对象要预先按最大空间分配,使存储空间不能得到充分利用

76 2.链表存储结构是一种动态数据结构 特点: (1)它包含的数据对象的个数及其相互关系可以按需要改变. (2)存储空间是程序根据需要在程序运行过程中向系统申请获得. (3)不要求逻辑上相邻的元素在物理位置上也相邻. (4)没有顺序存储结构所具有的弱点.

77 链表是一种常见的可以进行动态存储分配的数据结构。与数组相比,其所占内存的长度可以根据实际需要增加或减小,从而避免了内存分配时的浪费。
链表的结构 1、“头指针”变量 用于存放一个地址,以指向链表中第一个元素所在的内存地址。 2、“结点”变量 链表的基本组成单位。一个“结点”应该包含两个部分:结点内容和后继指针。其中结点内容是指结点中存储的数据的值,后继指针用于指向后一个结点所在的内存地址。 3、“表尾” 后继指针为“空”的结点变量。

78 以上链表结构中只有一个方向的指针,因此又称为单链表,简称为链表。
图 单向链表的逻辑状态 Qian Sun Li Zhou Wu Wang Head 7 13 1 43 25 37 以上链表结构中只有一个方向的指针,因此又称为单链表,简称为链表。 一般地,用户可根据链表存放的信息如存放学生信息就称为学生链表,存放职工信息就称为职工链表。

79 在单链表,通常称它的数据元素为结点,每个结点都是一个结构体,至少包括两个成员:存储数据元素信息的成员称为数据域;存储直接后继结点存储位置的成员称为指针域.
显然,链表结点的指针域存放的地址类型与它自身的类型是相同的。 这就是C语言中较为特殊的递归结构体或自引用结构体,这种结构体具指向自身结构体的指针,一般在实现链表、树等数据结构时会用到这种特殊的结构体。

80 链表的存储地址与指针域的关系 存储地址 数据域 指针域 head 7 1 Li 43 Qian 13 Sun 25 Wu 37 Wang NULL Zhou 每个链表都有一个“头指针”head,整个链表的访问必须从头指针开始进行,头指针指示链表中的第一个结点的存储位置,习惯上将“头指针”head指示的链表简称为链表head,下同。同时,由于最后一个数据元素没有直接后继结点,则链表中最后一个结点的指针为“空”(NULL,即空地址)。

81 数据元素之间的逻辑关系是由结点中的指针指示的,逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻,即链表中的数据元素在内存中不是顺序存放的,要访问其数据元素不能像数组一样按下标去查找。要找一个元素,必须先找到上一个元素,根据上一个元素的指针域才能找到下一个元素。 因此,链表的数据元素访问必须从头指针开始,逐个访问链表的每个结点,直到元素的指针域为空为止。

82 要使用链表,首先应定义结点的类型,再定义相应的结构体变量。例如,前面链表中结点的结构类型可以定义为:
struct student {char name[10]; struct student *next; }; 其中,next为指针变量,其类型为结构体类型student,它可存储一个student结构体类型变量的地址,即实现链表中指向下一个结点的指针域。 这是一个递归定义,它在结构体student的定义未完成时又引用它定义其它的变量(指针变量)。

83 建立动态链表 第0个结点称为头结点, 它存放有第一个结点的首地址,没有数据,只是一个指针变量。以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如学号num,成绩score等。另一个域为指针域, 存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。例如,一个存放学号和成绩的结点结构为: struct stu { int num; int score; struct stu *next; } 前两个成员项组成数据域,后一个成员项next构成指针域,它是一个指向stu类型结构的指针变量。

84 链表的基本操作对链表的主要操作有以下几种:
1.建立链表; 2.结构的查找与输出; 3.插入一个结点; 4.删除一个结点; 下面通过例题来说明这些操作。

85 一. 建立链表 链表与数组不同,不是程序定义时就可建立,而是在程序运行过程中一个结点一个结点地建立起来的,并在结点之间形式链接关系。
因此,链表的建立是一个动态存储空间分配和形成链接关系的过程。 建立单向链表的方法有尾插法与头插法两种。 1. 尾插法建立单链表 所谓尾插法,是指新插入的结点总是放在链表尾部。一般地,链表的建立过程是在一个空链表的基础上逐步插入新的结点而成的。所谓空链表,即链表没有一个结点,这时链表的头指针为空。

86 (2)生成新结点p,对p的数据域和指针域赋值。由于新结点是尾结点其后继为空,故p->next=NULL;。 (3)将p结点插入到链表:
用尾插法建立链表的过程如下: (1)建立一个空链表,head=NULL;head为头指针它指向链表的第一个结点。定义一个指向链表尾结点的指针last,显然last的初始值也为空(即NULL)。 将“指针head所指向的结点”简称为“head结点”,将“指针p所指向的结点”简称为“p结点” 。 (2)生成新结点p,对p的数据域和指针域赋值。由于新结点是尾结点其后继为空,故p->next=NULL;。 (3)将p结点插入到链表: 如果链表为空(即head=NUUL),则p结点为头结点,也是尾结点,即: head=p;last=p; 否则应将p结点插入到last结点之后,并使p结点成为当前的尾结点,即: last->next=p;last=p; (4)重复(2)~(3),继续插入新结点直到结束。

87 【例】建立一个三个结点的链表,存放学生数据。为简单起见,我们假定学生数据结构中只有学号和年龄两项。可编写一个建立链表的函数creat。程序如下:
#define NULL 0 #define TYPE struct stu #define LEN sizeof (struct stu) struct stu { int num; int age; struct stu *next; };

88 TYPE *creat(int n) { struct stu *head,*pf,*pb; int i; for(i=0;i<n;i++){ pb=(TYPE*) malloc(LEN); printf("input Number and Age\n"); scanf("%d%d",&pb->num, &pb->age); if(i==0) pf=head=pb; else pf->next=pb; pb->next=NULL; pf=pb; } return(head);

89 在函数外首先用宏定义对三个符号常量作了定义。结构stu定义为外部类型,程序中的各个函数均可使用该定义。creat函数用于建立一个有n个结点的链表,它是一个指针函数,它返回的指针指向stu结构。在creat函数内定义了三个stu结构的指针变量。head为头指针,pf 为指向两相邻结点的前一结点的指针变量。pb为后一结点的指针变量。在for语句内,用malloc函数分配长度与stu长度相等的空间作为一结点,首地址赋予pb。然后输入结点数据。如果当前结点为第一结点(i==0),则把pb值 (该结点指针)赋予head和pf。如非第一结点,则把pb值赋予pf 所指结点的指针域成员next。而pb所指结点为当前的最后结点,其指针域赋NULL。再把pb值赋予pf以作下一次循环准备。creat函数的形参n,表示所建链表的结点数,作为for语句的循环次数。

90 2. 头插法建立单链表 所谓头插法,是指新插入的结点总是作为链表的第一个结点。 用头插法建立链表的过程如下:
(1)建立一个空链表,即head=NULL;与尾插法不同的是这里不需定义尾指针。 (2)生成新结点p,对新结点p的数据域赋值。由于新插入的结点成为头结点,其指针域不必赋值。 (3)将p结点插入到链表: 先p结点的后继为当前的头结点,然后使p结点成为当前的头结点,即: p->next=head;head=p; (4)重复(2)~(3),继续插入新结点直到结束。

91 二. 链表的访问 1. 输出链表结点 将链表中各结点的数据依次输出。输出链表结点的操作过程如下: (1)取得链表头结点的地址 head;
(2)用一个跟踪指针p指向头结点,即p=head; (3)输出p所指结点的成员值; (4)移动指针p,使它指向它的后继结点。 即p=p->next; (5)重复(3)(4),直到指针p为空。

92 2. 统计链表结点的个数 一般情况下,各个单链表中结点个数是随机的,要想知道表中结点数目,必须从表头开始访问到表尾,逐个统计出结点数目。 3. 查找链表的某个结点 在链表上查找符合某个条件的结点,也必须从链表头开始访问链表。 (1)查找链表中第n个结点,若找到,则返回它的地址,否则返回空指针。 (2)在链表中查找指定值的结点,若找到,则返回它的地址,否则返回空指针。

93 【例】写一个函数,在链表中按学号查找该结点。
TYPE * search (TYPE *head,int n) { TYPE *p; int i; p=head; while (p->num!=n && p->next!=NULL) p=p->next; /* 不是要找的结点后移一步*/ if (p->num==n) return (p); if (p->num!=n&& p->next==NULL) printf ("Node %d has not been found!\n",n }

94 本函数中使用的符号常量TYPE与例7.10的宏定义相同,等于struct stu。函数有两个形参,head是指向链表的指针变量,n为要查找的学号。进入while语句,逐个检查结点的num成员是否等于n,如果不等于n且指针域不等于NULL(不是最后结点)则后移一个结点,继续循环。如找到该结点则返回结点指针。 如循环结束仍未找到该结点则输出“未找到”的提示信息。

95 三. 链表的删除操作 从一个链表中删除一个结点,并不是真正从内存中把它抹掉,而是把它从链表中分离出来,即改变链表的链接关系。
1.删除第n个结点 将以head为头的链表中的第n个结点从链表中移出,其后继的结点往前移,使之仍为一个链表,只是结点数目比原来少1。

96 删除学生链表head中的第n个结点。 分析: (1)p=head,q指针指向p所指结点的前1个结点; (2)i为访问过的结点数目; (3)i++,q=p,p=p->next,p、q移动1个结点; (4)若p!=NULL且i<n-1,重复(3) (5)若n==1,则删除第1个结点,将下一个结点作为链表头结点: head=head->next; (6)若head==NULL,链表为空,不能删除; (7)若p==NULL,第n个结点不存在,不能删除; (8)找到第n个结点,删除p结点: q->next=p->next; p的前1个结点的next值赋值为p的next域; (9)返回head。

97 2. 删除指定结点 删除链表head中值为x的结点。 分析: (1)p=head,q指针指向p所指结点的前1个结点;
(2)若head==NULL,链表为空,不能删除; (3)q=p,p=p->next,p、q移动1个结点; (4)若p->next!=NULL且p->score!=x,重复(3) (5)若n==1,则删除第1个结点,将下一个结点作为链表头结点:head=head->next; (6)若p->score==x,则找到score值为x的结点,删除p结点: 若p==head为第1个结点,让head指向下一个结点,head=p->next; 否则,q->next=p->next; p的前1个结点的next值赋值为p的next域; (7)返回head。

98 【例】写一个函数,删除链表中的指定结点。删除一个结点有两种情况:
1. 被删除结点是第一个结点。这种情况只需使head指向第二个结点即可。即head=pb->next。 2. 被删结点不是第一个结点,这种情况使被删结点的前一结点指向被删结点的后一结点即可。即 pf->next=pb->next。 函数编程如下:

99 TYPE * delete(TYPE * head,int num)
{ TYPE *pf,*pb; if(head==NULL) /*如为空表, 输出提示信息*/ { printf("\nempty list!\n"); goto end;} pb=head; while (pb->num!=num && pb->next!=NULL) /*当不是要删除的结点,也不是最后一个结点时,继续循环*/ pf=pb;pb=pb->next; } /*pf指向当前结点,pb指向下一结点*/

100 if(pb->num==num) {if(pb==head) head=pb->next; /*如找到被删结点,且为第一结点,则使head指向第二个结点,否则使pf所指结点的指针指向下一结点*/ else pf->next=pb->next; free(pb); printf("The node is deleted\n");} else printf("The node not been foud!\n"); end: return head; }

101 函数有两个形参,head为指向链表第一结点的指针变量,num删结点的学号。 首先判断链表是否为空,为空则不可能有被删结点。若不为空,则使pb指针指向链表的第一个结点。进入while语句后逐个查找被删结点。找到被删结点之后再看是否为第一结点,若是则使head指向第二结点(即把第一结点从链中删去),否则使被删结点的前一结点(pf所指)指向被删结点的后一结点(被删结点的指针域所指)。如若循环结束未找到要删的结点, 则输出“末找到”的提示信息。最后返回head值。

102 四. 链表的插入操作 所谓链表的插入操作就是将一个结点插入到一个已有链表的某个位置上。 1. 在第n个结点之后插入1个新结点(x)

103 (1)输入n和x(一般在调用函数中处理);
(2)申请一个新结点,q指针指向新结点,q的值为x(一般在调用函数中处理); (3)p=head,r=NULL,r为p的前驱结点,i=0; (4)i++,r=p,p=p->next,p结点往前移动一个结点; (5)若i<n且p!=NULL,则重复(4); (6)若i==0,则链表为空,没有结点,q结点作为链表的第1个结点插入: q->next=head,head=q; (7)若i<n且p==NULL,则链表不足n个,将q结点插入到链表尾r结点之后: r->next=q,q->next=NULL; (8)否则,将q结点插入到第n个结点之后,即插入到r结点与p结点之间: r->next=q,q->next=p; (9)返回链表头head。

104 2. 指定值的结点之后插入一个新结点 (1)q指针指向新结点; (2)p=head,r指向p结点的前一个结点;
(3)若head==NULL,则链表为空,没有结点,q结点作为链表的第1个结点插入: head=q; q->next=NULL; (4)r=p,p=p->next,p、r结点往前移动1个结点; (5)若p->score!=x且p!=NULL,则重复(4); (6)若p==NULL,则未找到score为x的结点,将q结点插入到链表尾r结点之后: r->next=q,q->next=NULL; (7)否则,将q结点插入到第n个结点之后,即插入到r结点与p结点之间: q->next=p->next,p->next=q; (8)返回链表头指针head。

105 【例】写一个函数,在链表中指定位置插入一个结点。在一个链表的指定位置插入结点, 要求链表本身必须是已按某种规律排好序的。例如,在学生数据链表中, 要求学号顺序插入一个结点。设被插结点的指针为pi。 可在以下不同情况下插入。 1. 原表是空表,只需使head指向被插结点即可。 2. 被插结点值最小,应插入第一结点之前。这种情况下使head指向被插结点,被插结点的指针域指向原来的第一结点则可。即: pi->next=pb; head=pi;

106 3. 在其它位置插入。这种情况下,使插入位置的前一结点的指针域指向被插结点,使被插结点的指针域指向插入位置的后一结点。即为:
pi->next=pb; pf->next=pi; 4. 在表末插入。这种情况下使原表末结点指针域指向被插结点,被插结点指针域置为NULL。即: pb->next=pi; pi->next=NULL; 函数如下:

107 TYPE * insert(TYPE * head,TYPE *pi)
{ TYPE *pf,*pb; pb=head; if(head==NULL) /*空表插入*/ (head=pi; pi->next=NULL;} else while((pi->num>pb->num) &&(pb->next!=NULL)) {pf=pb; pb=pb->next; } /*找插入位置*/ if(pi->num<=pb->num) {if(head==pb)head=pi; /*在第一结点之前插入*/ else pf->next=pi; /*在其它位置插入*/ pi->next=pb; } else {pb->next=pi; pi->next=NULL;} /*在表末插入*/ } return head;}

108 本函数有两个形参均为指针变量,head指向链表,pi 指向被插结点。函数中首先判断链表是否为空,为空则使head指向被插结点。表若不空,则用while语句循环查找插入位置。找到之后再判断是否在第一结点之前插入,若是则使head 指向被插结点被插结点指针域指向原第一结点,否则在其它位置插入, 若插入的结点大于表中所有结点,则在表末插入。本函数返回一个指针, 是链表的头指针。当插入的位置在第一个结点之前时, 插入的新结点成为链表的第一个结点,因此head的值也有了改变, 故需要把这个指针返回主调函数。

109 11.8 共用体 共用体的概念 当需要把不同类型的变量存放到同一段内存单元,或对同一段内存单元的数据按不同类型处理,则需要使用“共用体”数据结构。如: 将一个整型变量,一个字符型变量, 一个实型变量放在同一个地址开始的内存单元中. 由于三个变量在内存中占的字节数不同,故使用覆盖技术,使三个变量互相覆盖. 整型 字符型 实型 1000

110 在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入 该“联合”所定义的任何一种数据。
在实际问题中有很多这样的例子。 例如在学校的教师和 学生中填写以下表格: 姓名 年 龄 职业 单位 “职业”一项可分为“教师”和“学生”两类。 对“单位”一项学 生应填入班级编号,教师应填入某系某教研室。 班级可 用整型量表示,教研室只能用字符类型。 要求把这两种 类型不同的数据都填入“单位”这个变量中, 就必须把“单 位”定义为包含整型和字符型数组这两种类型的“联合”。

111 “联合”与“结构”有一些相似之处。但两者有本质上的不
同。在结构中各成员有各自的内存空间, 一个结构变 量的总长度是各成员长度之和。而在“联合”中,各成员 共享一段内存空间, 一个联合变量的长度等于各成员 中最长的长度。应该说明的是, 这里所谓的共享不是 指把多个成员同时装入一个联合变量内, 而是指该联 合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。如前面介绍的“单位”变量, 如 定义为一个可装入“班级”或“教研室”的联合后,就允许 赋予整型值(班级)或字符串(教研室)。要么赋予 整型值,要么赋予字符串,不能把两者同时赋予它。

112 11.8.2联合类型的定义和联合变量的说明 一个联合类型必须经过定义之后,才能说明变量. 定义一个联合类型的一般形式为: union 联合名
{ 成员表 }; 成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名; 2000 2001 2002 2003 ch i f

113 成员名的命名应符合标识符的规定。例如: union perdata { int class; char office[10]; }; 定义了一个名为perdata的联合类型,它含有两 个成员,一个为整型,成员名为class;另一个为 字符数组,数组名为office。联合定义之后,即可 进行联合变量说明,被说明为perdata类型的变量, 可以存放整型量class或存放字符数组office。

114 联合变量的说明 联合变量的说明和结构变量的说明方式相同, 也有三种形式: 先定义,再说明 定义同时说明 直接说明

115 (1)先定义类型,再定义变量,例如: union data /*共用体类型定义*/ { int i; char ch; float f; }; union data a,b,c;/*共用体类型变量定义*/

116 (2)定义类型时同时说明变量,例如: union data { int i; char ch; float f; }a,b,c; (3)不定义类型名而直接定义变量,例如: union

117 11.8.3 联合变量的赋值和使用 先定义后使用对共用体变量的引用,是通过其成员的引用来实现的。
a.i /*引用成员i,a.i就相当于一个整型变量*/ a.ch /*引用成员ch,a.ch就相当于一个字符变量*/ a.f /*引用成员f,a.f就相当于一个实型变量*/ 不能对共用体变量进行整体的输入输出,例如: printf(”%d”,a);

118 对联合变量的赋值,使用都只能是对变量的成员进行。
联合变量的成员表示为: 联合变量名.成员名 例如,a被说明为perdata类型的变量之后,可使用 a.class、 a.office 不允许只用联合变量名作赋值或其 它操作。也不允许对联合变量作初始化赋值,赋值只 能在程序中进行。还要再强调说明的是,一个联合变 量, 每次只能赋予一个成员 值。换句话说,一个联合变 量的值就是联合变员的某一 个成员值。 union perdata { int class; char officae[10]; };

119 【例】共用体变量引用。 #include <stdio.h> void main() {union{ char a; int b; long c; }uu; uu.a=’0x61’; printf(”\n1:a=%x,b=%x,c=%lx”,uu.a,uu.b,uu.c); uu.b=0x7656; printf(”\n2:a=%x,b=%x,c=%lx”,uu.a,uu.b,uu.c); uu.c=0x ; printf(”\n3:a=%x,b=%x,c=%lx”,uu.a,uu.b,uu.c); } 61 00 a b 56 76 78 34 12

120 { printf("input name,age,job and department\n");
【例】设有一个教师与学生通用的表格,教师数据有姓名,年龄, 职业,教研室四项。学生有姓名,年龄,职业,班级四项。编程 输入人员数据, 再以表格输出。 main() { struct char name[10]; int age; char job; union int class; char office[10]; } depa; }body[2]; int n,i; for(i=0;i<2;i++) { printf("input name,age,job and department\n"); scanf("%s %d %c",body[i].name, &body[i].age,&body[i].job); if(body[i].job=='s') scanf("%d",&body[i].depa.class); else scanf("%s",body[i].depa.office); } printf("name age job class/office");

121 for(i=0;i<2;i++) { if(body[i].job=='s') printf("%s\t%3d %3c %d\n", body[i].name, body[i].age,body[i].job,body[i].depa.class); else printf("%s\t%3d %3c %s\n",body[i].name, body[i].age,body[i].job,body[i].depa.office); }

122 本例程序用一个结构数组body来存放人员数据, 该结
构共有四个成员。其中成员项depa是一个联合类型, 这个联合又由两个成员组成,一个为整型量class,一 个为字符数组office。在程序的第一个for语句中,输入 人员的各项数据,先输入结构的前三个成员name,age 和job,然后判别job成员项,如为“s”则对联合 depa·class输入(对学生赋班级编号)否则对depa·office 输入(对教师赋教研组名)。在用scanf语句输入时要注 意,凡为数组类型的成员,无论是结构成员还是联合 成员,在该项前不能再加“&”运算符。如程序第 18 行 中body[i].name是一个数组类型,第22行中的 body[i].depa.office也是数组类型,因此在这两项之间 不能加“&”运算符。程序中的第二个for语句用于输出 各成员项的值:

123 共用体类型数据的特点: (1)同一内存段可以用来存放几种不同类型的成员,但每一瞬时只能存放其中一种。
(2)共用体变量中起作用的成员是最后一次存放的成员: a.i=1; a.c='a'; a.f=1.5; (3)共用体变量的地址和它的成员地址都是同一地址。 例如:&a,&a.i,&a.c,&a.f表示的是同一地址

124 例如 (4)不能对共用体变量名赋值, 如 a=1 不能引用变量名来得到成员值,如 m=a 不能初始化. 如:union {int i;
char ch; float f; } a={1,'a',1.5}; (5)不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以用指向共用体变量的指针 (6) 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组.反之依然. 例如

125 11.9枚举类型 如果一个变量只有几种可能的值,可以定义为枚举类型.变量的值只限于列举出来的值的范围内.
枚举是一个被命名的整型常数的集合.枚举在日常生活中很常见。 例如表示星期的SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, 就是一个枚举。

126 枚举的说明与结构和联合相似, 其形式为:           enum 枚举名{                标识符[=整型常数],                标识符[=整型常数],                ...                标识符[=整型常数],           } 枚举变量;

127 enum weekday {sun,mon,tue,wed,thu,fri,sat};
枚举类型名 枚举元素或 枚举常量 关键字 enum weekday {sun,mon,tue,wed,thu,fri,sat}; 然后,可以定义变量: enum weekday workday,week_end; 也可以直接定义枚举变量 enum {sun,mon,tue,wed,thu,fri,sat} workday,week_end;

128 如果枚举没有初始化, 即省掉“=整型常数”时, 则从第一个标识符开始, 顺次赋给标识符0, 1, 2,
如果枚举没有初始化, 即省掉“=整型常数”时, 则从第一个标识符开始,  顺次赋给标识符0, 1, 2, ...。但当枚举中的某个成员赋值后, 其后的成员按依次加1的规则确定其值。     例如:下列枚举说明后, x1, x2, x3, x4的值分别为0, 1, 2, 3。      enum string{x1, x2, x3, x4}x;

129 当定义改变成:       enum string       {           x1,           x2=0,           x3=50,           x4,       }x;     则: x1=0, x2=0, x3=50, x4=51

130 注意: 1. 枚举中每个成员(标识符)结束符是",",  不是";", 最后一个成员可省略 ","。
2. 初始化时可以赋负数, 以后的标识符仍依次加1。 3. 枚举变量只能取枚举说明结构中的某个标识符常量。     例如:       enum string      {            x1=5, x2, x3, x4,      };      enum string x=x3;     此时, 枚举变量x实际上是7。

131 说明: 例如 在C编译中,对枚举元素按常量处理,不能赋值.
枚举元素作为常量,标识符并不代表什麽含义.但它们是有值的,C语言编译按顺序为:0,1,2,3… …。 由上面定义,则sun值为0,mon的值为1,...sat值为6。 也可以改变枚举元素的值,如: enum weekday {sun=7,mon=1,tue,wed,thu,fri,sat} day; 枚举值可以用来作判断比较 一个整数不能直接赋给一个枚举变量.如: day=2;错误,因它们不属于同一类型.应先进行强制类型转换,如: workday=(enum weekday)2; 此相当于将序号为2的枚举元素赋给workday.表达式也可以. 例如

132 显示扑克牌 #include <conio.h> #include <stdio.h>
enum suit {spade,heart,diamond,club}; int color[4]={DARKGRAY,LIGHTRED,LIGHTRED, DARKGRAY}; char symb[4]={0x06,0x03,0x04,0x05}; void showcards(int line,int suit,char *faces) { gotoxy(8,line); textcolor(color[suit]); putch(symb[suit]); cputs(" "); cputs(faces); }

133 main() { textmode(C40); textcolor(YELLOW); textbackground(CYAN); window(6,6,35,20); clrscr(); gotoxy(1,1); cputs(" A Head of cards in card game"); showcards(6, spade, " A Q "); showcards(8, heart, " 7 Q"); showcards(10, diamond, " K L 3"); showcards(12, club, " L Q K A 5"); getch(); }

134 11.10 用typedef 定义类型 声明结构体类型:
除了可以使用C提供的标准类型名和、共用体、 指针、枚举类型外,还可以 typedef 定义新的类 型名来代替已有的类型名。 声明结构体类型: typedef struct {int month; int day; int year; }DATE; 可以定义变量: DATE birthday; DATE *p; typedef int INTEGER; typedef float REAL; int i,j; float a;b;等价于 INTEGER i,j; REAL a,b; 不要写成struct DATE birthday

135 用typedef定义了一个新的类型名,但它并没有创建类型。
它只是对已存在的类型创建了一个新的称呼,即新的类型名。 创建这个新的类型名可能是为了使类型名的引用更简单、方便(如对结构体、共用体等创建新的类型),也可能是为了使类型名的引用更符合某些人的习惯和喜好 在程序中,用户可以同时使用原有的类型名和新创建的类型名来定义变量。

136 如:typedef int NUM[100]; NUM n;
归纳定义一个 新的类型名的方法: ①先按定义变量的方法写出定义体(int i;) ②将变量名换成新类型名(将i换成COUNT) ③在最前面加typedef(typedef int COUNT) ④然后用新类型名去定义变量 typedef int count; COUNT i; 如:typedef int NUM[100]; NUM n; 定义NUM为整型数组类型 定义n为整型数组变量 如:typedef char *STRING; STRING p ,s[10] ; 定义STRING为字符指针类型 P为字符指针变量,s为指针数组 定义POINTER为指向返回整型值的函数的指针类型 如:typedef int (*POINTER)(); POINTER p1 ,p2 ; P1,p2为POINTER型指针变量

137 typedef long integer 对4字节的机器 typedef int integer; 对2字节的机器
说明: 用typedef可以定义各种类型名,但不能用来定义变量用typedef只是对已经存在的类型增加一个类型名,而没有创造出新类型 typedef与#define有相似之处.但事实上二者不同,预编译只作简单的字符替换,typedef是在编译时处理的,它如同定义变量那样来定义一个类型 typedef有利于程序的移植. 比如,我们要使用一类4字节的变量。 typedef long integer 对4字节的机器 typedef int integer; 对2字节的机器

138 11.12 位段结构 位段(又称为位域)是一种特殊的结构体成员或共用体成员(它只能用在结构体或共用体中),它通过指定成员在内存存储时占用的位数(二进制位),使数据在内存的存储更为紧凑。 位段结构定义的一般形式为: struct/union 位段名 { unsigned <成员1>:<二进制位数>; unsigned <成员2>:<二进制位数>; unsigned <成员n>:<二进制位数>; };

139 struct data_struct {unsigned int f1:1; unsigned int f2:1; unsigned int f3:1; unsigned int opcode1:3; unsigned int opcode2:4; unsigned int opcode3:6; }; data_struct为结构体名,它有6个成员,如第一个成员f1是一个无符号的整数,其后的“:1”表明该成员占据1“位”的存储空间。 这种方法的好处在于对压缩的数据值存取可采用结构成员方法,这样做起来方便。

140 对位段的操作与对相应的结构体的操作相似。
例如: struct data_struct data; /*定义一个位段结构变量data*/ data.opcode1=n; /*给data的opcode1赋值为n*/ m=data.opcode1; /*从data中提取opcode1域的值,并赋给变量m*/

141 请同学们注意结构体与共用体的区别 同时注意二者的定义与使用
第11章结束 请同学们注意结构体与共用体的区别 同时注意二者的定义与使用

142 struct student student1, student2
先声明结构体类型在定义变量名 如上面已定义的结构类型struct student,可以用它来定义变量: 变量名 struct student student1, student2 注意: 定义标准类型变量和定义结构体类型变量不同 前者类型是已知的(如int,float),而后者要先定义类型后 定义变量 人们通常用一个符号常量代表一个结构体类型 #define STUDENT struct student

143 在声明类型的同时定义变量 struct student {int num; char name[20]; char sex; int age; float score; char addr[30]; }student1,student2; 变量名列表

144 定义的一般形式为: struct 结构体名 { 成员表列 } 变量名表列

145 直接定义结构体类型变量 struct {int num; char name[20]; char sex; int age; float score; char addr[30]; } student1,student2;

146 (1)不能将结构体变量作为整体进行输入输出。
printf(“%d,%s,%c,%d,%f,%s\n”,student1); 引用方式:结构体变量名.成员名。如: student1.num 结构体变量名 成 员 运 算 符 成员名

147 正确引用: student1.num student1.birthday.day
如果成员本身又是一个结构体类型,则要用成员运算符一级一级地找到最低一级的成员 正确引用: student1.num student1.birthday.day struct student {int num; char name[20]; char sex; int age; struct date birthday; char addr[30]; }student1,student2; struct date {int month; int day; int year; };

148 student2. score=student1. score; sum=student1. score+student2
student2.score=student1.score; sum=student1.score+student2.score; student1.age+ +; + +student2.birthday.year; “.”的优先级最高

149 可以: scanf(“%d”,&student1.num); printf(“%o”,&student2);
不可以: scanf(“%d%s%c%d%f%s”,&student1);

150 struct student {long int num; char name[20]; char sex; char addr[30]; }stu[3]={{89031,”Li Lin”,’M’,”123 Beijing Road”},{10102,”Zhangyi”,…}, {10103,…}};

151 11.6.3 用结构体变量和指向结构体的指针作为函数参数
有3种方法: 1.用结构体变量成员作参数,如: 主函数中: ... print(stu.name); ... 被调函数中: void print(char a[ ] ) {…} 2.用结构体变量作实参,如: 主函数中: ... print(stu); ... 被调函数中: void print(a) struct student a; {…}

152 3. 用指向结构体变量(或数组)的指针作实参,将结构体(或数组)的地址传给形参.
3. 用指向结构体变量(或数组)的指针作实参,将结构体(或数组)的地址传给形参. 主函数中: ... print(&stu); ... 被调函数中: void print(a) struct student *a; {…}

153 例11.5 用结构体变量名作参数 #include <string.h>
#define FORMAT "%d\n%s\n%f\n%f%f\n" struct student {int num; char name[20]; float score[3]; }; main() {void print (struct student); struct student stu; stu.num = 12345; strcpy(stu.name,"Li Li");

154 stu.score[0]=67.5; stu.score[1]=89; stu.score[2]=78.9; print (stu); } void print(struct student stu) {printf (FORMAT, stu.num, stu.name, stu.score[0], stu.score[1], stu.score[2]); printf ("\n");

155 例11.6 改用指向结构体变量的指针作参数 #include <string.h>
#define FORMAT "%d\n%s\n%f\n%f%f\n" struct student {int num; char name[20]; float score[3]; }stu={12345,"Li Li", 67.5,89,78.6}; main() {void print (struct student *); print (&stu); }

156 void print (struct student *p)
{ printf (FORMAT, p->num,p->name, p->score[0], p->score[1], p->score[2]); printf("\n"); }

157 把一个完整的结构体变量作为参数传递,虽然合法,但既费时又费空间.不如用指针效率高.

158 C 语言程序设计 本章结束

159 例.11.22设有若干个人员的数据,其中有教师和学生
Name num sex job class/position Li f s Wang m t Prof. Name num sex job class/position 循环n 次 读入姓名、号码、性别、职业 Job==s 读入class Job=='t' 读入 position 输出 “输入错” 循环n次 Job='s' 输出:姓名、号码 性别、职业、班级 输出:姓名、号码 性别、职业、职务

160 struct {int num; char name[10]; char sex; char job; union {int class; char position[10]; } category; } person[2]; main() { int n,i; for (i=0;i<2;i++) { scanf ("%d %s %c %c", &person[i].num, person[i].name, &person[i].sex, &person[i].job); if(person[i].job=='s') scanf ("%d",&person[i].category.position); else if(person[i].job=='t') scanf ("%s",&person[i].category.position); else printf("input error"); } printf ("\n");

161 printf("no name sex job class/position\n");
for (i=0;i<2;i++) {if(person[i].job= ='s') printf( "%-6d %-10s %-3c %-3c %-6d\n", person[i].num, person[i].name, person[i].sex, person[i].job, person[i].category.class); else printf( "%-6d %-10s %-3c %-3c %-6s\n", person[i].num, person[i].job, person[i].category.position); }

162 例11.13口袋中有红、黄、蓝、白、黑5种颜色的球若干个。每次从口袋中取出3个球,问得到3种不同色的球的的可能取法,打印出每种组合的3种颜色。

163 case 1: pri=j; break; case 1: pri=k; break; default: break; } switch (pri) { case red: printf("%-10s","red"); break; case yellow: printf("%-10s","yellow"); break; case blue: printf("%-10s","blue"); break; case white: printf("%-10s","white"); break; case black: printf("%-10s","black"); break; default :break; main() enum color {red,yellow,blue, white,black}; enum color i,j,k,pri; int n,loop; n=0; for (i=red;j<=black;i++) for(j=red;j<=black;j++) if(i!=j) { for(k=red;k<=black;k--) if((k!=i) &&(k!=j) {n=n+1; printf("%-4d",n); for(loop=1;loop<=3;loop++) {switch(loop) { case 1: pri=I;break;

164 运行结果如下: 1 red yellow blue 2 red yellow white 3 red yellow black ……. …………….. 58 Black white red 59 black white yellow 60 black white blue total 60 } printf ("\n"); printf ("\ntotal:%5d\n",n);


Download ppt "第11章 结构体与共用体 结构体 共用体 枚举类型 用typedef定义类型 Tc工具. 江南大学控制科学与工程研究中心"

Similar presentations


Ads by Google