C语言学习第十章结构和杂类.ppt
第十章结构体和杂类,2,本章教学内容、要求,内容、结构体类型的定义,结构体变量的定义、引用、初始化及结构体数组;2、指向结构体类型数据的指针及用指针处理链表;3、共用体的概述;4、枚举类型的说明;5、用 typedef 定义类型。要求、了解结构体类型变量及结构体数组;2、掌握结构指针;3、掌握用指针处理链表;4、了解共用体的概述和枚举类型的说明;5、简单了解用 typedef 定义类型。,3,重点与难点,重点1.结构体及结构体数组的引用;2.用指针处理链表。难点用指针处理链表,4,10.1结构,在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符型;学号可为整型或字符型;年龄应为整型;性别应为字符型;成绩可为整型或实型。显然不能用一个数组来存放这一组数据。如:语言中提供了另一种构造数据类型“结构体”。它相当于其它高级语言中的记录。“结构体”是一种构造类型,它是由若干“成员”组成的。,5,10.1结构,一、定义一个结构体的一般形式为:struct 结构名 成员表列;二、成员类型说明形式为:类型说明符 成员名;因此若有n个成员,其结构体定义形式为:struct 结构名 类型说明符1 成员名1;类型说明符2 成员名2;.类型 说明符n 成员名n;;,例如:struct student int num;char name20;char sex;int age;float score;char addr30;注意在括号后的分号是不可少的。,6,定义结构体类型变量的三种方法,一、先定义结构体类型,再定义变量名:定义结构体类型的一般形式:struct 结构体名 成员表列;定义结构体变量的一般形式:struct 结构体名 变量名;二、定义结构类型的同时定义结构变量:.定义的一般形式:struct 结构体名 成员表列 变量名表列;三、直接定义结构类型变量 定义的一般形式:struct 成员表列 变量名表列;,7,定义结构体类型变量的三种方法,一、先定义结构体类型,再定义变量名:如:struct student int num;char name20;char sex;int age;float score;char addr30;struct student s1,s2;/*变量定义*/,8,定义结构体类型变量的三种方法,二、定义结构类型的同时定义结构变量:.如:struct student int num;char name20;char sex;int age;float score;char addr30;s1,s2;,9,定义结构体类型变量的三种方法,三、直接定义结构类型变量:如:struct int num;char name20;char sex;int age;float score;char addr30;s1,s2;,10,定义结构体类型变量的三种方法,四、关于结构体类型的说明:1、类型与变量是不同的概念2、结构体成员可象变量一样单独使用3、成员还可以是结构体变量如:struct date int month;int day;int year;4、成员名可以与变量同名,struct student int num;char name20;char sex;int age;struct date birthday;char addr30;s1,s2;,11,结构体类型变量的引用,只有在对结构体变量赋值或作为参数传递给函数等特殊情况下可以直接对一个结构体变量整体操作。其他情况只能对结构体变量的各个成员分别引用。一、结构体变量成员引用的一般形式:结构体变量名.成员名 如:s1.num 成员变量可以进行各种运算(如普通变量一样),如:+s1.num;二、结构体嵌套引用:.如:s1.birthday.day三、同类型的结构体变量间可相互赋值 如:s1=s2四、可引用结构体变量或成员的地址 如:scanf(%d,/*输出s1的首地址*/结构体变量的地址主要用于函数参数,传递结构体的地址,成员运算符,12,结构体变量的初始化,外部、静态和自动结构体变量可以初始化如:struct student long int num;char name20;char sex;char addr20;s1=99001,Li Lin,M,Bejing;,13,例如,下面的程序说明了访问结构成员的方法:结构成员变量的引用:.如:stu.student_num=990505;strcpy(stu.name,”Li”);/*注意:不能用赋值语句*/stu.sex=M;stu.age=18;stu.score=85.7;因为我们用结构来实现的目的,就是要区分一个数据聚集中的每个分量的类型和意义,每个分量数据类型可以相异,更重要的是,为了区分数据内容的意义。结构的成员有自己单独的名字,也就能区分每个成员各自的意义。,14,结构体变量的初始化,例10-1 输入某班32名学生的学号和考试成绩,求成绩最好的学生的学号和成绩及总成绩。分析:定义结构体,包含两个成员:学号和成绩。,main()struct struct_name int num;float score;student,max;int i;float sum;max.score=0;sum=0;,定义结构体类型struct_name,结构体变量student和max,用来存放成绩最好的那个学生的信息。,i 用来控制循环 sum用来存放总成绩,变量初始化,15,在数组中,数组是不能彼此赋值的。例如,下面数组的赋值语句会导致一个编译错误:void main()char a10,b10;a=b;/error/.这主要是因为数组名是一个常量指针,不允许被赋值。数组是一个数据类型的聚集,它本质上不是数据类型,定义的每个数组都认为是不同类型的,即使数组元素个数相同,如a与b。因而无法看作是同类型数据之间的赋值。结构就不同了,它大小固定,可以被赋值。,16,例【10-1】:本程序 的输出结果是什么?#include struct Personchar name20;unsigned long id;float salary;Person pr1=Frank Voltaire,12345678,3.35;void main()Person pr2;pr2=pr1;/结构变量的赋值printf(%s%ld%f,pr2.name,pr2.id,pr2.salary);,程序中定义了一个全局Person结构变量prl,它使用与初始化数组相似的方法进行初始化。在main()函数中,定义了一个结构变量pr2,然后使用赋值运算符将prl的内容赋值给pr2。在结构Person中,成员name是一个字符数组,通过结构变量的赋值,该数组作为成员也被赋值了。,17,10.1.3结构体数组,一、结构体数组的定义如:struct student long int num;char name20;char sex;int age;float score;char addr30;struct student stu3;以上定义了一个数组 stu,数组有3个元素,元素类型为struct student。,18,结构体数组,二、结构体数组的初始化如:struct student long int num;char name20;char sex;int age;float score;char addr30;stu3=101,Li ping,M,45,80,Beijing,102,Zhang ping,M,30,62.5,Shanghai,103,He fang,F,22,92.5,Tianjin;,19,例【10-2】下面的程序中,对一个Person结构数组进行“冒泡法”排序,分数高的排在后面:#include struct Person char name20;unsigned long id;float score;Person allone6=jone,12345,339.0,david,13916,449.0,marit,27519,311.0,jasen,42876,623.0,peter,23987,400.0,yoke,12335,511.0;void main()Person temp;for(int i=1;i allonej+1.score)/比较分数 temp=allonej;/结构变量的交换 allonej=allonej+1;allonej+1=temp;for(int k=0;k6;k+)/输出printf(%s,allonek.name);printf(%ld,allonek.id);printf(%f,allonek.score);,20,10.1.4 结构与函数,结构变量可以作为一个整体被复制、赋值、传递给函有函数以及由函数返回。但不能将结构变量作为一个整体进行输入与输出。【例10-3】打印某一学生的成绩。将结构变量整体作为函数参数。#include stdio.h struct studentinfo long student_num;char name10;char sex;int age;float score;char addr30;void print_score(struct studentinfo st)printf(%5.2f,st.score);main()struct studentinfo stu=990505,Li,M,18,85.7,Rm303 Bldg4;print_score(stu);,21,10.2指针在结构中的应用,一、指向结构体变量的指针 一个结构体变量的指针就是该变量所占据的内存段的首地址。指向结构体指针的定义,与结构体变量的定义完全类似。如:struct struct_name char name10;int num;float score;struc struct_name student,*p;,22,10.2指针在结构中的应用,赋值语句 p=,指针变量p指向结构体变量student,引用结构体中的成员变量:(*p).成员名 或 p-成员名,相当于,student.成员名,student,23,说明:和成员运算符一样,“-”为指向运算符,是运算优先级最高的运算符。由于成员运算符“.”的运算优先级高于运算符“*”,因此(*p).成员名中()不能少。,*p.成员名,p=,不能用指向某个结构体变量的指针指向该结构体变量的某个成员。,10.2指针在结构中的应用,24,例10-4#include string.hmain()struct student long int num;char name20;char sex;float score;struct student stu_1,*p;p=,P,stu_1,占4字节,占4+20+1+4=29字节,10.2指针在结构中的应用,25,二、指向结构体数组的指针 例10-5 struct student long int num;char name20;char sex;int age;struct student stu3=10101,LiLin,M,18,10102,ZhangFun,M,19,10104,Wang Min,F,20;main()struct student*p;printf(No.Name sex agen);for(p=stu;p num,p-name,p-sex,p-age);,10.2指针在结构中的应用,26,P=stu;指向stu0,判断P stu+3;,再打印 stu0;,P+;指向stu1,判断 P stu+3;,P+;指向stu2,P+;指向stu3,不存在,P,P,P,输出标题;,P,执行过程:,10.2指针在结构中的应用,27,注意以下两点:.若 p 的初值为 stu,即指向第一个元素,则 p+1指 向 下一个元素的起始地址.如:(+p)-num:先使 p自加 1,然后得到它指向的 元 素中的 num 成员值(即10102)。(p+)-num:先得到 p-num 的值(即10101),然后使 p自加 1,指向 stu1。.指针 p只能指向一个结构体型数据(即 stu 数组的一个元素的起始地址),而不能指向一元素中的某一成员(即 p 的地址不能是成员的地址)。,注意:以下赋值语句都是错误的:,p=,(不能指向数组元素的成员变量),p=,(数组名本身就代表该数组的首地址,因此不能使用地址运算符&),10.2指针在结构中的应用,28,三、用指向结构体的指针作函数参数(不讲述)1、用结构体变量的成员作参数(值传递)2、用指向结构体变量(或数组)的指针作实参(地址传递)例10-6有一个结构体变量stu内含学生学号、姓名和三门课的成绩。要求在main函数中赋值,在另一函数print 中将它们输出。,10.2指针在结构中的应用,29,#include string.h#define format%dn%sn%f n%f n%f n struct student int num;char name20;float score3;main()void print();struct student stu;stu.num=12345;strcpy(stu.name,Li Lin);stu.score0=67.5;stu.score1=89;stu.score2=78.6;print(,void print(struct studnt*p)printf(format,p-num,p-name,p-score0,p-score1,p-score2);printf(n);,P,stu,10.2指针在结构中的应用,30,其中 main 函数中最后一行调用 print 函数,也可改用print(stu);即实参改用结构体变量(而不是指针)。即void print(struct studnt*p)改为:void print(struct studnt stud)同时 print 函数也应相应改为:printf(format,stud.num,stud.name,stud.score0,stud.score1,stud.score2);printf(n);,10.2指针在结构中的应用,31,用指针处理链表,一、链表概述1、链表结构是一种动态结构,而数组是静态结构;链表中相邻元素(结点)物理地址不一定连续,而数组中相邻元素的物理地址连续。2、动态分配、释放函数 stdlib.h void*malloc(int size)calloc(int n,int size)free(ptr)3、关于单向链表的常用术语(1)“头指针”变量 head 指向链表的起始结点;(2)结点 链表中的每一个元素;(3)“表尾”它的地址部分放一个“Null”(空地址);,1249,head,1249,1356,1475,1021,32,用指针处理链表,二、建立链表1、建立有5个学生数据的单向链表 开辟一个新结点,使 p1,p2指向它 读入一个学生的数据给 p1 所指的结点 当读入的 p1-num 不为 0 时,做:n=n+1 如果 n=1 则:head=p1/*把 p1所指的结点作为第一个结点*/否则:p2-next=p1/*把 p1所指的结点连接到表尾*/p2=p1/*p2移到表尾*/head=NULL,n=0,head,P1,P2,(n=1),33,用指针处理链表,再开辟一个新结点,使 p1 指向它 读入一个学生数据给 p1 所指结点 尾结点的指针变量 置 NULL算法的思路是:让 p1指向新开的结点,p2 指向链表中最后一个结点,把 p1 所指的结点连接在 p2 所指的结点后面,用p2-next=p1 来实现#define NULL 0#define LEN sizeof(struct student)struct student long num;float score;struct student*next;int n;,head,P2,P1,(n=2),34,struct student*creat()/*此creat函数带回一个链表起始 地址*/struct student*head,*p1,*p2;int n;n=0;p1=p2=(struct student*)malloc(LEN);/*开辟一个新单元*/scanf(%ld,%f,35,用指针处理链表,三、输出链表 p=head(使 p指向第一个结点),如果 p指向的不是尾结点,则:输出 p所指向的结点 然后使 p后移指向下一个结点,再输出 直到 p指向链表的尾结点为止void print(struct student*head)struct student*p;printf(nNow,These%d records are:n,n);p=head;if(head!=NULL)do printf(%ld%5.1fn,p-num,p-score);p=p-next;while(p!=NULL);,36,用指针处理链表,四、删除操作 删除操作包含两步:搜索:寻找符合条件的结点,并记下其前一结点位置;删除结点 处理方式:设两个指针变量p1为当前指针,p2为前一结点指针若链表为非空,则:p1=head当 num p1-num 且p1 指向结点不是尾结点,做:p2=p1(p2 后移一个位置)p1=p1-next(p1 后移一个位置)若 p1 是要删的结点,则:若 p1 所指是头结点,则head=p1-next(删除头结点)否则 p2-next=p1-next(删除一个结点),37,struct student*del(struct student*head,long num)struct student*p1,*p2;if(head=NULL)printf(nlist null!n);return NULL;p1=head;while(num!=p1-num,38,用指针处理链表,五、插入操作 将一个给定的数据插入到链表中,分三种情况:链表空;插入到头结点前;中间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;/*使 p0 指向的结点作为第一个结点*/else while(p0-num p1-num)/*p2 指向刚才 p1 指向的结点,p1 后移一个结点*/,39,用指针处理链表,if(p0-num num)if(head=p1)/*插到原来第一结点之前*/head=p0;p0-next=p1;else p2-next=p0;/*插入p2指向的结点之后*/p0-next=p1;else p1-next=p0;/*插到最后的结点之后*/p0-next=NULL;n+;/*结点数加1*/return(head);,40,一位运算,一、字节和位 一个字节8位二、原码例:+0 的原码为 00000000-0 的原码为 10000000三、反码一个数如果值为正,则它的反码与原码相同;如:+7 的反码为 00000111 一个数的值如为负,则符号位为1,其余各位是对原码取反;如:-7 的反码为 11111000四、补码规定:正数:其原码、反码、补码相同;负数:最高位为1,其余各位为原码的相应位取反,然后对整个数加 1;如:-7 的补码为 1111001,41,位 运 算 符,42,位 运 算 符,2、“按位或”运算符(|)两个相应位中只要有一个为 1,该位的结果值为 1。即:0|0=0;0|1=1;1|0=1;1|1=1;3、“异或”运算符(),也称 XOR 运算符 参加运算的两个相应位同号,则结果为 0(假);异号则为 1(真)。即:0 0=0;0 1=1;1 0=1;1 1=0;“异或”的意思是:判断两个相应的位值是否为“异”,为“异”(值不同)就取 1(真),否则为 0(假);4、“取反”运算符()是一个单目(元)运算符,用来对一个二进制数按位取反,即将 0 变 1,1 变 0。运算符的优先级别比 算术运算符、关系运算符、逻辑运算符 和其它位运算符都高。,43,位 运 算 符,5、左移运算符()如:a 2 表示将 a 的各二进位右移 2 位。移到右端的低位被舍弃,对无符号数,高位补 0。注:右移1 位相当于除以 2,右移 n 位相当于除以 2的 n 次方。7、位运算符与赋值运算符结合可以组成扩展的赋值运算符。如:例如:a&=b 相当于 a=a&b a=2 相当于 a=a2,44,位 运 算 举 例,例11-1取一个整数 a 从右端开始的 47 位分析:(1)先使 a 右移 4 位;(2)设置一个低4位全为1,其余全为0的数;(3)将上面二者进行,45,位 运 算 举 例,例11-2 循环右移 n 位。要求将 a 进行右循环移位,假设用两个字节存放一个整数。分析:(1)将 a 的右端 n 位先放到 b 中的高 n 位中;(2)将 a 右移 n 位,其左面高位 n 位补 0;(3)将 c 与 b 进行按位或运算;main()unsigned a,b,c;int n;scanf(a=%o,n=%d,46,2位 段,所谓位段,相当于结构体类型中的成员,但是定义位段的长度是以位为单位。它是一种特殊的结构体类型。一、位段的定义定义位段的一般形式为:struct 结构体类型标识符 unsigned 位段名1:长度1;unsigned 位段名2:长度2;.unsigned 位段名n:长度n;变量名表;,47,2位 段,二、说明:1、在定义中,每个位段(相当于成员)的长度是以位为单位,因此位段的类型为unsigned。2、一个位段必须存储在同一存储单元,不能跨两个单元。即一个位段的长度不能大于一个单元存放。3、当某个位段开始存放时,若相应的存储单元空间不够,则剩余空间不用,该位段将在下一个单元存放。例如,设某个系统存储单元长度为8(一个字节),且有如下的定义:struct unsigned a:4;unsigned b:2;unsigned c:6;x;,a,b,c,48,2位 段,4、可以定义无名位段。例如:struct unsigned a:4;unsigned:3;unsigned b:2;unsigned c:6;x;,此位段无名,该3位闲置不用。,49,2位 段,5、可以用长度为0的无名位段,使下一个位段从另一个存储单元开始存放。struct unsigned aa:4;unsigned:3;unsigned bb:2;unsigned:0;unsigned cc:6;y;6、注意,不能定义位段数组。,50,2位 段,三、位段的引用 变量名.位段名 可以用赋值语句赋值 例:x.a=4;y.cc=6;可以在表达式中引用,C语言将自动将其转换 为int类型 例:x.a+;y.cc=y.aa+1;可以用整型格式输出 例:printf(“a=%d b=%d c=%d”,x.a,x.b,x.c);,51,3共 用 体,一、共用体的定义 所谓“共用体”类型,是指使几个不同的变量共同占用同一段内存的数据类型。(1)直接定义共用体变量,其一般形式为:union 类型标识符1 成员名1;类型标识符2 成员名2;.类型标识符n 成员名n;变量名表;,(2)先定义共用体类型标识符,再定义变量。一般形式为:union 共用体类型标识符 类型标识符1 成员名1;类型标识符2 成员名2;.类型标识符n 成员名n;union 共用体类型标识符 变量名表;,52,3共 用体(联合体),二、共用体的引用 共用体变量成员名例如:定义了如下的共用体变量union var_type int i;float x;char c;comm;引用情况举例:comm.i=5;comm.x=5.5;comm.c=a;printf(“%c”,comm.c);,53,3共 用 体,二、共用体类型数据的特点 1、共用体变量占内存等于最长的成员的长度,即不同成员共用同一内存,例:变量comm的长度为;结构体变量占内存是各成员长度之和,即不同成员具有各自内存。、只有最后一个存放的成员的值有效,其他成员将失去原值。例如,comm.i=5;comm.x=5.5;comm.c=a;只有comm.c的值有效3、共用体变量的地址与各成员地址相同;4、不能对共用体变量名赋值,也不能引用变量名来得到成员的值,又不能在定义共用体时对它初始化;5、不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针;6、共用体类型可以出现在结构体类型定义中,也可以定义共用体数组;,54,4枚举类型,所谓“枚举”,是指将变量的所有取值一 一列举出来,该变量称之为枚举型变量。所列举的值叫做枚举元素(枚举常量)。一、类型定义 enum 类型名 枚举元素表(标识符);如: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;,55,4枚举类型,三、说明1、枚举元素是常量,不能对其赋值.2、枚举元素是有值的,C语言编译按定义时的顺序使它们的值为:0,1,2.,其值可以输出.如:printf(“%d”,mon);则输出整数 13、枚举元素的值也可由程序员指定.如:enum weekday sun=7,mon=1,tue,wed,thu,fri,sat;则:tue=2,sat=64、枚举值可以用来作判断比较 如:if(workday=mon)if(workday sun)比较规则:按其在定义时的顺序号比较。如果定义时 未人为指定,则第一个枚举元素的值认作 0。,56,4枚举类型,三、说明5、整数不能直接赋给枚举变量.如:workday=2;是错的 它们是不同的类型,应先用强制类型转换后才能赋值 如:workday=(enum weekday)2;相当于将顺序号为2的枚举元素赋给 workday,相当于:workday=tue;,57,4枚举类型,例:口袋中有红、黄、蓝、白、黑五色球若干个。每次从口袋中取 出 3 个不同色球,有几种取法,58,4枚举类型,main()enum color red,yellow,blue,white,black;enum color j,k,l,pri;int n=0,loop;for(j=red;j=black;j+)for(k=red;k=black;k+)if(j!=k)for(l=red;l=black;l+)if(l!=j,59,4枚举类型,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;printf(n);printf(ntotal:%5dn,n);,60,5用typedef定义类型,可采用typedef定义一个新的类型标识符来代替已有的类型标识符。一般形式为:typedef 原类型 新类型例如,使用INTEGER代表int类型:typedef int INTEGER;INTEGER i,j,k;使用REAL代表float类型:typedef float REAL;REAL x,y,z;使用DATE代表下面结构类型:typedef struct int month;int day;int yeay;DATE;,DATE d,*pd;,61,5用typedef定义类型,typedef int ARR100;(定义 ARR 为整型数组类型)ARR a,b,c;(定义 a,b,c 为整型数组变量)typedef char*STRING;(定义 STRING 为字符指针类型)STRING p,s10;(p为字符指针变量,s为字符指针数组)typedef int(*POINTER)();(定义 POINTER为指向函数的指针类型,该函数返回整型值)POINTER p1,p2;(p1、p2为POINTER类型的指针变量)归纳起来,定义新类型名的方法:1.先按定义的方法写出定义体(如:int i)2.将变量名换成新类型名(如:将i换成COUNT);3.在最前面加 typedef(如:typedef int COUNT);4.最后可以用新类型名去定义变量;,62,5用typedef定义类型,说明:1.用typedef可以定义各种类型名,但不能用来定义变量。2.用 typedef只是对已经存在的类型增加一个类型名,而没有 创造新的类型。3.typedef和#define有相似之处,如:typedef int COUNT 和#define COUNT int 两者的作用都是用COUNT代表 int。但#define是在预处理时处理,只能作简单字符替换 而typedef是在编译时处理,不是简单的字符替换4.当不同源文件中用到同一类型数据时,常用 typedef定义一些数据类型,把它们单独放在一个文件中,在需要用到它们的文件中用#include 命令把它们包含进来。5.使用 typedef有利于程序的通用与移植。,63,小 结,1、结构体类型的定义,结构体变量的定义、引用;2、结构体与数组;3、结构体与指针;4、用指针处理链表;5、共用体;6、枚举类型;7、用 typedef 定义类型。重点:结构体变量及结构体数组的引用,用指针处理链表。,