760 likes | 1.04k Views
8.1 结构. 1. 结构:是拥有若干分量的一种构造类型,而且一个结构的分量可以有不同的类型,包括指针、数组、其它结构。 2. 定义: struct 结构类型名 { 成员列表 } ;. 第八章 结构和联合. 例 : struct date { unsigned year; unsigned month; unsigned day; };. year. month. day. 注 : 不带变量的结构说明不 占存储空间,结构类型 集中放到一个文件中, 需要时用 # include.
E N D
8.1 结构 1. 结构:是拥有若干分量的一种构造类型,而且一个结构的分量可以有不同的类型,包括指针、数组、其它结构。 2. 定义: struct 结构类型名 { 成员列表 }; 第八章 结构和联合
例 : structdate { unsigned year; unsigned month; unsigned day; }; year month day 注: 不带变量的结构说明不 占存储空间,结构类型 集中放到一个文件中, 需要时用# include
3. 三种定义结构变量的方法 1)先定义结构类型,再定义结构变量 structdate { unsigned year; unsigned month; unsigned day; } ; structdate d1,d2 d1 d2 year year month month day day
2)在定义结构类型的同时定义变量 struct date { unsigned year; unsigned month; unsigned day; } d1,d2; 3)直接定义结构类型变量 struct { unsigned year; unsigned month; unsigned day; } date,d1,d2; • 3. 三种定义结构变量的方法
structstudent { int num; char name[20]; char sex; int age; structdate birthday; char addr; } student1,student2; 4. 结构的嵌套 birthday num name sex age addr year month day structdate { unsigned year; unsigned month; unsigned day; } ;
8.2 访问结构成员 旧标准规定只能对结构变量中的成员分别访问,而不能对结构变量进行赋值和输出。 新标准允许将一个结构变量直接赋给另一个具有相同结构的结构变量。 如果结构变量本身又属于一个结构类型,只能对最低层的成员进行赋值、存取和运算。如: student1.birthday.year=1967; “.”是成员(分量)运算符,优先级最高,自左向右结合。
结构成员可以象普通变量一样进行各种运算,例如:结构成员可以象普通变量一样进行各种运算,例如: student2. age=student1. age; student1. age++; ++studdent2. age; 可以引用成员的地址,结构变量的地址,例如: & student1.num & student1 8.2 访问结构成员
当结构变量为全局变量或静态变量时,可在定义时初始化,例如:当结构变量为全局变量或静态变量时,可在定义时初始化,例如: static struct student {int num; char name[20]; char sex; char addr[20]; } a={54, “Jane”, “M”, “123Road”}; 8.2 访问结构成员
8.3 结构数组和指向结构的指针 当结构中的每一个元素都是同一结构类型时,称该数组为结构数组。 定义结构数组时,可对全局或静态存储类别的数组初始化。 struct student {int num; char name[20]; int age; char addr[30]; }stu[]={{10101, “LiLin”,18,“M”,“beijing”}, {10102,“FuPing”,19,“M”,“shanghai”}};
num name sex age addr stu 10101 LiLin M 18 beijing 10102 FuPing M 19 shanghai 8.3 结构数组和指向结构的指针 • struct student • {int num; • char name[20]; • int age; • char addr[30]; • }stu[]={{10101, “LiLin”,18,“M”,“beijing”}, • {10102,“FuPing”,19,“M”,“shanghai”}};
structvalue { int key; int freq;}; #define ARRAYSIZE 1000; static structvalue set_arr[ARRAYSIZE]; find(x) int x {int i,m; set_arr[m=set_arr[0].freq+1].key=x; set-arr[m].freq=0; for(i=1;set-arr[i].key!=x;i++); set-arr[i].freq++; if(i==m )set-arr[0].freq++; } 例 8.1 在某集合中查询 是否存在值x,若不存在,则将x加入集合中。每次查询均记录查询的频率。 key freq set-arr[0] set-arr[i] set-arr[m] 字符个数 x freq++ x 0
指向结构的指针:用一个指针变量指向结构变量,此时该指针变量的值是结构变量的起始地址。指向结构的指针:用一个指针变量指向结构变量,此时该指针变量的值是结构变量的起始地址。 指针变量也可以用来指向结构数组中的元素。例如: structstudent stu_1;/*定义结构变量*/ structstudentstu[4]; /* 定义结构数组*/ structstudent*p;/*定义指向结构的指针变量*/ p=&stu_1;/*指向结构变量stu_1*/ p=stu;/*指向结构数组stu*/ 8.3 结构数组和指向结构的指针 struct student { int num; charname [20]; char sex; float score; };
通过指向结构的指针访问结构成员,访问结构成员的方式:结构变量名. 成员名 例如:stu_1. num; (*p).num;/*(*p)表示p指向的结构变量,注意*p两侧的括号不能省略,因为成员运算符的优先级高于取内容运算符的优先级。 (*p).num: p指向的结构变量中的成员num。 通过指针变量访问结构成员的一般形式是: 指向结构的指针变量->成员名 注意“.”和“->”的优先级相同,自左向右结合。 例如:p->num;stu_1. num;(*p). num;含义相同。 8.3 结构数组和指向结构的指针
请分析以下几种运算: p->n;得到p指向的结构变量中成员n的值。 p->n ++; 得到p指向的结构变量中的成员n的值,用完该值后使它加1。 ++ p->n ;得到p指向的结构变量中的成员n的值使之加1。 如果p指向结构数组的某个元素,那么, (++p)->num; 取下一个数组元素的成员num的值。 (p++)->num 取数组元素的成员num的值,然后将p指向下一个数组元素。 () ()
如果指针p已定义为指向structstudent类型的数据,它只能指向一个结构类型数据。 p的值是stu数组的一个元素的起始地址。 不是指向一个数组元素中的某一成员, 即p的地址不能是成员的地址。 例如: p=&stu[2].num;/*是不对的*/ 8.3 结构数组和指向结构的指针
8.3 结构数组和指向结构的指针 例 8.2在UNIX系统中用结构数组sptmap来记录系统页表空间。可以这样简单理解:数组sptmap的每一个元素记录一片连续的可用空间,其成员分别表示可用空间的首地址和长度,结构如下: • structmap • { short m_size;/*长度*/ • unsigned short m_addr;/*地址*/ • } sptmap[100]; • 从sptmap[1]开始搜索sptmap, • sptmap[0].m-addr=0 • sptmap[0].m-size=整个stpmap表中未用的map数, • 初始化时将未用表项的成员m-size置0。
m -addr m -size 0 1023 1024 2047 2048 3047 3048 5047 5048 sptmap[100] 表中未用 的map数 0 0 1024 1024 1000 2048 2000 3048
#define mapstart(x) &x[1] #define mapsize(x) x[0].m-size malloc(mp,size) struct map *mp; unsigned size; { register unsigned int a; register struct map *bp; • for( bp=mapstart(mp); bp->m-size; bp++) • { if (bp->m-size>=size) • { a=bp->m-addr; • bp->m-addr+=size; • if ((bp->m-size-=size)==0) • { do { bp++; • (bp-1)->m-addr=bp->m-addr; • } while((bp-1)->m-size=bp->m-size); • mapsize(mp)++; } • return(a); } • } • return(0); }
8.4 sizeof运算符和C的动态存储分配函数 1. sizeof():“求字节数”运算符,其优先级别与++、--等运算符相同。 • sizeof的运算对象:数据类型, • 返回值:该类型数据在内存中占用的字节数量。 • 例如:sizeof(int), sizeof(double*), sizeof(struct student)等等。 2. malloc函数:以一个无符号整数作为参数,返回指向字符的指针,例如:char *p; p=malloc(10); • 从可用空间中分配连续10个字节的存储空间,返回该片存储空间的首地址。如果没有足够的可用空间,则返回一个空指针NULL用以表明分配空间失败。
8.4 sizeof运算符和C的动态存储分配函数 3. free(p):p是一个指针,free将p所指向的存储空间归还给系统。 • 执行free(p)后,不会变更指针变量p的值,这个指针仍然指向原来的存储空间。若p和q是两个指向同一地址的指针变量,则执行free(p);后,应该跟有如下语句: • p=q=NULL;例如:int *pi;char *pc; • 语句*pc=‘a’;是错误的。因为pc的初值不确定。 • 可以把某个变量的地址赋给pc,或用malloc函数为它动态地分配存储空间:pc=malloc(1); • 为安全起见,调用malloc时应该判断是否成功地分配了存储空间。形式如下:if((pc=malloc(1))==NULL) • 假设调用malloc分配了位置是10的一个存储单元,则语句 *pc=‘a’;
例 8.3 建立一个从1到9的整数链表,然后打印出这些值。 #include “stdio.h” #define INTSIZE sizeof(int) #define NODESIZE sizeof(int)+sizeof(char *) #define ERROR {printf(“error\n”);return;} main() { int i; char *p,*q,*first,*malloc(); if((p=malloc(NODESIZE))==NULL) ERROR first=p; *(int *)p=1; p+=INTSIZE; for (i=2;i<10;i++,p+=INTSIZE) if ((q=malloc(NODESIZE))==NULL) ERROR else { *(char **)p=q; p=q; *(int *)p=i; } *p=NULL; for (p=first;p;p+=INTSIZE,p=*(char **)p) printf(“%d”,*(int *)p); putchar(‘\n’);}
P=malloc(sizeof(int)+sizeof(char *)) p+=sizeof(int) p+=sizeof(int) p+=sizeof(int) *(int*)p=1 *(int*)p=2 *(int*)p=8 * * ** ** *(char**)p=(q=malloc(sizeof(int)+sizeof(char *))) *(int*)p=9 *(char**)p=(q=malloc(sizeof(int)+sizeof(char *))) NULL
8.5 结构作为函数的参数 • 函数之间结构体变量的数据传递 • 向函数传递结构体变量的成员 • 作为成员变量(简单变量、数组或指针变量),它们可以参与所属类型允许的任何操作; • 向函数传递结构体变量 • 新的ANSI C标准允许把结构体变量作为一个整体传送给相应的形参; • 传递的是实参结构体变量的值; • 系统将为结构体类型的形参开辟相应的存储单元,并将实参中各成员的值赋给对应的形参成员。
8.5 结构作为函数的参数 • 结构体变量作为实参传递给函数,对应形参得到的是它的值; • 函数体内对形参结构体变量中任何成员的操作,都不会影响对应实参中成员的值; • 保证了调用函数中数据的安全; • 限制了将运算结果返回给调用函数。
8.5 结构作为函数的参数 • 在旧标准中,在结构上可以执行的操作仅有用&来取得它的地址,和引用它的成员。 • 指向结构的指针变量却不受这些限制,从而使结构和函数仍能在一起协调工作。 • 函数的返回值是结构体类型; • 函数可以返回指向结构的指针; • 用指向结构的指针变量作参数。
8.5 结构作为函数的参数 • 传递结构体的地址:将结构体变量的地址作为实参传递 • 对应的形参应该是一个基类型相同的结构体类型指针; • 系统只需为形参指针开辟一个存储单元存放实参结构体变量的地址值,而不必另行建立一个结构体变量; • 通过函数调用,有效地修改结构体中成员的值 • 节省时间、提高效率。
8.5 结构作为函数的参数 • 例:通过函数给结构体成员赋值 struct st {char s[10]; int t;} getdata(structst *p) {scanf(“%s%d”,p->s,&p->t);} main() {structst a; getdata(&a); printf (“%s,%d\n”,a.s,a.t); }
8.5 结构作为函数的参数 • 函数的返回值是结构体类型,例: structst {int a; char b;} struct st fun(structstx) {x.a=99; x.b=‘S’; return x;} main() {structst y; y.a=0; y.b=‘A’; printf(“y.a=%d y.b=%c\n”,y.a,y.b); y=fun(y); printf(“y.a=%d y.b=%c\n”,y.a,y.b); } 运行结果: y.a=0 y.b=A y.a=99 y.b=S
8.5 结构作为函数的参数 • 函数的返回值可以是指向结构体变量的指针,例: structst {int a; char b;} struct st *fun(structstx) {structst*px x.a=100; x.b=‘C’; px=&x; return px;} main() {structsty,*p; y.a=999; y.b=‘X’; printf(“y.a=%d y.b=%c\n”,y.a,y.b); p=fun(y); printf(“(*p).a=%d (*p).b=%c\n”,(*p).a,p->b); } 运行结果: y.a=999 y.b=X (*p).a=100 (*p).b=C
8.5 结构作为函数的参数 • 例如: struct student {long num; char name[20]; }; • 一个返回结构指针的函数 struct student *new() {struct student *aux; aux=(struct student *)malloc(sizeof(struct student)); if(aux==NULL) printf(“Out of memory\n”); return(aux);}
8.5 结构作为函数的参数 • 也可以把此函数写成传递一个指向结构的指针的指针作参数的函数。 void new1(s) structstudent **s; { structstudent *aux; aux=(structstudent *)malloc(sizeof(structstudent)); if (aux==NULL) printf(“Out of memory\n”); else *s=aux; }
8.6 结构的自引用 • 利用结构体变量构成链表 • 结构体中含有可以指向本结构体的指针成员 • 结构体的自引用:当一个结构体中有一个或多个成员是指针,它们的基类型就是本结构体类型时,通常把这种结构体称为可以“引用自身的结构体”。 • 例如: structlink {char ch; structlink*p; } a; a.ch a.p
h 0f04 0f00 0f02 a.ch 10 0f04 a.p 0f0C 0f06 0f08 0f0A b.ch 20 0f0C b.p 0f14 0f0E 0f10 0f12 c.ch 30 0f14 c.p NULL 0f16 8.6 结构的自引用 • 链表:把同一类型的结构体变量“链接”到一起形成“链表”; • 结点:被连接在一起的结构体变量; • 静态链表:由系统在内存中开辟了固定的、互不连续的存储单元,在程序执行的过程中,不可能人为地再产生新的存储单元,也不可能人为地使已开辟的存储单元消失。
p p p h a b c 8.6 结构的自引用 • 例:一个简单的链表 structnode { int data; structnode*next;} main() { struct node a,b,c,*h,*p; a.data=10; b.data=20; c.data=30; h=&a; a.next=&b; b.next=&c; c.next=‘\0’; p=h; while(p) { printf(“%d”,p->data); p=p->next;} printf(“\n”); } 10 20 30 \0
8.6 结构的自引用 • 动态链表:链表中的每个存储单元都由动态存储分配函数获得。 • 各次动态分配的存储单元地址不连续; • 各数据之间存在着接序关系; • 每个结点由数据域和指针域组成 • 指针域:存放下一个结点元素的地址 • 每个结点元素没有自己的名字,只能由指针维系结点元素之间的接序关系; • 一旦某个元素的指针“断开”,后续元素就再也无法找寻。
9 8 7 \0 头指针 头结点 8.6 结构的自引用 • 头指针:指向链表的开始; • 头结点:该结点的数据域中不存放数据; • 尾结点:链表最后一个结点的指针域置成‘\0’,标志链表的结束; • 每个结点的指针域存放下一结点的地址; • 单向链表:只能从当前结点找到后继结点。 • 链表的建立、结点数据域的输出、结点的插入和删除。
9 8 7 \0 头指针 头结点 8.6 结构的自引用 • 建立带有头结点的单向链表 • 读取数据 • 生成新结点 • 将数据存入结点的成员变量中 • 将新结点插入到链表中
8.6 结构的自引用 struct slist { int data; struct slist*next; } struct*create_slist1() { int c; struct slist*h,*s,*r; h=(struct slist)malloc(sizeof(struct slist)); r=h; scanf(“%d”,&c); while(c!=-1) { s=(struct slist)malloc(sizeof(struct slist)); s->data=c; r->next=s; r=s; scanf(“%d”,&c);} r->next=‘\0’; return h; } main() { struct slist*head; ······ head=create_slist1(); ······ }
8.6 结构的自引用 • 顺序访问链表中各结点的数据域 • 工作指针p:从头到尾依次指向链表中的每个结点 • 当指针指向某个结点时,就输出该结点数据域中的内容; • 直到遇到链表结束标志为止; • 如果是空链表,只输出有关信息并返回调用函数。
9 8 7 \0 头指针 头结点 8.6 结构的自引用 struct slist { int data; struct slist*next; } void print_slist(struct slist*head) { struct slist*p; p=head->next; if (p==‘\0’) printf(“Linklist is null!\n”); else { printf(“head”); do { printf(“->%d”, p->data); p=p->next;} while(p!=‘\0’); printf(“->end\n”); } }
9 7 q p 8 s 8.6 结构的自引用 • 在单向链表中插入结点 • 确定插入的位置 • 后插:当插入结点插在指针p所指的结点之后; • 前插:当插入结点插在指针p所指的结点之前,当进行前插操作时,需要三个工作指针; • s:指向新开辟的结点; • p:指向插入的位置; • q:指向p的前趋结点。
8.6 结构的自引用 • 例:编写函数insert_snode,在值为x的结点前插入值为y的结点,若值为x的结点不存在,则插在表尾。 • 在进行插入的过程中,可能遇到三种情况: • 链表非空,值为 x 的结点存在,新结点应插在该结点之前; • 链表非空,但值为 x的结点不存在,按要求,新结点应插在链表的结尾; • 链表为空表,这种情况相当于值为 x的结点不存在,新结点应插在链表的结尾,即插在头结点之后,作为链表的第一个结点;
8.6 结构的自引用 insert_snode(struct slist *head,int x,int y) {struct slist *s,*p,*q; s=(struct slist *)malloc(sizeof(struct slist)) ; s->data=y; q=head;p=head->next; while((p!=‘\0’)&&(p->data!=x)) { q=p; p=p->next; } s->next=p; q->next=s; } struct slist { int data; struct slist*next; }
9 8 7 头结点 头指针h p q 8.6 结构的自引用 • 删除单向链表中的结点 • 找到待删除结点的前趋结点q; • 将此前趋结点的指针域指向待删除结点的后续结点; • 释放被删结点所占存储空间。
8.6 结构的自引用 • 二叉树的数据结构的实现。 • 二叉树中每个结点记录一个不同的单词,结点内容如下: • 指向该单词内容的指针 • 该单词出现的次数 • 指向左孩子结点的指针 • 指向右孩子结点的指针
tnode *left *word count *right 8.6 结构的自引用 • 定义结点的结构: struct tnode { char *word; int count; structtnode*left; structtnode*right; }; • 这就是结构的自引用。left和right是指向结构的指针,而不是一个结点。
*left C++ count *right *left Basic count *right *left Java count *right *left Fortran count *right *left Foxbase count *right 8.6 结构的自引用 • 若输入如下单词:C++、Java、Fortran、basic、Foxbase、C++,二叉树的生成过程如下: Count=2
8.6 结构的自引用 • #define MAXWORD 20 • main() • { structtnode*root,*tree(); • char *t; • root=NULL; • while((t=getword())!=NULL) • root=tree(root,t); • treeprint(root);} • char *getword() • { char *p; • if ((p=malloc(MAXWORD+1))==NULL) • { printf(“Out of memory\n”); • return(NULL);} • scanf (“%MAXWORDs”,p); • if (strlen(p)==0) return(NULL); • return(p);}
8.6 结构的自引用 • structtnode*tree(p,w) • struct tnode*p; • char *w; • {structtnode*talloc(); • int cond; • if(p==NULL) • { p=talloc(); • p->word=w; • p->count=1; • p->left=p->right=NULL; } • else if((cond=strcmp(w,p->word))==0) • p->count ++; • else if(cond<0) • p->left=tree(p->left,w); • else
8.6 结构的自引用 • p->right=tree(p->right,w); • return(p);} • treeprint(p) • structtnode*p; • {if(p!=NULL) • {treeprint(p->left); • printf(“%4d%s\n”,p->count,p->word); • treeprint(p->right); } • } • structtnode*talloc() • { return((structtnode *)malloc(sizeof(structtnode))); • }