第十四章结构体共用体和用户定义类型.docx
- 文档编号:5898917
- 上传时间:2023-01-02
- 格式:DOCX
- 页数:29
- 大小:58.52KB
第十四章结构体共用体和用户定义类型.docx
《第十四章结构体共用体和用户定义类型.docx》由会员分享,可在线阅读,更多相关《第十四章结构体共用体和用户定义类型.docx(29页珍藏版)》请在冰豆网上搜索。
第十四章结构体共用体和用户定义类型
第十四章结构体、共用体和用户定义类型
到目前为止,我们已经介绍了C语言中的基本类型(整型、字符型、实型、双精度型和空值型)以及派生类型(指针和数组)。
本章将介绍在C语言中可由用户构造的三种数据类型。
它们是:
1.用户定义类型(typedef):
对已有的类型,另外说明一个新的类型标识符。
2.结构体(struct):
把具有相互关系的不同类型的数据组成一个有机的整体。
3.共用体(union):
又称联合体。
使几种不同类型的变量共用一段存储空间。
14.1用typedef说明一种新类型名
C语言允许用tyhpedef说明一种新类型名,说明新类型名的语句一般形式为:
typedef类型名标识符
在此,“类型名”必须是在此语句之前已有定义的类型标识符。
“标识符”是一个用户定义标识符,用作新的类型名。
Typedef语句的作用仅仅是用“标识符”来代表已存在的“类型名”,并未产生新的数据类型。
原有类型名依然有效。
例如:
typedefintINTEGER;
该语句把一个用户命名的标识符INTEGER说明成了一个int类型的类型名。
在此说明之后,可以用标识符INTEGER来定义整型变量。
例如:
INTEGERm,n;等价于intm,n;
也就是说:
INTEGER是int的一个别名。
为了便于识别,一般习惯将新的类型名用大写字母表示。
又如:
typedefchar*CHARP;
CHARPp;
等价于char*p;
初学者可能会感到,上述说明形式似乎不够直观,不好理解。
下面以此为例来解释说明一个新类型名的具体步骤:
1.首先按通常定义变量的方法写出定义的主体:
char*p;
2.将变量名换成新类型名:
char*CHARP;
3.在最左面加上关键字typedef:
typedefchar*CHARP;
4.可以用新类型名定义变量了:
CHARPP;
14.2结构体类型
结构体是一种较为复杂但却非常灵活的构造型数据类型。
一个结构体类型可以由若干个称为成员(或域)的成分组成。
不同的结构体类型可根据需要,由不同的成员组成。
对于某个具体的结构体类型,成员的数量必须固定,这一点与数组相同;但该结构体中各个成员的类型可以不同,这是结构体与数组的重要区别。
因此,当需要把一些相关信息组合在一起时,采用结构体这种类型就很方便。
例如,我们常用的“日期”可由以下三部分描述:
年(year)、月(month)、日(day )。
它们都可以选用整型数表示。
可以把这三个成员组成一个整体,并给它取名为data,这就是一个最简单的结构体。
再以学生档案为例,假设包括如下数据项:
姓名(name):
字符串
性别(sex):
字符型
出生日期(birthday):
date结构体
四门课成绩(sc):
一维实型数组
可以将这四个成员组成一个名为student的整体,这就构成了一个稍复杂些的结构体类型。
显然,这些数据之间有着相互关连的关系,只有结合起来看才有实用价值。
14.2.1结构体类型的说明
结构类型说明的一般形式为:
struct结构体标识符
{类型名1结构成员名表1;
类型名2结构成员名表2;
|
类型名n结构成员名表n;
};
其中struct是关键字,是结构体类型的标志。
“结构体标识名”和“结构成员名”都是用户定义的标识符。
其中“结构体标识名”是可选项,在说明中可以不出现。
每个“结构成员名表”中都可以含有多个不同类型有成员名,它们之间以逗号分隔。
结构体中的成员名可以和程序中的其它变量同名;不同结构体中的成员也可以同名。
注意:
结构体说明同样要以分号(:
)结尾。
依此格式,上述关于日期的结构体类型可以说明如下:
structdate
{intyear,month,day;};
结构体类型说明中的“类型名1~类型名n”,不仅可以是简单数据类型,也可以是构造类型,当然也可以是某种结构体类型。
当结构体说明中又包含结构体时称为结构体的嵌套。
例如,关于上述学生档案的结构体类型可以说明如下:
structstudent
{charname[12];
charsex;
structdatebirthday;
floatsc[4];
};
以上说明中,birthday成员的类型structdate是一个已说明过的结构体类型。
若没有事先说明这一类型,以上结构体类型说明可改写成如下形式:
structstudent
{charname[12];
charsex;
struct
{intyear;
intmonth;
intday;
}birthday;
floatsc[4];
};
ANSIC标准规定结构体至少允许嵌套15层,并且允许内嵌套结构体成员的名字与外层成员的名字相同。
结构体类型的说明只是列出了该结构的组成情况,标志着这种类型的结构“模式”已存在,编译程序并没有因此而分配任何存储空间。
真正占有存储空间的仍应是具有相应结构类型的变量、数组以及动态开辟的存储单元,只有这些“实体”才可以用来存放结构体的数据。
因此,在使用结构体变量、数组或指针变量之前,必须先对这些变量、数组或指针变量进行定义。
14.2.2可以用以下四种方式定义结构体类型的变量、数组和指针变量:
1.紧跟在结构体类型说明之后进行定义。
例如:
structstudent
{charname[2]
charsex;
structdatebirthday;
floatsc[4];
}std,pers[3],*pstd;
此处,在说明结构体类型structstudent的同时,定义了一个结构体变量std、具有3个元素的结构体数组pers和基类型为结构体类型的指针变量pstd。
变量std的结构如图14.1所示。
namesexbirthdaysc[0]sc[1]sc[2]sc[3]
yearmonthday
图14.1
具有这一结构类型的变量中只能存放一组数据即一个学生的档案)。
结构体变量中的各成员在内存中按说明中的顺序依次排列。
如果要存放多个学生的数据,就要使用结构体类型的数组。
以上定义的数组pers就可以存放三名学生的档案。
它的每一个元素都是一个structstudent类型的变量,仍然符合数组成部分元素属同一数据类型这一原则。
以上定义的指针变量pstd可以指向具有structstudent类型的存储单元,但目前还没有具体的指向。
2.在说明一个无名结构体类型的同时,直接进行定义。
例如:
以上定义的结构体中可以把student略去,写成:
struct
{
|
}std,pers[3],*pstd;
这种方式与前一种的区别仅仅是省去了结构体标识名。
通常用在不需要再次定义此类型结构变量的情况。
3.先说明结构体类型,再单独进行变量定义。
例如:
structstudent
{
|
};
structstudentstd,pers[3],*pstd;
此处先说明了结构体类型structstudent;再由一条单独语句定义了变量std、数组pers和指针变量pstd。
使用这种定义方式应注意:
不能只使用struct而不写结构体标识名student,因为struct不象int、char可以唯一的标识一种数据类型。
作为构造类型,属于struct类型的结构体可以有任意多种具体的“模式”,因此struct必须与结构体标识名共同来说明不同的结构体类型。
也不能只写结构体标识名student而省掉struct。
因为student不是类型标识符,由关键字struct和student一起才能唯一地确定以上所说明的结构体类型。
4.使用typedef说明一个结构体类型名,再用新类型名来定义变量。
例如:
typedefstruct
{charname[12];
charsex;
strucdatebirthday;
floatsc[4];
}STREC;
STRECstd,pers[3],*pstd;
此处STREC是一个具体的结构体类型名。
它能够唯一的标识这种结构体类型。
因此,可用它来定义变量,如同使用int、char一样,不可再写关键字struct。
14.2.3给结构体变量、数组赋初值
和一般的变量、数组一样,结构体变量和数组也可以在定义的同时赋初值。
1.结构体变量赋初值
所赋初值顺序放在一对花括号中,例如:
structstudent
{charname[12];
charsex;
structdatebirthday;
floatsc[4];
}std={“LiMing”,’M’,1962,5,10,88,76,85.5,90};
赋初值后,变量std的内容如图14.2所示。
namesexbirthdaysc[0]sc[1]sc[2]sc[3]
yearmonthday
LiMing
M
1962
5
10
88.0
76.0
85.5
90.0
图14.2
对结构体变量进行赋初值时,C编译程序按每个成员在结构体中的顺序一一对应赋初值;不允许跳过前边的成员给后面的成员赋初值;但可以只给前面的若干个成员赋初值,对于后面未赋初值的成员,对于数值型和字符型数据,系统自动赋初值零。
2.给结构体数组赋初值
给结构体数组赋初值的规则与第九章中介绍的相同。
只是由于数组中的每个元素都是一个结构体,因此通常将其成员的值依次放在一对花括号中,以便区分各个元素。
例如:
structbookcard
{cahrnum[5]
floatmoney;
}bk[3]={{“NO.1”,35.5},{“NO.2”,25.0},{NO.3”,66.7}};
/*bk[0]bk[1]bk[2]*/
也可以通过这种赋初值的方式,隐含确定结构体数组的大小。
即:
由编译程序根据所赋初值的成员个数决定数组元素的个数。
以下是一个给二维数组赋初值的例子:
struct
{charch;
inti;
floatx;
}arr[2][3]={{{‘a’,1,3e10},{‘a’,2,4e10},{‘a’,3,5e10}},/*第一行*/
{{‘b’,1,6e5},{‘b’,2,7e5},{‘b’,3,8e5}}/*第二行*/
};
根据花括号的嵌套关系,可以清楚地分辨出所赋初值与数组元素一一对应的关系。
14.2.4引用结构体变量中的数据
在老版本的C语言中,不允许对结构体变量进行整体操作。
因此,对结构体类型变量中数据只能按成员逐个进行操作。
新的ANSIC标准增加了对结构体类型变量的整体赋值操作。
下面将分别给予介绍。
1.对结构成员的引用
若已定义了一个结构体变量,和基类型为同一结构体类型的指针变量,并使该指针指向同类型的变量,则可用以下三种形式来引用结构体变量中的成员。
结构体变量名也可以是已定义的结构体数组的数组元素:
(1)结构体变量名.成员名
(2)指针变量名->成员名
(3)(*指针变量名).成员名
其中点号(.)称为成员运算符:
箭头(->)称为结构指向运算符,它由减号(-)和大于号(>)两部分构成,它们之间不得有空格;在第三种形式中,一对圆括号不可少。
这些运算符与圆括号、下标运算符的优先级相同,在C语言的运算中优先级最高。
例如有如下定义和语句:
structstudent
{charname[12];
charsex;
structdatebirthday;
floatsc[4];
}std,arr[5],*ps;
ps=&std;
(1)若要引用结构体变量std中的sex成员,可写作:
std.sex/*通过结构体变量引用*/
ps->sex/*通过指针变量引用*/
(*ps).sex/*通过指针变量引用*/
注意,这时指针变量ps已指向确切的存储单元;最后一种形式中的一对圆括号不可以省略,若把(*ps).sex写成:
*ps.sex,由于点号的优先级高于星号,因此,这相当于*(ps.sex),显然这是非法的,因为sex不是一个指针变量。
若要引用结构体数组arr的第0个元素arr[0]中的sex成员,可写作arr[0].sex。
注意:
不能写作arr.sex,因为arr是一个数组名。
(2)若要引用结构体变量std中、数组成员sc中的元素sc[2],可写作std.sc[2]或ps->sc[2]或(*ps).sc[2]。
不能写成std.sc,因为sc是一个数组名,C语言不允许对数组整体访问(字符串除外),只能逐个引用其元素。
对于结构体数组arr可写作arr[0].sc[2]。
(3)若结构体变量中的成员是作为字符串使用的字符型数组,如结构体中的成员name,由于可以将其看作“字符串变量”,因此其引用形式可以是std.name或ps->name或(*ps).name或arr[0].name。
(4)内嵌结构体变量成员的引用
访问结构体变量中各内嵌结构体成员时,必须逐层使用成员名定位。
例如,引用结构体变量std中的出生年份时,可写作std.birthday.year、ps->birthday.year、(*ps).birthday.year。
引用结构体数组arr第0个元素arr[0]中的出生年份时,可写作arr[0].birthday.year。
注意:
birthday后面不能使用->运算符,因为birthday不是指针变量。
对于多层嵌套的结构体,引用方式与此类似,即按照从最外层到最内层的顺序逐层引用,每层之间用点号隔开。
2.对结构体变量中的成员进行操作
结构体变量中的每个成员都属某个具体的数据类型。
因此,对结构体变量中的每个成员,都可以象普通变量一样,对它进行同类变量所允许的任何操作。
例如:
变量std中的成员std.name是字符串型,可以对它进行对任何字符串允许的操作,包括输入输出。
若有定义:
structstudentstd,pers[5],*pstd;和语句pstd=&std;
(1)则以下对相应变量中的name成员所进行的操作都是合法的:
①scanf(“%s”,std.name);或gets(std.name);
②pstd=&std;scanf(“%s”,pstd->name);或gets(pstd->name);
③for(i=0;i<3;i++)scanf)”%s”,pers[i].name);
④strcpy(std.name,”LiMing”);
注意:
不能写成:
std.name=”LiMing”;因为成员name为字符数组,不能直接用赋值语句赋字符串。
(2)以下对相应变量中的sex成员进行操作:
①scanf(“%c”,&std.sex);或std.sex=getchar();
②pstd=&std;
scanf(“%c”,&pstd->sex);或std->sex=getchar();
③for(i=0;i<3;i++)scanf(“%c”,&pers[i].sex);
④std.sex=’M’;
(3)以下对相应变量中、birthday成员中的year进行操作:
①scanf(“%d”,&std.birthday.year);
②pstd=&std;
scanf(“%d”,&pstd->birthday.year);
③for(i=0;i<3;i++)scanf(“%d”,&pers[i].birthday.year);
④std.birthday.year=1962;
(4)以下对相应变量中、成员数组sc中的元素进行操作:
①for(j=0;j<5;j++)scanf(“%f”,&std.sc[j]);
②pstd=&std;
for(j=0;j<5j++)scanf(“%f”,&pstd->sc[j]);
③for(i=0;i<3;i++)
for(j=0;j<5;j++)
{scanf(“%f”,&std.sc[j]);
pers[i].sc[j]=std.sc[j];
}
#include
structdate
{intyear,month,day;};
structstudent
{charname[12];
charsex;
structdatebirthday;
floatsc[4];
}std,pers[5],*ps;
voidmain()
{
inti,j;
for(i=0;i<5;i++)
for(j=0;j<4;j++)
{//scanf("%f",&std.sc[j]);
//pers[i].sc[j]=std.sc[j];
scanf("%f",&(pers[i].sc[j]));
}
for(i=0;i<5;i++)
{for(j=0;j<4;j++)
{
printf("%f",pers[i].sc[j]);
}
printf("\n");
}
}
3.当通过指针变量来引用结构体成员,并且与++、--等运算符组成表达式时,应当根据运算符的优先级别来确定表达式的含义。
例如,若有以下说明和定义:
struct
{inta;
char*s;
}s,*p=&x;
且变量x的成员a、指针成员s和已正确赋值,则表达式:
++p->a,使得a增1,而不是p增1,因为运算符->的优先级高++,表达式等价于++(p->a)。
如果要在访问a之前使p增1,应当写成:
(++p)->a。
表达式(p++)->a表达式(p++)->a,与p++->a等价,在访问了p所指变量x中的a成员之后,指针p增1。
同理,表达式*p->s,引用的是变量x中s所指存储单元:
*p->s++是在引用了s所指存储单元之后,使指针s增1;表达式(*p->s)++,使得s所指向的存储单元的值增1;而*p++->s在访问了s所指存储单元之后,使p增1。
4.相同类型结构体变量之间的整体赋值
新的ANSIC标准允许相同类型的结构体变量之间进行整体赋值。
设有定义:
struct
{charname[10];
intnum;
}per1,per2={“YANGGM”,46};
执行赋值语句:
per1=per2;后,per2中每个成员的值都赋给了per1中对应的同名成员。
这种赋值方法很简洁,但必须保证赋值号两边结构体变量的类型相同。
14.2.5函数之间结构体变量的数据传递
老版本的C标准中,在调用函数时不允许将结构体变量作为实参进行整体传送,而只能逐一传递结构体变量中的成员。
新的ANSIC标准取消了这一限制。
下面将对传递的具体形式分别给予介绍。
1.向函数传递结构体变量的成员
在前面曾指出:
结构体变量中的每个成员可以是简单变量、数组或指针变量等,作为成员变量,它们可以参与所属类型允许的任何操作。
这一原则在参数传递中仍适用。
2.向函数传递结构体变量
新的ANSIC标准允许把结构体变量作为一个整体传送给相应的实参。
这时传递的是实参结构体变量的值,系统将为结构体类型的形参开辟相应的存储单元,并将实参中各成员的值赋给对应的形参成员。
使用结构体变量作实参时,由于结构体变量中往往含有较多成员,不象普通变量那样单一,系统要为相应的形参开辟一片存储区,并一一对应传递各成员的数据;为了在返回后取用,系统同时要为保存结构体参数中所有成员的当前值而进行一系列内部操作;所有这些,都将势必增加系统的处理时间,影响程序的执行效率。
结构体变量作实参时,传递给函数对应形参的是它的值,函数体内对形参结构体变量中任何成员的操作,都不会影响对应实参中成员的值。
从而保证了调用函数中数据的安全。
但这也限制了将运算结果返回给调用函数。
3.传递结构体的地址
C语言的新、老版本都允许将结构体变量的地址作为实参传递。
这时,对应的形参应该是一个基类型相同的结构体类型的指针。
系统只需为形参指针开辟一个存储单元存放实参结构体变量的地址值,而不必另行建立一个结构体变量。
这样既可以减少系统操作所需的时间,提高程序的执行效率;又可以通过函数调用,有效地修改结构体中成员的值。
例14.1通过函数给结构体成员赋值。
#include
typedefstruct
{chars[10];
intt;
}ST;
getdata(ST*p)/*形参为结构体类型的指针变量*/
{scanf("%s%d",p->s,&p->t);}
main()
{STa;
getdata(&a);/*结构体变量的地址作实参*/
printf("%s,%d\n",a.s,a.t);
}
4.函数的返回值是结构体类型
例14.2通过函数返回结构体类型的值。
typedefstruct
{inta;
charb;
}ST;
STfun(STx)
{x.a=99;x.b=’S’;returnx;}
main()
{STy;
y.a=0;y.b=’A’;
printf(“y.a=%dy.b=%c\n”,y.a,y.b);
y=fun(y);
printf(“y.a=%dy.b=%c\n”,y.a,y.b);
}
程序的运行结果为:
y.a=0y.b=A
y.a=99y.b=S
5.函数的返回值可能是指向结构体变量的指针类型
例14.3通过函数的返回值返回指向结构体变量的指针
typedefstruct
{inta;
charb;
}ST;
ST*fun(ST*x)
{ST*px;
(*x).a=100;(*x).b=’C’;
px=x;
returnpx;
}
main()
{STy,*p;
y.a=999;y.b=’X’;
printf(“y.a=%dy.b=%c\n”,y.a,y.b);
p=fun(&y);
printf(“(*p).a=%d(*p).b=%c\n”,(*p).a,p->b);
}
程序的运行结果为:
y.a=999y.b=x
(*p).a=100(*p).b=C
例14.4读入五位用户的姓名和电话号码,按姓名的字典顺序排列后,输出用户的姓名和电话号码。
程序中说明了一个名为USER的结构体类型,该结构体包含了两个字符串变量用以存放姓名和电话号码。
getdata函数读入五位用户的姓名和电话号码。
getsort函数把数据按姓名的字典顺序排列。
outdata输出最后的结果。
#include"stdio.h"
#include"string.h"
#defineN5
typedefstruct
{charname[20];
charnum[10];
}USER;
getdata(USER*sp)
{inti;
printf("Entername*phonenumber:
\n");
for(i=0;i
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第十四章 结构体共用体和用户定义类型 第十四 结构 共用 用户 定义 类型