再上一篇:6.2 字符数组的定义及应用
上一篇:6.3 字符串处理函数
主页
下一篇:7.2 位域
再下一篇:7.3 共同体的定义及应用
文章列表

第七章 结构体、共同体和枚举类型

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

C++语言的导出数据类型除了前一章介绍的数组之外,还有结构体、共同体、枚举类型和类等。本章介绍结构体、共同体、枚举类型的定义方法及其应用。 7.1结构体的定义及应用

数组是同一类型的数据的集合,在实际应用中, 还常要将不同类型的数据作为一个整体来处理。如要描述一个学生的数据可以包括:学号、学生的姓名、性别、年龄、成绩等数据项,这些数据项属于不同的数据类型。姓名是字符串类型,而成绩是实型等。把由不同类型的数据作为一个整体的数据类型来处理,这种类型的数据称为结构体(structure)。

7.1.1 结构体类型的说明

结构体类型是一种导出的数据类型,编译程序并不为任何数据类型分配存储空间,只有定义了结构类型的变量时,系统才为这种变量分配存储空间。要定义结构体变量,必须先说明结构体类型。如描述上面的学生基本情况的结构体可以是:

struct student {

int id; //学号

char name[20]; //姓名

char sex; //性别

int age; //年龄

float eng, phy, math,poli; //分别为英语、物理、数学和政治的成绩};

定义一个结构体类型的一般格式为:

struct <结构体类型名> {

<类型名> <变量1>;

《<类型名> <变量2>; ......》

};

其中结构体类型名由标识符组成,在花括号中依次列举变量的类型和变量名,每一个变量的类型可以是基本类型或者是导出类型。把花括号中所定义的变量称为结构体的成员或分量。如前面定义的结构体student,它有八个成员,每一个成员的含义,已在注解中作了说明。

注意,在定义结构体的成员时,不能指定成员的存储类型的auto,register,extern,这是由于系统不为结构体类型分配任何存储空间,当然就不能指定其成员为以上三种存储类型之一。但可以指定成员的存储类型为static。如:struct test{

auto int i,j; //不正确,不能指定为自动存储类型

register int x; //不正确,不能指定为寄存器存储类型

extern int f; //不正确,不能指定为外部存储类型

static int y; //正确,可指定为静态存储类型

};

当结构体成员定义为静态存储类型时,其作用与用途在后面作说明。

在一个程序中,一旦定义了一个结构体类型,就增加了一种新的数据类型,也就可以用这种结构类型说明变量。

7.1.2 定义结构体类型的变量

结构类型的变量也与其它类型的变量一样,应该定义在前,使用在后。定义结构体类型,只是规定了结构体中成员的结构框架或存储格式,并不为结构体类型分配存储空间,在定义结构体变量时,编译程序按照结构体中所定义的存储格式为结构体变量分配存储空间。定义结构体类型变量的一般格式为:

《存储类型》 <结构体类型名> <变量名1>《,<变量名2>,......》;或者

《存储类型》 struct <结构体类型名> <变量名1>《,<变量名2>,......》;有struct与省略struct的作用是一样的。在本书中均采用前一种格式。如前面定义的结构体类型student,可定义student类型的变量:

student s1, s2[10];

这里定义s1为结构体student类型的变量,s2定义为student类型的数组,编译程序要为这二个变量分配存储空间。为s1分配的内存空间如图7.1所示。

id 4个字节

name 20个字节

sex 1个字节

age 4个字节

eng 8个字节

phy 8个字节

math 8个字节

poli 8个字节

图7.1 为s1分配存储空间的结构

实际上,定义结构体变量的方法有三种,下面通过例子说明之。

例 7.1 用三种方法定义结构体变量。

#include

#include

struct s1 {

char name[20];

char addr[40];

char tel_num[7];

int id;

};

struct s2 {

int id_no;

int eng, phy, math;

float ave;

} stu1, stu2; //A,定义结构体变量

main()

{

s1 r1,r2; //B,定义结构体变量

s2 r3, r4;

struct {

int year , month ,day;

} date1, date2; //C,定义结构体变量

strcpy( r1.name , "zhang sa");

strcpy( r1.addr , "Nangjin China");

r2 = r1 ;

cout <<"\n name = " <

stu1.eng = 90;

cout <<"\n eng = "<< stu1.eng;

date1.year = 1994, date1.month = 12 , date1.day = 27;

cout<<"\n"<

<'\n';

r3.eng = 100;

cout <<"\n r3.eng = "<< r3.eng<<'\n';

}

第一种定义结构体变量的方法是:先定义结构体的类型,再定义结构体的变量。如程序中的B行,定义了变量r1,r2;r1,r2为局部变量。第二种方法是在定义结构体类型的同时,直接定义结构体的变量。如程序中的A行定义了结构体变量stu1, stu2;stu1, stu2为全局变量。第三种方法是不定义结构体的类型名直接定义结构体变量。如程序中的C行,定义了结构体变量date1, date2,这二个变量也是局部变量。

由于前二种方法都定义了结构体的类型名,在程序中可用这类型名来定义其它的结构体变量;而第三种方法没有定义类型名,也就无法再定义这种类型的变量。我们建议使用第一种格式来定义结构体类型的变量。对于一个大的程序,通常,把所有定义的结构体类型集中存放在一个或几个头文件中,在程序文件中用到某一种结构体类型的变量时,包含相应的头文件。

结构体类型变量也存在作用域的问题,其作用域与前面介绍的一般变量相同,即在函数定义外定义的结构体变量为文件作用域;而在函数体内定义的变量为块作用域。同样地,定义的类名也存在作用域问题,与前面讨论的变量作用域相同。

注意,在定义结构体类型的变量时,可以指定其存储类型。如:

static s1 s5;

auto s1 stru2;

extern s1 stru3;

以上说明的结构体变量都是合法的。

与数组类同,在定义结构体变量时,可对结构变量进行初始化。其初始化的方法是用花括号将每一个成员的值括起来。如:

s1 a3={"张 三","南京","445681",35};

表示a3的成员name初始化为"张 三",成员addr 初始化为"南京",成员tel_num初始化为"445681", 成员id的值为 35。注意,在初始化时,在花括号中列出的值的类型及顺序必须与该结构体类型的定义中所说明的结构体成员一一对应。如说明:

s1 a4={"张 三","南京",445681,35};

则编译给出警告信息,且s4的成员tel_num和id的初始化值都不正确了。又如:

s1 a5={"张 三","南京","4456819",35};

则给出编译错误,因a5的成员tel_num只能存放7个字符,而字符串"4456819"要占用8个字符的位置(字符串结束符要占用一个字符的位置)。

7.1.3 结构体变量及其成员的使用

使用结构体变量的成员的方法是:在结构体变量后指明要访问的成员名,其一般格式为:

结构体变量名.成员名

其中“.”称为成员运算符。在例7.1中已多次使用了这种方法来对结构体变量的成员进行赋值或输出。

关于结构类型变量的使用,说明以下几点:

1、同类型的结构体变量之间可以直接赋值。这种赋值等同于各个成员的依次赋值。如定义如下的结构体类型:

struct test{

int m,n ;

char name[20];

};

test s1={45,100,”zhang ming”}, s2,s3;

s2=s1;

其中s2=s1;等同于:

s2.m=s1.m;

s2.n=s1.n;

strcpy(s2.name,s1.name);

2、结构体变量不能直接进行输入输出,它的每一个成员能否直接进行输入输出,取决于其成员的类型,若是基本类型或是字符数组,则可以直接输入输出。如:

cin>>s3; //错误的

cin>>s3.m>>s3.name; //正确

3、结构体变量可以作为函数的参数,函数也可以返回结构体的值。当函数的形参与实参为结构体类型的变量时,这种结合方式属于值调用方式,即属于值传递,只能用作输入参数,在函数中能使用结构体成员的值或结构体变量的值;但不能用作输出参数,不能将在函数内修改后的结构体成员值作为参数返回。

例 7.2 结构体变量用作函数的参数。

#include

struct s {

int m;

float x;

};

void swap( s s1,s s2)

{

s t;

t=s1; s1=s2;

s2=t;

}

s fun(s s1,s s2)

{

s t;

t.m=s1.m+s2.m;

t.x=s1.x+s2.x;

return t;

}

void main(void )

{

s r1={100,250.5} ,r2={200, 350.5};

swap(r1,r2); //A

cout<<"r1.m="<

cout<<"r2.m="<

s sum;

sum=fun(r1,r2);

cout<<"sum.m="<

}

执行程序后的输出为:

r1.m=100 r1.x=250.5

r2.m=200 r2.x=350.5

sum.m=300 sum.x=601

A行调用了函数swap( ),在该函数内对二个结构体变量的值作了交换,由于属于值调用方式,输出r1和r2的成员值仍为原来的值。

7.1.4 结构体数组

数组是相同类型的数据元素的集合,当然数据元素的类型也可以是结构体。由结构体类型的元素组成的数组称为结构体数组。定义结构体数组的方法与定义结构体变量的方法完全类同,也可有三种方法,只要在每一种方法的基础上,增加维数的说明。如:

struct s {

int id_no;

int eng, phy, math;

float ave;

} stu1[10], stu2[20][50];

分别定义了一维数组stu1和二维数组stu2。

在说明结构体数组时,也可对它进行初始化。其方法与数组类同,一种方法是:将每一个元素的成员值用花括号括起来,再将数组的全部元素值用一对花括号括起来。另一种方法是:在一个花括号内依次列出各个元素的成员值。

例 7.3 用二种方法初始化结构体数组。

#include

struct s {

int id;

int eng, math;

} ;

void main(void )

{

s r[3]={{15,60,70},{10,100,100}};

s rr[2]={1,60,30,4,80,90};

for(int i=0;i<3;i++)

cout<

for( i=0;i<2;i++)

cout<

}

执行程序后的输出为:

15 60 70

10 100 100

0 0 0

1 60 30

4 80 90

程序中对数组r置初值时,采用了第一种方法,并且仅对前二个元素置初植,另一个元素的各个成员的初值为0。对数组rr采用了第二种方法置初值。建议使用第一种方法给结构体数组置初值。

例 7.4 建立一个学生档案的结构体数组,描述一个学生的信息包括:姓名、性别、年龄和课程C++的成绩。并输出已建的学生档案。

#include

struct student {

char name[10];

char sex[4];

int age;

float cScore;

} ;

student Input( student x)

{

cout <<"输入姓名,性别、年龄和课程C++的成绩:";

cin >>x.name >>x.sex>>x.age >>x.cScore;

return x;

}

void Output(student x)

{

cout <

}

void main(void)

{

student sts[5];

for(int i=0;i<5;i++)

sts[i]=Input(sts[i]);

for(i=0;i<5;i++)

Output(sts[i]);

cout <<'\n';

}

在本例中,函数Input( )实现输入一个结构体变量的值,并返回已输入值的结构体变量。函数Output( )输出一个结构体变量的成员值。这二个函数的形参与实参都为结构体类型的变量,由于这种形参与实参的结合是属于值调用方式,其效率比较低。若把形参改为引用类型的结构体变量,程序的运行效率要高一些,即系统的开销要小一点。改为引用调用方式后,程序为:

#include

struct student {

char name[10];

char sex[4];

int age;

float cScore;

} ;

void Input( student &x)

{

cout <<"输入姓名,性别、年龄和课程C++的成绩:";

cin >>x.name >>x.sex>>x.age >>x.cScore;

}

void Output(student &x)

{

cout <

}

void main(void)

{

student sts[5];

int i;

for (i=0;i<5;i++) Input(sts[i]);

for (i=0;i<5;i++) Output(sts[i]);

cout <<'\n';

}

该程序与前一个程序相比,不但效率提高了,而且程序也紧凑了。7.1.5 结构体类型的静态成员

当把结构体类型中的某一个成员的存储类型定义为静态(static)时,表示在这种结构类型的所有变量中,编译程序为这个成员只分配一个存储空间,即这种结构体类型的所有变量共同使用这个成员的存储空间。

例 7.5 静态成员的初始化及其使用。

#include

struct s{

static int id;

int eng;

};

int s::id=50; //A

s s1;

void main(void )

{

s s2,s3;

cout<<"s1.id="<

s2.id=200;

cout<<"s2.id="<

cout<<"s1.id="<

s3.id=400 ;

cout<<"s3.id="<

cout<<"s1.id="<

cout<<"s2.id="<

}

执行这程序后,输出:

s1.id=50

s2.id=200

s1.id=200

s3.id=400

s2.id=400

s1.id=400

程序中把成员id的存储类型定义为静态的。从例中可以看出,s1.id,s3.id,s2.id使用的是同一个变量(同一个存储空间),当改变s3.id的值为400时,s1.id,s2.id的值也改为400。

在结构体中说明的静态成员属于引用性说明,必须在文件作用域中的某一个地方对静态的成员进行定义性说明,且仅能说明一次。程序中的A行就是对成员id的定义性说明,在定义性说明时也可以对该成员进行初始化。A行使id的初值置为50。当A行写出为:

int s::id;

时,则id的初值为0(静态变量的缺省初值均为0)。A行中的“::”称为作用域运算符,它表示id是属于结构体类型s的成员。对结构体的静态成员进行定义性说明的一般格式为:

<类型> <结构体类型名>::<静态成员名>;

其中类型要与在结构体中定义该成员的类型一致,结构体类型名指明静态成员属于哪一个结构体。该说明语句的作用是:为该静态的成员分配存储空间,并为其置初值。

最后要说明的是:结构体中的成员可为已定义的任意类型,当然也可以是结构类型的成员如:

struct date{

int year, month, day;

};

struct s{

char name[20];

int id;

date d1;

};

s s1;

......

s1.d1.year=1999; s1.d1.day=2;

当要访问嵌套在内层的结构体成员时,同样是使用嵌套的成员运算符“.”来实现的。在结构体中嵌套结构体的次数没有限制。