第十一章 结构体.docx
《第十一章 结构体.docx》由会员分享,可在线阅读,更多相关《第十一章 结构体.docx(50页珍藏版)》请在冰豆网上搜索。
第十一章结构体
第十一章结构体、共用体和用户定义类型
C语言中的整型、实型、字符型等基本数据类型适合于简单问题的数据处理,当遇到复杂的数据关系时很难用简单的数据类型来描述,为此,C语言提供了几种专门处理复杂数据关系的数据类型,即结构体、共用体和枚举类型。
11.1结构体类型
在前面的章节中,全面地介绍了C语言提供的几种基本数据类型,如int、float、char、double等,这些基本的数据类型在处理简单的单一数据时是十分方便的,而且在处理一组相同数据类型的数据时,只要利用C语言中数组的概念,将它们组织成一个数据集合,就可以很方便地对它们进行引用和操作。
但是在实际的应用中,经常会遇到所需处理的一批数据,并不具有完全相同的数据类型的特征。
例如处理有关学生的信息时,应该将某一位学生的姓名、性别、学号、年龄、课程成绩等作为一个有机整体来处理。
观察一下这些数据就会发现,虽然它们同是描述某一位学生情况的数据,但是,这一组数据是由多个具有不同类型的数据所组成,如表11.1所示。
表11.1学生信息的组成
学号
姓名
性别
年龄
成绩
字符串
字符串
字符
整型
实型
在处理这种形式的数据时,如果仅使用前面介绍过的几种基本数据类型来操作,那么只能将这些数据一一割裂开来单独地进行处理,这种处理方式会大大降低数据的处理效率。
为此,在C语言中引入了一种专门组织和处理复杂关系的数据类型,即结构体类型。
11.1.1结构体类型的定义
结构体类型定义的一般形式:
struct结构体名
{
类型名1成员名1;
类型名2成员名2;
......
类型名n成员名n;
};
其中,struct是结构体类型定义的关键字,是英文单词structure的缩写形式。
“结构体名”是用户自定义的结构体类型标识符,也称为结构体类型名。
“struct结构体名”作为一个整体与C语言的基本数据类型具有同样的地位和作用。
花括号中的结构体成员表定义了此结构体内所包含的每一个成员的类型,它们组成了一个结构体。
结构体名和结构体成员名的命名规则与简单变量名的命名规则相同。
注意:
在书写结构体类型定义时,不要忽略最后的分号。
例如,对表11.1所描述的数据形式可定义如下的结构体类型:
structstudent
{
charnum[10];
charname[20];
charsex;
intage;
floatscore;
};
经过以上定义,即向编译系统声明用户定义了一个“结构体类型”,结构体名为student,该结构体的全体成员包括num、name、sex、age和score,它们在结构体中被依次作了类型定义。
结构体成员的类型既可以是基本数据类型,也可以是构造类型或者指针类型。
结构体类型定义仅仅是对一种特定的数据结构的描述,用户为了建立和描述不同的数据结构模型,可以定义不同形式的结构体,并由不同的结构体名来标识。
结构体类型一旦建立,就规定了该结构体自身所占用存储空间的存储模型,除此之外,它不含有任何具体的数据内容,所以系统在编译此段代码时,并不为它分配实际的存储空间。
11.1.2结构体类型变量的定义
结构体类型变量(简称结构体变量)的定义有3种方式。
1.先定义结构体类型,再定义结构体变量
一般形式如下:
struct结构体名
{
结构体成员表;
};
struct结构体名结构体变量名表;
例如:
structstudent
{
charnum[10];
charname[20];
charsex;
intage;
floatscore;
};
structstudentstudent1,student2;
即在定义了结构体类型structstudent后,利用该类型定义两个变量student1与student2。
一旦定义了结构体变量,就按照结构体类型的组成,系统为定义的结构体变量分配内存单元。
结构体变量的各个成员在内存中占用连续存储区域,结构体变量所占内存大小为结构体中每个成员所占用内存的长度之和。
如上面定义的结构体变量student1在内存中的存储情况如图11.1所示(假定int型占用2个字节,如在TurboC中)。
num
10个字节
name
20个字节
sex
1个字节
age
2个字节
score
4个字节
student1共占37个字节
图11.1结构体变量的内存存储示意图
这里特别提醒注意的是:
在定义结构体类型时并不分配内存空间,只有在定义结构体变量后才分配实际的存储空间。
2.在定义结构体类型的同时定义结构体变量
一般形式如下:
struct结构体名
{
结构体成员表;
}结构体变量名表;
例如:
structstudent
{
charnum[10];
charname[20];
charsex;
intage;
floatscore;
}student1,student2;
即在定义结构体类型structstudent的同时定义了该类型的两个变量student1,student2。
3.在定义结构体类型时省略结构体名,直接定义结构体变量
一般形式如下:
struct
{
结构体成员表;
}结构体变量名表;
例如:
struct
{
charnum[10];
charname[20];
charsex;
intage;
floatscore;
}student1,student2;
这时student1,student2也称为匿名结构体类型变量。
注意:
在匿名结构体的定义中结构体变量名表是不能缺少的,并且在程序中不能再定义相同类型的其它结构体变量。
C语言也允许结构体中的某个成员是另一个结构体类型的变量,即结构体类型可以嵌套定义。
如表11.2也是学生信息的一种形式,它组成一个结构体,在它的内部有一个变量(出生日期)也自成一个结构体。
表11.2学生信息的组成
学号
姓名
性别
出生日期
成绩
字符串
字符串
字符
年
月
日
实型
整型
整型
整型
对表11.2的数据形式可以有如下的结构体类型定义:
structdate
{
intyear;
intmonth;
intday;
};
structstud
{
charnum[10];
charname[20];
charsex;
structdatebirthday;
floatscore;
};
也可以采用结构体类型的嵌套定义形式。
如:
structstud
{
charnum[10];
charname[20];
charsex;
structdate
{
intyear;
intmonth;
intday;
}birthday;
floatscore;
};
如有以下定义:
structstudstudent3;
则结构体变量student3在内存的存储情况如图11.2所示。
num
10个字节
name
20个字节
sex
1个字节
year
2个字节
month
2个字节
day
2个字节
score
4个字节
student3共占41个字节
birthday
图11.2结构体类型嵌套时的结构体变量内存存储示意图
11.1.3结构体变量的引用
结构体变量引用的一般方式为:
结构体变量名.成员名
“.”被称为成员运算符,它在所有的运算符中优先级最高。
所以可以把“结构体变量名.成员名”作为一个整体看待。
如:
student3.score=94.5;
strcpy(student3.name,"YeWenjuan");
如果成员变量又是结构体类型,必须一级一级地找到最低级成员变量,然后对其进行引用。
如:
student3.brithday.year=1985;
student3.brithday.month=2;
student3.brithday.day=20;
结构体变量的成员所能进行的运算与同类型的普通变量相同。
如:
studenr2.score=student1.score;
sum=student1.score+student2.score;
student1.age++;
scanf(“%d”,&student1.age);
同类型的结构体变量可以直接赋值。
如:
studenr2=student1;
11.1.4结构体变量的初始化
结构体变量的初始化是指在定义结构体变量的同时为其成员变量赋初值。
例11.1初始化结构体变量并输出相应的信息。
#include
structdate
{
intyear;
intmonth;
intday;
};
structstud
{
charnum[10];
charname[20];
charsex;
structdatebirthday;
floatscore;
};
main()
{
structstudstud1={"200608247","YeWenjuan",'F',{1988,3,15},94};
structstudstud2=stud1;
stud2.score=99;
printf("No.:
%s\n",stud1.num);
printf("name:
%s\n",stud1.name);
printf("sex:
%c\n",stud1.sex);
printf("birthday:
year:
%4dmonth:
%2dday:
%2d\n",stud1.birthday.year,
stud1.birthday.month,stud1.birthday.day);
printf("stud1score:
%5.1f\n",stud1.score);
printf("stud2score:
%5.1f\n",stud2.score);
}
程序运行结果如下:
No.:
200608247
name:
YeWenjuan
sex:
F
birthday:
year:
1988month:
3day:
15
stud1score:
94.0
stud2score:
99.0
在例11.1中先初始化结构体变量stud1,再利用已有值的结构体变量stud1去初始化另一个结构体变量stud2。
初始化数据的数据类型及顺序要和结构体类型定义中的结构体成员相匹配。
如果初始化数据中包含多个结构体成员的初值,则这些初值之间要用逗号分隔。
可以同时对多个结构体变量进行初始化,它们之间也是用逗号来分隔。
C语言规定,不能跳过前面的结构体成员而直接给后面的成员赋初值,但可以只给前面的成员赋初值。
这时,未得到初值的成员由系统根据其数据类型自动赋初值0(数值型)、‘\0’(字符型)或NULL(指针型)。
11.2结构体数组
结构体数组是指该数组中的每个数据元素都是一个结构体变量,且这些元素都具有相同的结构体类型。
结构体数组与普通数组一样,都是先定义,后引用。
结构体数组定义的一般形式:
struct结构体名数组名[常量表达式];
例如:
structstudent
{
charnum[10];
charname[20];
charsex;
intage;
floatscore;
};
structstudentstud[3];
就定义了一个有3个元素的结构体数组,它的每个元素都是structstudent类型。
引用结构体数组的方式则是引用数组与引用结构体的方式的结合,如:
stud[0].age=18;
stud[2].score=87.0;
结构体数组也可以进行初始化,结构体数组初始化方式类型于二维数组按行初始化方式,即用花括号将每个元素的初值括起来。
当数组元素全部赋初值时,结构体数组的定义长度也可以缺省。
如下面的初始化形式隐含其数组长度为3。
structstudentstud[]={{…},{…},{…}};
例11.2先定义一个学生信息结构体,包括学号、姓名、性别、出生日期及三门课的分数和总分,再定义三位学生信息的结构体数组并初始化,计算每位同学的总分,然后输出所有学生信息。
#include
#defineN3
structdate
{
intyear;
intmonth;
intday;
};
structstudent
{
charnum[10];
charname[20];
charsex;
structdatebirthday;
floatscore[4];
};
main()
{
structstudentstud[N]=
{{"200608247","YeWenjuan",'F',1988,3,15,75,90,94},
{"200606677","ChenYong",'M',1989,6,24,85,90,82},
{"200606688","WangNan",'F',1988,2,29,95,70,80}};
inti;
printf("No.NameSexBirthdayscore1score2score3total\n");
for(i=0;i{
stud[i].score[3]=stud[i].score[0]+stud[i].score[1]+stud[i].score[2];
printf("%s%s%c%4d%2d%2d%6.1f%6.1f%6.1f%6.1f\n",
stud[i].num,stud[i].name,stud[i].sex,
stud[i].birthday.year,stud[i].birthday.month,stud[i].birthday.day,
stud[i].score[0],stud[i].score[1],stud[i].score[2],stud[i].score[3]);
}
}
程序运行结果如下:
No.NameSexBirthdayscore1score2score3total
200608247YeWenjuanF198831575.090.094.0259.0
200606677ChenYongM198962485.090.082.0257.0
200606688WangNanF198822995.070.080.0245.0
11.3指向结构体的指针
11.3.1指向结构体变量的指针
一个结构体变量的指针就是该变量在内存所占用的存储空间的首地址,可以定义一个指针变量,用来指向一个结构体变量,此时该指针变量的值就是结构体变量的首地址。
指向结构体变量的指针变量定义的一般形式为:
struct结构体名*结构体指针变量名;
如:
structstudent*ps;
即定义一个指针变量ps,它可以指向结构体类型structstudent的变量。
例11.3指向结构体变量的指针变量的定义与应用。
#include
#include
structstudent
{
charnum[10];
charname[20];
charsex;
floatscore;
};
main()
{
structstudentstud1;
structstudent*p;
p=&stud1;
strcpy((*p).num,"200606688");
strcpy((*p).name,"WangNan");
(*p).sex='F';
(*p).score=80;
printf("No.:
%s\nname:
%s\nsex:
%c\nscore:
%g\n",
(*p).num,(*p).name,(*p).sex,(*p).score);
}
程序运行结果为:
No.:
200606688
name:
WangNan
sex:
F
score:
80
在程序中首先定义一个structstudent类型的变量stud1,同时又定义一个指针变量p,它指向一个structstudent类型的数据,并将结构体变量studl的首地址赋给指针变量p,也就是使p指向stul,然后通过指针变量p来实现对stul各成员的引用,见图11.3。
图11.3通过指针变量引用结构体的成员
通过指针变量引用结构体成员的一般形式为:
(*结构体指针变量名).结构体成员名
这种方式是一种间接引用方式。
如:
(*p).score是代表指针变量p指向的结构体变量中的成员score。
由于运算符“.”比运算符“*”的优先级高,所以,*p必须用圆括号括起来。
为了使通过指针变量引用结构体变量中的成员的方式更加直观,C语言还提供了另一种引用形式:
结构体指针变量名->结构体成员名
其中,“->”称为指向成员运算符。
因此,引用结构体变量成员的方式就有了3种表达形式:
①结构体变量名.结构体成员名
②(*结构体指针变量名).结构体成员名
③结构体指针变量名->结构体成员名
指向成员运算符与点运算符具有相同的优先级。
如果有:
structstudentstudent1;
structstudent*pStudent=&student1;
则下面三种形式等价:
①student1.score;②(*pStudent).score;③pStudent->score;
所以,例11.3程序中最后一个printf函数也可改为:
printf("No.:
%s\nname:
%s\nsex:
%c\nscore:
%g\n",p->num,p->name,p->sex,p->score);
11.3.2指向结构体数组的指针
像普通的指针变量可以指向普通的数组或数组元素一样,也可以设置一个结构体指针变量去指向同一类型的结构体数组或结构体数组元素。
例11.4指向结构体数组的指针变量的应用。
#include
structstudent
{
charnum[10];
charname[20];
charsex;
floatscore;
};
main()
{
structstudentstud[]={{"200608247","YeWenjuan",'F',94},
{"200606677","ChenYong",'M',82},
{"200606688","WangNan",'F',80}};
structstudent*p;
printf("\nNo.NameSexScore\n");
for(p=stud;p{
printf("%s%s%c%g\n",p->num,p->name,p->sex,p->score);
}
}
程序运行结果为:
No.NameSexScore
200608247YeWenjuanF94
200606677ChenYongM82
200606688WangNanF80
在程序中,定义了structstudent类型的指针变量,通过操作p=stud使得指针变量p指向了结构体数组stud的首地址,即指向stud[0]。
for语句每循环执行一次,执行p++操作使得指针变量p指向stud数组的下一个元素,如图11.4所示。
因为指针变量p是指向结构体数组stud的,stud数组的每一个元素占据的存储单元是35个字节(10+20+1+4=35),所以指针变量p做自加1操作时,就是将指针跳过一个数组元素的存储空间,即跳过35个字节而去指向下一个数组元素。
即如果指针变量p指向结构体数组stud的第一个元素的起始地址,则p+1将指向第二个元素的起始地址,p+2将指向第三个元素的起始地址。
图11.4指向结构体数组的指针变量
在对结构体类型指针变量进行操作时,要特别注意其含义与效果。
假定stud是一个structstudent类型的数组,p是指向stud起始地址的指针变量,则:
(1)++p->score表示把stud[0]的成员score自加1。
由于指向成员运算符->的优先级别高,所以,++p->score相当于++(p->score)。
(2)(++p)->score表示先使结构体指针变量p自加1,指向下一个数组元素的起始地址,再引用它所指向的数组元素的成员score的内容,即stud[1].score。
(3)(p++)->score表示先引用结构体指针变量p所指数组元素的成员score的内容,即stud[0].score,再对指针变量p作自加1操作,使该指针变量指向下一个数组元素stud[1]的起始地址。
11.4结构体与函数
1.结构体变量作函数参数
实参与形参的数据传递采用的是“值传递”方式,即将实参中结构体变量的值全部顺序传递给形参。
结构体变量也可以作为一个实参整体传递给相应的形参,当然,形参必须是与实参同类型的结构体变量。
在函数调用的执行期间,系统也要为形参分配与实参一样大小的存储空间。
例11.5建立一个学生的成绩单,包括学生学号、姓名、成绩。
要求在主函数中赋值,另一函数完成打印功能。
#include
structstudent
{
charnum[10];
charname[20];
floatscore;
};
voidprintstruct(structstudentst)
{
printf("%s%s%g\n",st.num,st.name,st.score);
}
main()
{
inti;
structstudentstud[]={{"200608247","YeWenjuan",94},
{"200606677","ChenYong",82},
{"200606688","WangNan",80}};
printf("\nNo.namescore\n");
for(i=0;i<3;i++)
{
printstruct(stud[i]);
}
}
程序运行结果为:
No.namescore
200608247YeWenjuan94
200606677ChenYong82
200606688WangNan80
在程序中,structstude