第11章 结构体和共用体
本 章 内 容 概述 定义结构体类型变量的方法 结构体变量的引用 结构体变量的引用和初始化 结构体数组 指向结构体类型数据的指针 用指针处理链表 共用体 枚举类型 用typedef定义类型
11.1 概述 定义 一个组合项中包含若干个类型不同(或相同)的数据项,这样的数据结构称为结构体。 例如: num name sex age addr struct student { int num; char name[20]; char sex; int age; char addr[30]; }; 声明结构体类型的一般形式 struct 结构体名 {成员表列}; "成员表列"称为"域表"。 每个成员称为结构体中的一个域,对各成员(域)的定义形式如下: 类型名 成员名;
11.2 定义结构体类型变量的方法 三种方法: 先声明结构体类型,再定义变量名。 在定义类型的同时定义变量。 直接定义结构类型变量。 方法一:假设有如下定义: struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }; struct student stud1, stud2;
11.2 定义结构体类型变量的方法 方法二: struct 结构体名 { 成员表列 }变量名表列; 方法三: struct } 变量名表列; 对结构体类型的几点说明 类型与变量是不同的概念。在编译时,对类型是不分配空间的,只对变量分配空间。 结构体中的成员可以单独引用。 成员也可以是一个结构体变量。(P283) 成员名可以与程序中的变量名相同。
11.3 结构体变量的引用 四项原则 不能整体输入输出,只能对各个成员分别引用。 结构体变量名.成员名 (成员运算符".") 结构体变量名.成员名 (成员运算符".") 如果成员本身又属于一个结构体类型,则用多个成员运算符,一级一级地找到最低一级的成员,只能对最低一级的成员进行赋值、存取及运算。 例如: struct date { int month; int day; int year; }; struct student { char name[20]; char sex; int age; struct date birthday; }; stu1, stu2; stu1.birthday.month = 1;
11.3 结构体变量的引用 对成员变量可以像普通变量一样进行各种运算。 例如: stu1.num ++; 可以引用成员的地址,也可以应用结构体变量的地址。 scanf("%d", &stu1.num); printf("%o", &stu1); 但不能用以下语句整体读入结构体变量, scanf(“%s, %c, %d, ”, &stu1); //错误
11.4 结构体变量的初始化 结构体变量和其它类型变量一样,可以在定义时指定初始值。 例11.1:对结构体变量的初始化。 #include <stdio.h> void main() { struct student char name[20]; char sex; int age; } a = {"Li Lin", 'M', 23}; printf("name: %s\n sex:%c\n age:%d\n",a.name, a.sex, a.gae); }
11.5 结构体数组 定义结构体数组 只要说明该结构体变量为数组类型,则每个数组元素都是一个结构体类型数据。 例如: struct student { char name[20]; char sex; int age; }; /* stu[3] */ struct student stu[3];
11.5 结构体数组 结构体数组的初始化 一般形式: 在定义数组的后面加上: ={初值表列}; struct student { 定义数组时,元素个数可以不指定,编译时系统会根据给出初值的结构体常量的个数来确定数组元素的个数。 struct student { char name[20]; char sex; int age; } stu[3] = {{"Li Lin", 'M', 23}, {"Wang nan", 'F', 21}, {"Liu Li", 'F', 22} };
11.5 结构体数组 结构体数组应用举例 例11.2:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果。(P287) struct person { char name[20]; int count; }leader[3] = {"Li", 0, "Zhang", 0, "Fun", 0};
11.5 结构体数组 void main() { int i, j; char leader_name[20]; for(i=1; i<=10; i++) scanf("%s", leader_name); for(j=0; j<3; j++) if(!strcmp(leader_name,leader[j].name)) leader[j].count++; } printf("\n"); for(i=0; i<3; i++) printf("%s: %d\n", leader[i].name, leader[i].count);
11.6 指向结构体类型数据的指针 指向结构体变量的指针 一个结构体变量的指针就是该变量所占据的内存段的起始地址。 例11.3:指向结构体变量的指针的应用。(P289) struct student stu_1; struct student *p=&stu_1; printf("%f", (*p).score); 指向运算符 " ->" printf("%f", p->score); 引用结构体成员的三种方法 结构体变量名.成员名 (*指针变量名).成员名 指针变量名->成员名
11.6 指向结构体类型数据的指针 指向结构体数组的指针 例11.4:指向结构体数组的指针的应用。(P290) … struct student stu[3] = {{101, "Li", 'M', 18}, {102, "Liu",'M', 19}, {103, "Wu",'F', 20}}; void main() { struct student *p for(p=stu; p<stu+3; p++) printf("%5d%-20s %2c %4d\n",p->num,p->name,p->sex,p->age); }
11.6 指向结构体类型数据的指针 p 101 stu[0] Li 18 102 Liu stu[1] M 19 103 Wu F 20 F Wu 103 19 M Liu 102 18 Li 101 stu[0] stu[1] stu[2] p
11.7 用指针处理链表 用指向结构体的指针作函数的参数 将一个结构体变量的值传递给另一个函数的方法有三种: 用结构体变量的成员作参数,同普通变量。 用结构体变量作实参,形参也必须是同类型的结构体变量,顺序传递全部内容。 用指向结构体变量(或数组)的指针作实参,传递结构体变量(或数组)的地址给形参。 例11.5 例11.6
11.7 用指针处理链表 链表的基本概念 链表是一种常见的重要的数据结构,它动态地进行存储分配。 存储特点:用一组任意的存储单元存储一组数据元素。(这组存储单元可以是连续的,也可以是不连续的。) 结点:链表中的每一个元素称为"结点",包括两个域: 数据域:存储数据元素。 指针域:存储下一结点的地址。(直接后继存储位置。) 头指针:指示链表中第一个结点的存储位置。 链表中最后一个元素的指针域为"空"(NULL)。 一个指针类型的成员既可以指向基他类型的结构体数据,也可以指向自己所在的结构体类型的数据。 1249 head A 1356 B 1475 C 1021 D NULL
11.7 用指针处理链表 简单链表 例11.7:建立一个简单链表,它由3个学生数据的结点组成。输出各结点中的数据。 #include <stdio.h> #define NULL 0 struct student { long num; float score; struct student *next; };
11.7 用指针处理链表 void main() { struct student a, b, c, *head, *p; a.num = 10101; a.score = 89.5; b.num = 10103; b.score = 90; c.num = 10107; c.score = 85; head = &a; a.next = &b; b.next = &c; c.next = NULL; p = head; while(p!=NULL) printf("%ld %5.1f\n", p->num, p->score); p = p->next; }
11.7 用指针处理链表 处理动态链表所需的函数 malloc函数 函数原型:void *malloc(unsigned int size); 功能:在内存的动态存储区中分配一个长度为size的连续空间。 函数返回值:是一个指向分配域起始地址的指针(类型为void)。 若函数执行失败,则返回NULL。 calloc函数 函数原型: void *calloc(unsigned int n, unsigned int size); 功能:在内存的动态存储区中分配n个长度为size的连续空间。 为一维数组分配动态存储空间。 free函数 函数原型:void free(void *p); 功能:释放由p指向的内存区,使这部分内存区能被其他变量使用。
11.7 用指针处理链表 建立动态链表 建立动态链表:指在程序执行过程中从无到有建立起一个链表。包括:开辟结点,输入结点数据,勾链。 例11.8:写一函数建立一个有3名学生数据的单向动态链表。P297
11.7 用指针处理链表 输出链表 思路: 已知链表第一个结点地址(head)。 设指针变量p,先指向第一个结点,输出所指结点。 例11.9:编写一个输出链表的函数print。P300 对链表的删除操作 A B C D E A B C D E
11.7 用指针处理链表 例11.10:写一函数以删除动态链表中指定的结点。(P301) 思路: 链表为空。 链表不为空。 查找要删除结点。 要删的是第一个结点。 要删的不是第一个结点。 脱链。 对链表的插入操作 对链表的插入是指将一个结点插入到一个已有的链表中。 问题: 怎样找到插入的位置; 怎样实现不同位置的插入。 插入过程:P304 图11-22 例11.11 插入结点的函数insert
11.7 用指针处理链表 对链表的综合操作 将对链表进行建立、输出、删除、插入的函数组织在一个C程序中。 只删除一个结点,插入一个结点。(P306) 删除多个结点,插入多个结点。(P307)
11.8 共用体 共用体的概念 使几个不同类型的变量共占同一段内存的结构称"共用体"类型的结构。 定义形式 union 共用体名 { 成员表列 }变量表列; 例如: union data int i; cha ch; float f; }; union data a, b, c; 共用体变量的引用方式 只能引用共用体变量的成员,不能引用共用体变量。 f ch i 1000
11.8 共用体 共用体类型数据的特点 某一瞬间只有一个成员起作用。 a.i = 1; 起作用的是最后一次存放的成员。 a.c = 'a'; 共用体变量的地址及各成员的地址为同一地址。 不能对共用体变量名赋值。如:a = 1; 不能企图引用变量名来得到一个值。如:m = a; 不能在定义时赋初值。如: 共用体变量不能作函数的参数,不能使函数带回共用体变量,但可以使用指向共用体变量的指针。 共用体类型可出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。 例11.12:(P311) a.i = 1; a.c = 'a'; a.f = 1.5; union { int i; cha ch; float f; } a = {1, 'a', 1.5};
11.9 枚举类型 枚举 枚举指将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。 如果一个变量只有几种可能的值,可以定义为枚举类型。 枚举类型的声明 enum 变量名{value1, value2, …}; 例如: enum weekday{sun, mon, tue, wed, thu, fri, sat}; 声明变量: enum weekday workday, week-end; 或者: enum weekday{sun, mon, tue, wed, thu, fri, sat} workday, week-end;
11.9 枚举类型 说明 在C编译中,对枚举元素按常量处理,称为枚举常量。不能对其赋值,如:sun = 0; mon = 1; enum weekday workday; workday = mon; printf("%d", workday); /* 输出1 */ 也可以在定义时改变枚举元素的值,例如: enum weekday{sun=7, mon=1, tue, wed, thu, fri, sat} workday, week_end; 枚举值可用来作判断比较。例如: if(workday == mon) … 一个整数不能直接赋给一个枚举变量。例如: workday = 2; //error workday = (enum weekday)2; 相当于:wrokday = tue; 可以是表达式,例如:Workday = (enum weekday)(5-3); 例 11.13:(P313)
11.10 用typedef定义类型 用typedef声明新的类型名以代替已有的类型名。 例如: typedef int INTEGER; typedef float REAL; 则以下两行等价: int i, j; float a, b; INTEGER i, j; REAL a, b; 声明新类型名的方法是 写出定义体:int i; 将变量名转换为新类型名:int COUNT 在前面加typedef:typedef int COUNT 用新类型名定义变量 用typedef定义数组类型和结构体类型的例子。(见P316)
11.10 用typedef定义类型 说明 习惯上把用typedef定义的类型名用大写字母表示。 只能定义类型名,不能定义变量。 与#define有所不同。 例如:typedef int COUNT; #define COUNT int; 语句作用:用COUNT代表int。 区别 #define是在预编译时处理,作简单字符替换。 typedef是在编译时处理,不是作简单字符替换。 常将用typedef声明的一些数据类型放在头文件中,在所需文件中用#include命令进行包含。 使用typedef有利于程序的通用和移植。
作 业 P318 11.1 11.2 11.6 11.7 11.11