




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第10章结构体与共用体10.1结构体10.2链表10.3共用体10.4枚举类型10.5typedef结构体类型和共用体类型都是用户定义的构造数据类型,是实现对不同类型数据封装的一种方法。在比较复杂的数据结构中,结构体和共用体都有着广泛的应用。枚举类型的引入,使得程序中的一些含义清楚的标识符可作为数据使用,增加了程序的灵活性和可读性。typedef语句可以为程序提供较好的说明信息,便于理解,可以使程序参数化,便于移植。
在实际应用中,一组相关的数据可能是相同类型的,也可能是不同类型的。例如,表示一个学生的信息可能包括学号、姓名、出生年月和成绩这四项。为了相关数据的封装性,需要采用可以包含不同数据类型成员的类型来定义这样的数据,这种数据类型称为“结构体”类型。10.1结构体结构体类型是一种用户自定义的构造数据类型。它是由若干成员组成的,每个成员可以是一个基本数据类型,也可以是一个构造数据类型。结构体类型的各个成员可以是不同的类型,也可以是相同的类型。10.1.1结构体类型的定义
结构体类型定义的一般形式为
struct结构体类型名{
类型名1成员名1;
类型名2成员名2;
……
类型名n成员名n;
};说明:
(1)
struct是定义结构体类型的关键字。struct后面跟的是所定义的结构体类型的名字,其命名应符合标识符的命名规则。
(2)结构体类型的所有成员用花括号括起来,结构体类型成员的定义方式和变量的定义方式一样,成员名的命名规则和变量名的命名相同,各成员之间用分号分隔。结构体类型定义最后以分号结束。
(3)结构体类型成员的数据类型可以是基本类型,也可以是构造类型,比如数组,当然也可以是结构体类型。如上述关于学生情况的结构体类型可按如下定义:
structstudent_type{
intnumber;
charname[8];
structdate_typebirthday;
floatscore[3];
};它包含4个成员,其中出生年月又是一个如下的结构体类型:
structdate_type{
intyear;
intmonth;
intday;
};
注意:在实际应用程序中,结构体类型structdate_type的定义应出现在结构体类型structstudent_type的定义之前。10.1.2结构体变量与指向结构体变量的指针变量的定义
结构体类型的定义说明了一种有若干个特定成员组成的模型,像int类型、float类型一样,类型并不是实体,不占内存空间,只有结构体变量才能存放结构体类型的数据,是占用内存空间的实体。结构体变量占用内存空间,所以它是有地址的,结构体变量的地址就是它在内存中存储单元的首地址,用来存放结构体变量地址的变量就是指向结构体变量的指针变量。
结构体变量初始化是指在定义结构体变量的同时为结构体内的各成员变量赋初值。
结构体变量和指向结构体变量的指针变量的定义方法有3种。
(1)用已定义的结构体类型定义结构体变量和指向结构体变量的指针变量。例如:
structperson{
intnum;
charname[8];
intage;
floatwage;
}; /*定义结构体类型。*/
structpersonmember; /*定义结构体变量member。*/
structperson*pm=&member; /*定义指向结构体变量的指针变量pm,并初始化使其指向member。*/结构体的各成员顺次占用连续的存储空间,结构体变量member中有4个成员,它们各占2、8、2、4个字节,因此member共需占16个字节(假设int型占用2个字节,char型占用1个字型,float型占用4个字节)。指向结构体变量member的指针变量pm中存放的是member的首地址。
(2)定义结构体类型的同时定义结构体变量和指向结构体变量的指针变量。
structperson{
intnum;
charname[8];
intage;
floatwage;
}member,*pm=&member; /*定义结构体类型变量member和指向member的指针变量pm。*/
(3)定义无名结构体类型的同时定义结构体变量和指向结构体变量的指针变量。
struct{
intnum;
charname[8];
intage;
floatwage;
}member,*pm=&member; /*定义结构体类型变量member和指向member的指针变量pm。*/
这种定义形式和第二种方式相比,仅是省略了结构体名,一般用于不再需要定义此种类型的结构体变量的情况。10.1.3结构体变量的引用
对于结构体变量,只能通过引用其成员来使用结构体变量。结构体变量成员的引用方式为
结构体变量名.成员名
其中“.”称为成员运算符。
也可以通过指向结构体变量的指针变量来引用结构体变量的成员,其引用方式为
(*结构体指针变量名).成员名
或
结构体指针变量名->成员名
这两种表示形式是完全等价的。例如,若有定义:
structdate_type{
intyear;
intmonth;
intday;
};
structstudent_type{
intnumber;
charname[8];
structdate_typebirthday;
floatscore[3];
}student,*ps=&student;
那么,对于变量student的成员number可以有如下几种引用方式:
student.number
(*ps).number
ps->number
这几种引用方式是完全等价的。
对于结构体变量的成员仍然是结构体类型的,则需要逐级引用,直至最低级的成员。
例如:
student.birthday.year
(*ps).birthday.year
ps->birthday.year
注意:只能对最低级的成员进行存取和运算。
对于结构体变量的成员是数组类型的,只能引用其元素,而不能整体引用。
例如:
student.score[1]
(*ps).score[1]
ps->score[1]特别地,对于结构体变量的成员是字符数组类型的,虽然也可以像以上方法一样引用其元素,但是,由于字符数组存放的是字符串,字符数组名表示的是字符串的首地址,因此可引用字符数组名来对字符数组进行操作。
例如:
(*ps).name
ps->name
注意:数值型数组是不可以这样使用的。
结构体成员的运算规则与同类型的变量的运算规则相同。10.1.4结构体变量的初始化
和其它类型的变量一样,定义结构体变量的同时可以对其赋以初值,即对结构体变量初始化。
例如:
structdate_type{
intyear;
intmonth;
intday;
};
structstudent_type{
intnumber;
charname[8];
structdate_typebirthday;
floatscore[3];
}student={1001,"ZHANG",{1992,11,20},{89,90,91}};10.1.5结构体数组
1.结构体数组的定义和初始化
结构体数组的定义与结构体变量的定义类似,可以定义结构体类型后再定义数组,也可以在定义结构体类型的同时定义数组。结构体数组也具有一般数组的性质:结构体数组是由固定数目的相同结构体类型的变量按照一定的顺序组成的数据类型。例如:
structstudent{
intnum;
charname[8];
floatsc[3];
floatave;
}st[3]={{2001,"zhang",{81,77,88}},{2002,"li",{83,89,82}},{2003,"chang",{67,75,86}}};
表示定义了结构体数组变量st,其中含有3个元素,每一个元素都是structstudent这种结构体类型的,并且定义数组的同时对其成员进行了初始化。图10-1给出的是结构体数组st的逻辑存储结构。图10-1结构体数组st的逻辑存储结构和基本类型的数组一样,对于结构体类型的一维数组、二维数组,也可以定义指向它们的指针变量。例如:
structstudent*ps=st;
即指针变量ps指向结构体数组变量st的第0个元素,那么,ps+1表示st[1]的地址,ps++表示指针向后移动一个数组元素的空间,即指向下一个数组元素。
2.结构体数组的引用
结构体数组元素也是通过数组名和下标来引用的,但其元素是结构体类型的数据,因此,对结构体数组元素引用与结构体变量的引用一样,也要逐级引用,只能对最低级的成员进行存取和运算。一般的引用形式为
结构体数组名[下标].成员名
如对于上面定义的数组st而言,下列是合法的引用方式:
st[1].num /*表示第1个学生的学号。*/
st[0].name /*表示第0个学生的姓名(字符串首地址)。*/
st[0].sc[2] /*表示第0个学生的第2门课程的成绩。*/
st[1].ave /*表示第1个学生的平均成绩。*/
还可以通过指向结构体数组的指针来引用数组元素及成员,如果ps指向st,那么:
(*ps).num /*表示第0个学生的学号。*/
ps->name /*表示第0个学生的姓名。*/
(ps+1)->sc[2]; /*表示第1个学生的第2门课程的成绩。*/
++ps->ave; /*表示使第0个学生的平均成绩增1(“->”运算符的优先级高于“++”运算)。*/
(++ps)->ave; /*表示指针ps向后移动一个数组元素空间,指向st[1],即该式子表示第1个学生的平均成绩。*/
需要注意的是,ps表示数组元素(结构体变量)的地址,虽然它与其第一个成员的地址的值相同,但它们的类型是不同的,它们之间不能直接相互赋值。若需要将其成员的地址赋值给结构体指针,可以先将其用强制类型转换转换为结构体指针的类型,如:
ps=(structstudent*)(&st[1].ave);
这时,若执行“ps++;”,那么ps指向的是st[2].ave,也就是说ps还是向后移动了一个结构体类型structstudent那么大的空间。
【例10.1】
编写程序,输入若干个学生的有关信息(包括学号、姓名、出生日期、课程1成绩、课程2成绩、课程3成绩),计算出平均成绩。
#defineN3
#include<stdio.h>
structstudent{
intnum;
charname[8];
struct{
intyear;
intmonth;
intday;
}birthday;
intsc[3];
floatave;
};
main()
{
structstudentst[N],*p;
intj,k;
printf(“\n”);
for(j=0;j<N;j++){/*输入学生的有关信息*/
printf(“The%dstudent:\n”,j);
printf(“number:”);
scanf(“%d”,&st[j].num);
printf(“name:”);
scanf(“%s”,st[j].name);
printf(“birthday(year,month,day):”);
scanf("%d%d%d",&st[j].birthday.year,&st[j].birthday.
month,&st[j].birthday.day);
st[j].ave=0;
for(k=0;k<3;k++){
printf("score[%d]:",k);
scanf("%d",&st[j].sc[k]);
st[j].ave+=st[j].sc[k];/*计算总成绩*/
}
st[j].ave/=3;
}
printf("\n");
printf("NumberNameYYYY/MM/DDScore[0]--Score[3]Average\n");
for(p=st;p<st+N;p++){
printf("%6d",p->num);
printf("%8s",p->name);
printf("%4d/%2d/%2d",p->birthday.year,p->birthday.month,p->birthday.day);
for(k=0;k<3;k++)
printf("%6d",p->sc[k]);
printf("%10.2f\n",p->ave);
}
}程序运行结果:
The0student:
number:1↙
name:a↙
birthday(year,month,day):198011↙
score[0]:67↙
score[1]:78↙
score[2]:80↙
The1student:
number:2↙
name:b↙
birthday(year,month,day):198125↙
score[0]:87↙
score[1]:79↙
score[2]:90↙
The2student:
number:3↙
name:c↙
birthday(year,month,day):197951↙
score[0]:85↙
score[1]:79↙
score[2]:93↙
NumberNameYYYY/MM/DDScore[0]-Score[3]Average
1a1980/1/167788075.00
2b1981/2/587799085.33
3c1979/5/185799385.6710.1.6结构体变量作为函数的参数
用结构体变量的成员作函数的实参的用法与普通变量作函数的实参的用法相同,要注意作为实参的结构体变量的成员的类型要与形参的类型相匹配。形参与实参之间仍然是“值传递”的方式。
1.结构体变量作为函数的参数
旧版的C编译系统不允许结构体变量作函数的参数,ANSIC标准取消了此限制,允许函数之间传递结构体变量,若实参是结构体变量,那么形参也应是同类型的结构体变量。
在发生函数调用时,形参结构体变量也要占用空间,接受实参结构体变量传来的信息,因此函数之间传递结构体变量会带来时间和空间的巨大开销。而且,形参与实参之间是“值传递”的方式,被调函数对形参结构体变量的修改并不能传递给实参,即主调函数无法得到处理后的数据,所以虽然语法上允许,但一般很少采用这种传递方式。
2.结构体指针作为函数的参数
结构体指针作为函数的参数,向函数传递结构体变量的地址,被调函数中可以通过实参传来的结构体变量的地址引用结构体变量,从而在被调函数中对结构体变量进行修改,也就间接地达到了改变主调函数中的结构体变量的值的目的。
【例10.2】
职工的信息包含职工号、姓名、年龄和工资,编写程序将这名职工的工资增加100元。要求在函数input()中输入职工的各项信息,在函数process()中修改职工的工资,在函数output()中输出职工的信息,在main()函数中调用以上3个函数。
#include<stdio.h>
structperson{
intnumber;
charname[8];
intage;
intwage;
};
voidinput(structperson*p)/*输入职工信息*/
{
printf("\nnumber:");
scanf("%d",&p->number);
printf("name:");
scanf("%s",p->name);
printf("age:");
scanf("%d",&p->age);
printf("wage:");
scanf("%d",&p->wage);
return;
}
voidprocess(structperson*p)/*修改工资*/
{
p->wage+=100;
return;
}
voidoutput(structperson*p)/*输出职工信息*/
{
printf("\n%6d,%8s,%2d,%6d",p->number,p->name,p->age,p->wage);
return;
}
main()
{
structpersonw;
printf("\nPleaseinputthedata:");
input(&w);
process(&w);
printf("\nThedatais:");
output(&w);
}
程序运行结果:
Pleaseinputthedata:
number:1↙
name:a↙
age:25↙
wage:1500↙
Thedatais:
1,a,25,1600本程序中,作为实参的是结构体变量的地址,发生函数调用时,将结构体变量的地址赋给形参指针,这样形参指针就指向了结构体变量w,因此在各被调函数中的各项操作的结果,在主调函数中都能得到。
3.结构体数组作为函数的参数
向函数传递结构体数组与传递其他的数组一样,实质上传递的是数组的首地址,形参数组与实参数组共同占用同一段内存单元。
【例10.3】
利用结构体数组作为函数的参数重新编写例10.1。
#defineN3
#include<stdio.h>
structstudent{
intnum;
charname[8];
struct{
intyear;
intmonth;
intday;
}birthday;
intsc[3];
floatave;
};
voidinput(structstudentst[
],intn)/*输入学生的信息*/
{
intj,k;
printf("\n");
for(j=0;j<n;j++){
printf("The%dstudent:\n",j);
printf("number:");
scanf("%d",&st[j].num);
printf("name:");
scanf("%s",st[j].name);
printf("birthday(year,month,day):");
scanf("%d%d%d",&st[j].birthday.year,&st[j].birthday
.month,&st[j].birthday.day);
for(k=0;k<3;k++){
printf("score[%d]:",k);
scanf("%d",&st[j].sc[k]);
}
}
return;
}
voidaverage(structstudentst[
],intn) /*计算学生的平均成绩*/
{
intj,k;
for(j=0;j<n;j++)
{
st[j].ave=0;
for(k=0;k<3;k++)
st[j].ave+=st[j].sc[k]; /*计算总成绩*/
st[j].ave/=3; /*计算平均成绩*/
}
return;
}
voidoutput(structstudentst[
],intn) /*输出学生的信息*/
{
structstudent*p=st;
intk;
printf("\n");
printf("NumberNameYYYY/MM/DDScore[0]--Score[3]Average\n");
for(p=st;p<st+n;p++)
{
printf("%6d",p->num);
printf("%8s",p->name);
printf("%4d/%2d/%2d",p->birthday.year,p->birthday.month,p->birthday.day);
for(k=0;k<3;k++)
printf("%6d",p->sc[k]);
printf("%10.2f\n",p->ave);
}
return;
}
main()
{
structstudentst[N];
input(st,N);
average(st,N);
output(st,N);
}
程序运行结果:
The0student:
number:1↙
name:aaa↙
birthday(year,month,day):198018↙
score[0]:80↙
score[1]:79↙
score[2]:85↙
The1student:
number:2↙
name:bbb↙
birthday(year,month,day):197959↙
score[0]:87↙
score[1]:85↙
score[2]:90↙
The2student:
number:3↙
name:ccc↙
birthday(year,month,day):198135↙
score[0]:78↙
score[1]:76↙
score[2]:85↙
NumberNameYYYY/MM/DDScore[0]--Score[3]Average
1aaa1980/1/880798581.33
2bbb1979/5/987859087.33
3ccc1981/3/578768579.67
链表是一种常见的数据结构,是一种动态分配存储空间的数据结构,应用非常广泛。链表是指将若干个称为结点的数据项按一定的规则连接起来的表。链表的连接原则是:前一个结点指向后一个结点,只有通过前一个结点才能找到后一个结点。因此,一个链表必须已知其表头指针。10.2链表10.2.1内存分配函数和回收函数
链表是一种动态的存储结构,根据需要动态地分配和释放结点。C语言中提供了以下函数来实现对内存空间的申请和释放。
1. malloc()函数
函数的调用格式为
malloc(字节数)
函数的功能是:在内存的动态存储区中分配“字节数”个字节的连续空间。函数调用成功其返回值是所分配的内存空间的首地址;函数调用失败(没有足够的内存)则返回一个空指针NULL。说明:
(1)当函数调用格式里的“字节数”为0时,函数的返回值为NULL。
(2)使用返回值时,需要检查返回值是否为NULL。
malloc()函数的返回值是void类型指针,在使用该函数时,需要强制类型转换为所需的类型。例如:
structnode{
intdata;
structnode*next;
} /*定义结构体类型*/
structnode*pnode; /*定义指向structnode类型指针变量*/
pnode=(structnode*)malloc(sizeof(structnode)); /*pnode指向函数malloc分配的内存*/
这里,sizeof是C语言中的运算符,其功能是计算类型或变量所占的字节数,sizeof(structnode)就是计算structnode这种类型所占的字节数。那么,malloc(sizeof(structnode))的功能就是分配一个structnode这种变量所占的内存空间,malloc()函数的返回值是void类型的指针,而指针pnode的基类型为structnode类型,因此需要在malloc(sizeof(structnode))之前加上强制类型转换(structnode*),将malloc()函数的返回值转换为指向structnode类型的指针。
malloc()函数在头文件malloc.h和stdlib.h中定义。若要使用,必须在程序的开始将这两个头文件用文件包含命令包含到本文件中。
2. calloc()函数
函数的调用格式为
calloc(存储单元的个数,存储类型的字节数)
函数的功能是:在内存分配大小为“存储单元的个数
×
存储类型的字节数”的存储空间,且新分配的内存空间全部初始化为0。函数调用成功其返回值是所分配内存的首地址;函数调用失败(没有足够的内存空间)其返回值是NULL。说明:
(1)当函数调用格式里的“存储单元的字节数”为0时,函数的返回值为NULL。
(2)使用返回值时,需检查返回值是否为NULL。
(3)
calloc函数的返回值是void类型指针,在使用函数时,需用强制类型转换为所需要的类型。
calloc函数常用来为一维数组分配空间。例如:
int*a;那么,下面语句实现长度为10的一维整型数组申请空间,并且让a指向数组的首地址:
a=(int*)calloc(10,sizeof(int));
calloc函数在头文件malloc.h和stdlib.h中定义。若要使用,必须在程序的开始将该文件用文件包含命令包含到本文件中。
3. free()函数
函数的调用格式为
free(指针)
函数的功能是:释放由“指针”所指向的内存区域,函数free没有返回值。
注意:free()函数只能用于已由malloc()函数或calloc()函数分配地址的指针。
free()函数在头文件malloc.h和stdlib.h中定义。若要使用,必须在程序的开始将这两个头文件用文件包含命令包含到本文件中。10.2.2用指针和结构体构成链表
链表是一种动态存储分配的数据结构,它与数组不同,在定义数组时必须定义数组的长度,即数组一旦定义,则它就含有固定个数的元素。在实际应用中,有时很难预先确定数据元素的个数,其个数是不断变化的,这时若数组长度定义过小,会没有足够的空间来存放数据;若数组长度定义过大,则会造成空间的浪费。那么,在程序的执行过程中,若能够在需要时开辟存储空间,在不需要时释放存储单元,就能合理地使用存储空间。C语言中提供了动态分配和释放内存的功能,能够满足这种需要。链表是由若干个称为结点的数据项构成的,每个结点都包含数据域和指针域,数据域用来保存该结点的数据信息,指针域用来保存该结点的后继结点或前驱结点的地址。
在此仅介绍单向链表,即每个结点只包含一个指向后继结点的链域的链表类型,关于其它的链表请参考“数据结构”的相关书籍。
链表都有一个头指针,用来保存该链表的首地址,即第一个结点的地址。头指针是链表的标志,图10-2是一个单向链表的逻辑示意图。
图10-2一个单向链表的逻辑示意图其中,head是指向头结点的指针变量,用来保存单向链表的第1个结点的地址,第1个结点的指针域中又保存着第2个结点的地址,第2个结点的指针域中又保存着第3个结点的地址,直到最后一个结点,该结点没有后继结点,它的指针域为空,其值为NULL,在图中用“^”表示。
单向链表的结点可以定义为结构体类型,结构体中除了表示数据信息的若干成员外,还需要一个指向自身结构体类型的指针变量来保存后继结点的地址。例如,设结点中的信息只有一个整型成员,那么结点的类型应定义为
structnode{
intdata;
structnode*next;
};
该类型的第二个成员next是一个基类型为structnode类型的指针,即next是指向自身结构体类型的指针变量。
【例10.4】
给结点的指针域赋值,建立一个单向链表。
#include<stdio.h>
main()
{
structnode{
struct{
intnum;
floatsc;
}data;
structnode*next;
}node1,node2,node3,*head,*p;
head=&node1;
node1.data.num=1;
node1.data.sc=89;
node1.next=&node2;
node2.data.num=2;
node2.data.sc=78;
node2.next=&node3;
node3.data.num=3;
node3.data.sc=92;
node3.next=NULL;
for(p=head;p!=NULL;p=p->next)
printf("%3d%4.1f\n",p->data.num,p->data.sc);
}
程序中的结点由数据域data和指针域next构成;而数据域由整型num和单精度型sc两部分构成;指针域是structnode类型的。
程序运行结果:
189.0
278.0
392.010.2.3单向链表的建立
创建链表的过程就是从无到有建立链表的过程,建立链表需要逐个分配结点的存储空间,建立结点间的链接关系。
为方便在链表上进行各种操作,一般在含有有效信息的所有结点之间再附加一个“头结点”。头结点的数据域一般不包含任何信息,头结点只起方便操作的作用。
建立单向链表的主要步骤如下:
(1)生成只含有头结点的空链表。
(2)读取数据信息,生成新结点,将数据存放于新结点中,插入新结点到单向链表中。
(3)重复(2),直到输入结束。
根据新结点插入到链表位置的不同,建立链表的方式分为在表尾插入和在表头插入两种,下面分别举例说明。
【例10.5】
在表尾插入结点生成单向链表。
#include<stdio.h>
#include<malloc.h>
structnode{
chardata;
structnode*next;
};
structnode*create1()
{
structnode*head,*tem,*rear;
charch[80];
inti=0;
head=(structnode*)malloc(sizeof(structnode));
rear=head;
gets(ch);
while(ch[i]!='\0'){
tem=(structnode*)malloc(sizeof(structnode));
tem->data=ch[i];
rear->next=tem;
rear=tem;
i++;
}
rear->next=NULL;
return(head);
}
main()
{
structnode*np;
np=create1();
while((np->next)!=NULL){
np=np->next;
printf("%c",np->data);
}
}程序运行结果:
xy↙
xy
程序的执行过程如下:
main函数调用函数create1,在create1函数中:
(1)生成头结点,让头指针head指向头结点,此时链表中只有一个结点,当前它既是头结点也是最后一个结点(尾结点),因此让尾指针rear也指向这个结点,如图10-3(a)所示。
(2)输入字符串
'xy'
给字符数组ch,ch[i](i=0)中的字符
'x'
非
'\0',进入循环体。这时生成新结点tem,给tem->data赋值为ch[i]的值,如图10-3(b)所示。
(3)修改尾结点的指针域指向新结点,此时tem是新的尾结点,所以修改rear指针指向rear结点,如图10-3(c)所示。
(4)
i++;后,ch[i](i=1)为
'y'
非
'\0',生成新结点tem,tem的数据域赋值为
'y',插入tem到表尾,修改rear指针,保持其指向尾结点,插入
'y'
后链表如图10-3(d)所示。
(5)
i++;后,ch[i](i=2)为
'\0',循环结束,将尾结点的指针域设置为空。
图10-3插入链表的结点
【例10.6】
在表头结点插入结点生成单向链表。
#include<stdio.h>
#include<malloc.h>
structnode{
chardata;
structnode*next;
};
structnode*create2()
{
structnode*head,*tem;
charch[80];
inti=0;
head=(structnode*)malloc(sizeof(structnode));
head->next=NULL;
gets(ch);
while(ch[i]!='\0'){
tem=(structnode*)malloc(sizeof(structnode));
tem->data=ch[i];
tem->next=head->next;
head->next=tem;
i++;
}
return(head);
}
main()
{
structnode*np;
np=create2();
while(np->next!=NULL){
np=np->next;
printf("%c",np->data);
}
}
程序的运行结果:
xy↙
yx
采用表尾插入结点的方式创建的链表,在生成过程中需要始终使用一个尾指针指向最后一个结点来表示插入的位置,最后表中结点的次序与生成的次序相一致;而采用表头插入结点,最后表中结点的次序与生成的次序相反。10.2.4链表的删除操作
在一个单向链表中删除指定结点,首先要找到该结点的前驱结点,然后修改前驱结点的指针域指向待删除结点的后继结点,最后释放被删结点。
【例10.7】编写函数实现在已经创建的单向链表中删除值为u的结点(u为参数)。
structnode*delete(structnode*head,intu)
{
structnode*p,*q;
q=head;/*q指向头结点,p指向第一个结点*/
p=head->next;
while(p&&(q->data!=u)){/*查找值为u的结点p*/
q=p;/*查找过程中,q指向p的前驱*/
p=p->next;
}
if(p){/*若找到,修改q的指针域指向p的后继结点,释放p*/
q->next=p->next;
free(p);
}
else/*若未找到,输出相应的信息*/
printf("\notfound!");
return(head);
}
找到结点后,删除结点的示意图如图10-4所示。
图10-4删除链表的结点10.2.5链表的插入操作
在链表中插入结点,首先要找到插入位置,再进行插入。假设指针变量s指向待插入结点,那么根据插入位置的不同,分为将s插入到结点之后和将s插入到结点s之前。
假设p所指为指定结点,那么将s所指结点插入到p所指结点之后可用这两句C语句描述:
s->next=p->next; /*s所指结点的指针域指向p所指结点的后继结点。*/
p->next=s; /*p所指结点的指针域指向s所指结点,即新插入结点。*/
要在指定结点p之前插入新结点,除了使得新结点的指针域指向p外,还需要修改p的前驱结点的指针域指向新结点。因为单向链表的结点中只有保存后继结点地址的指针域,所以插入到指定结点p之前,首先要找到p的前驱结点q,那么,这时插入到p结点之前,就相当于插入q结点之后。
【例10.8】
编写函数实现在已经创建的单向链表中的值为u的结点之前插入一个值为v的结点(其中u、v为参数)。
structnode*insert(structnode*head,intu,intv)
{
structnode*p,*q,*s;
s=(structnode*)malloc(sizeof(structnode)); /*生成新结点s*/
s->data=v; /*将v存放在新结点中*/
q=head; /*q指向头结点,p指向第一个节点*/
p=head->next;
while(p&&(p->data!=u)){ /*查找值为u的结点*/
q=p; /*查找过程中q指向p的前驱*/
p=p->next;
}
s->next=p; /*插入s结点到p结点之前,若未找到则插入到表尾*/
q->next=s;
return(head);
}
10.3.1共用体类型的定义
共用体类型是一种用户自定义构造型数据类型,和结构体一样,是由类型不同的、固定个数的成员所组成,但结构体的成员是同时存在的,分别占用不同的内存单元;而对于共用体而言,其所有成员共同占用同一段内存。从微观上看,某一时刻只能有某一个成员起作用,其它成员是不存在的。10.3共用体共用体类型定义的一般形式为
union共用体名{
类型名1成员名1;
类型名2成员名2;
……
类型名n成员名n;
}说明:
(1)
union是关键字,标志共用体类型。union后面跟的是共用体类型的名字,共用体的命名应符合标识符的命名规则。
(2)花括号后面是共用体的各个成员,定义成员的方式和定义变量相同,各个成员之间用分号分隔,共用体类型的定义以分号结束。
(3)共用体类型的成员可以是基本类型,也可以是构造类型,但通常情况下共用体的成员是基本类型。
(4)共用体的所有成员共同占用同一段内存,所有成员的起始地址是相同的,整个共用体所占的内存单元的大小等于所有成员中占用内存单元最多的那个成员所占的字节数。
例如:
unionun{
charc;
intk;
floatf;
};上述定义表示定义共用体类型un,它有3个成员c、k、f,其中c是字符型,需占一个字节;k是整型,需占两个字节;f是单精度实型的,需占4个字节;那么,这种共用体共占4个字节。c占用第1个字节;k占用第1个和第2个字节;f占用所有的4个字节。在使用时,某一时刻只能使用c、k、f中的一个,不能同时使用。10.3.2共用体变量的定义
和结构体一样,共用体类型的定义只是表示定义了一种模型,变量才是存放数据的实体。定义共用体变量的方式和定义结构体变量的方式类似,也有3种方式。
(1)用已定义的共用体类型定义共用体变量。
unionun{
charc;
intk;
floatf;
}; /*定义共用体类型。*/
unionunun1,un2; /*定义共用体变量un1、un2。*/
(2)定义共用体类型的同时定义共用体变量。
unionun{
charc;
intk;
floatf;
}un1,un2;
(3)定义无名共用体类型的同时定义共用体变量。
union{
charc;
intk;
floatf;
}un1,un2;
指向共用体的指针变量的定义,与指向结构体的指针变量的定义方式是相同的,在此不再赘述。10.3.3共用体变量的引用
对于共用体变量,只能引用它的成员,而不能引用整个共用体变量。引用共用体变量的方式和引用结构体变量的方式类似,其引用方式为
共用体变量名.成员名
例如:un1.c、un2.f和un1.k。
若共用体的成员是构造类型的,则需要逐级应用至最低级的成员。
同样,也可以通过指向共用体变量的指针变量来引用共用体变量的成员,其引用方式为
(*共用体指针变量名).成员名
或
共用体指针变量名->成员名
这两种表示形式是完全等价的。例如,若已有定义:
unionun*pu=&unl;
那么以下是合法的引用方式:
(*pu).c
pu->k
共用体变量的地址与共用体成员的地址是相同的,如对上面定义的共用体变量unl而言,&ul、&ul.c、&ul.k、&ul.f是相等的,但是它们的类型是不同的。在给共用体变量赋值时,只能对共用体的一个成员赋值,而不能对整个共用体变量赋值,也不能对共用体变量在定义时初始化。因为共用体的成员共用一段连续的内存单元,若多次给共用体的成员赋值,那么,每次赋值都会覆盖原来的值,也就是说,对共用体变量而言,只有最近一次的赋值是有效的,而再去使用原来的成员的值是不合适的。10.3.4共用体类型数据在内存中的存储
共用体类型变量和结构体类型变量的定义形式相同,但有着本质区别。区别表现为:结构体类型变量的不同成员分别使用不同的内存空间,一个结构体类型变量所占用的内存空间的大小,至少为该变量各个成员所占用的内存大小的总和(有可能有对齐要求),结构体变量中的各个成员相互独立,彼此不占用同一内存空间;而共用体类型变量中的各个成员则共同使用同一内存空间(但不在同一时刻),共用体变量中的各个成员都有相同的起始地址,这个起始地址就是该共用体变量的起始地址。一个共用体变量所占用的内存空间的大小与它的某一成员所需的存储空间的大小相同,该成员是各个成员中占用存储空间最大的那一个。
例如:
union{
charc;
intk;
floatf;
}un1;共用体变量un1是由字符型变量c、整型变量k和实型变量f三个成员构成的,三个成员的起始地址相同,共用体变量un1所占用的存储空间的大小为三个成员中占用存储空间最大的那个,即为实型变量f,共4个字节。
【例10.9】
分析下列程序的输出结果,进而说明共用体的成员是共址的。
#include<stdio.h>
uniondata{
charc_data;
inti_data;
floatf_data;
};
main()
{
uniondatad1;
d1.c_data='a';
d1.i_data=5;
d1.f_data=3.7;
printf("%c%d%f\n",d1.c_data,d1.i_data,d1.f_data);
printf("%d\n",sizeof(d1));
printf("%p%p%p%p\n",&d1.c_data,&d1.i_data,&d1.f_data,&d1);
}
程序运行结果(注:系统不同,输出结果会有差异):
=-131073.700000
4
FFD4FFD4FFD4FFD4
说明:
(1)该程序中,首先定义了一个data类型,其拥有3个成员,分别为字符型、整型和实型。由该共用体类型定义了共用体变量d1,并给其成员分别赋值。当输出d1的3个成员的值时,前两个成员的输出值显然是无意义的,只有最后一个成员是有意义的,其值为3.7。这说明:某一时刻一个共用体变量中只有一个成员起作用,其它成员不起作用。
(2)输出sizeof(d1)的值为4,这说明共用体变量d1占内存4个字节。在多个共用体成员共占一个内存地址时,该地址所指向的内存空间是所有成员中占内存空间最大的成员所占的内存空间。该例中的3个成员所占内存字节数分别为1、2和4,最大的是4,因此,共用体变量d1所占内存空间为4个字节。
(3)输出共用体变量d1的3个成员的内存地址都是相同的,并且与共用体变量d1的地址值也是相同的,可见共用体变量各成员是共址的。
当然,程序稍加修改,便可得到3个成员变量及变量d1的地址。修改后的程序如下:
#include<stdio.h>
uniondata{
charc_data;
inti_data;
floatf_data;
};
main()
{
uniondatad1;
d1.c_data='a';
printf("%c",d1.c_data);
d1.i_data=5;
printf("%d",d1.i_data);
d1.f_data=3.7;
printf("%f\n",d1.f_data);
printf("%d\n",sizeof(d1));
printf("%p%p%p%p\n",&d1.c_data,&d1.i_data,&d1.f_data,&d1);
}程序运行结果:
a53.700000
4
FFD2FFD2FFD2FFD2
10.4.1枚举类型的定义
在实际问题中,有些数据的取值用有意义的名字表示更直观,比如,月份、星期用相应的英文单词来表示比用整型数据表示更能增加程序的可读性,并且它们的取值应该限定在合理的范围内。10.4枚举类型在C语言中,枚举类型就是若干个采用标识符表示的整型常量的集合。枚举类型的定义方式如下:
enum枚举类型名{枚举常量1,枚举常量2,……,枚举类型n};
说明:
enum是用来定义枚举类型的关键字,枚举类型名是用户定义的类型名,枚举常量是用户定义的有意义的标识符,即罗列出该枚举类型各种可能的、合法的取值。例如:
定义表示月份的枚举类型:
enummonths{jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec};
定义表示星期的枚举类型:
enumweek{sun,mon,tue,wed,thu,fri,sat};
枚举常量是有确定值的,每一个枚举常量隐含着一个整型的值,在默认的情况下,第一个枚举常量的值是0,以后每一个枚举常量的值是前一个常量的值加1的结果。如上面月份的定义,各枚举常量的值是0~11。也可以人为地指定枚举常量的值,如:
enummonths{jan=1,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec};
枚举常量jan被显式地赋值为1,那么,没有被显式赋值的枚举常量的值和前一个相比逐个增1,即在本类型中,各枚举常量的值为1~12。
再如:enumweek{sun=7,mon=1,tue,wed,thu,fri,sat};
sun被指定为7,mon被指定为1,后面的常量tue的值为2,wed的值为3,thu的值为4,fri的值为5,sat的值为6。定义时,显式地指定枚举常量的值,其实是指定枚举常量间的顺序。在定义枚举类型后,对于已定义的枚举常量是不能赋值的,因为常量的值是确定的,不能被修改。10.4.2枚举类型变量的定义和引用
1.枚举类型变量的定义
定义枚举类型变量的方式和定义结构体变量的方式类似,也有3种方式:
(1)用已定义的枚举类型定义枚举类型变量:
enumweek{sun,mon,tue,wed,thu,fri,sat};
enumweekw1,w[3];
(2)定义枚举类型的同时定义枚举类型变量:
enumweek{sun,mon,tue,wed,thu,fri,sat}w1,w[3];
(3)定义无名枚举类型的同时定义枚举类型变量:
enum{sun,mon,tue,wed,thu,fri,sat}w1,w[3];
2.枚举类型变量的引用
枚举类型变量的引用方式和普通变量一样,但枚举类型变量的取值只能是其枚举类型所枚举的各个常量,一般给枚举类型变量赋值枚举常量。
注意:
虽然编译系统将枚举类型处理为整型,但整型和枚举类型之间不能相互赋值;若要赋值,需进行强制类型转换。如:
w1=mon;
we[0]=(enumweek)1;
以上都是合法的赋值方式。
we[2]=3;
这不是合法的赋值方式。说明:
(1)可以比较枚举类型变量的大小,枚举类型变量的比较相当于比较它们所隐含的整数值,如已有赋值:we[0]=mon;we[1]=sat;,那么,we[0]的值小于we[1]的值。
(2)枚举类型变量的值在该枚举类型中枚举的常量所代表的整数值范围内,因此枚举类型变量可以作为循环变量来控制循环。
【例10.10】
编程实现输出12个月份的英文名称。
#include<stdio.h>
enummonths{jan=1,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec};
main()
{
enummonthsm;
char*name[]={"January","February","March","April","May","June","July","August","September",
"October","November","December"};
for(m=jan;m<=dec;m++)
printf("%2d:%s\n",m,name[m-1]);
}
程序运行结果:
1:January
2:February
3:March
4:April
5:May
6:June
7:July
8:August
9:September
10:October
11:November
12:December
本程序中,在函数体外定义了枚举类型enummonths,其中的枚举常量是12个月份的英文缩写,并且指定第一个枚举常量的值为1,则后面的枚举常量顺次得到值2、3、4、…、12。
对于枚举常量,并不能采用什么方法来输出枚举常量名,就像符号常量一样,输出的只能是它的值,而不能输出常量名。采用d格式符输出的是枚举常量所隐含的整数值。因此,要输出这12个字符串,本程序采用输出指针数组元素所指向的字符串的方
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 超市提成合同协议书
- 邻居违建调解协议书
- 道路损毁修复协议书
- 高中宿舍承包协议书
- ufc比赛伤亡协议书
- 单位章程及联营协议书
- 衣柜闲置转让协议书
- 车位包租返租协议书
- 路人死亡赔偿协议书
- 高中就业合同协议书
- 大会-冠脉微循环障碍课件
- 《城市环境卫生质量标准》
- 2023年湖北省武汉第二中学高考英语一模试卷(含答案解析)
- 2023届高考语文复习:西藏男孩丁真 课件
- DB4401-T 31-2019数字地图测绘技术规程-(高清现行)
- 吡格列酮联合二甲双胍治疗2型糖尿病的循证证据
- 全国青少年电子信息智能与创新大赛(智能运输器)考试题库
- SAP电池行业解决方案
- unit5 Will you be a worker or a laborer名师优质课赛课一等奖市公开课获奖课件
- 布草间管理制度(3篇)
- 物流管理专业 苏果超市南京地区配送路径优化研究
评论
0/150
提交评论