结构体、共用体及枚举类型

定义一种类型,把不同的数据作为一个整体来处理——结构体

一、 结构体的定义及应用

(一) 结构体类型的定义

0
其中的数据类型可以为简单类型,也可以为构造类型
结构体类型的名字是由一个关键字 struct 和结构体名组合而成的(例如 struct Student)。结构体名是由用户指定的,又称 结构体标记 (structure tag) ,以区别于其他结构体类型。上面的结构体声明中 Student 就是结构体名(结构体标记)
结构体类型不分配任何存储空间;相应结构体类型的变量、数组及动态开辟的存储单元占存储空间。

(二) 结构体类型变量的定义

1. 先声明结构体类型,再定义该类型的变量

声明一个结构体类型 struct Student ,可以用来定义变量。例如:

struct Student
{
int num; // 学号为整型
char name[20]; // 姓名为字符串
char sex; // 性别为字符串
int age; // 年龄为整型
float score; // 成绩为实型
char addr[30]; // 地址为字符串
}; //注意最后有一个分号
struct Student student1 , student2;
//结构体类型名 结构体变量名

这种形式是声明一个结构体类型的一般形式

struct 结构体名
{
成员表列
};
类型名 成员名;
int a , b;
2. 在声明类型的同时定义变量
struct Student
{
int num; // 学号为整型
char name[20]; // 姓名为字符串
char sex; // 性别为字符串
int age; // 年龄为整型
float score; // 成绩为实型
char addr[30]; // 地址为字符串
}student1 , student2 ; // 注意最后有一个分号
struct Studentstruct Studentstudent1 , student2
struct 结构体名
{
成员表列
}变量名表列;

声明类型和定义变量放在一起进行,能直接看到结构体的结构,比较直观,在写小程序时用此方法比较方便,但写大程序时,往往要求对类型的声明和变量的定义分别放在不同的地方,以使程序结构清晰,便于维护,所以一般不多用这种方式。

3.不指定类型名而直接定义结构体类型变量

其一般形式为

struct
{
成员表列
}变量名表列;

指定了一个无名的结构体类型,它没有名字(不出现结构体名)。显然不能再以此结构体类型去定义其他变量。这种方式用得不多。

(三) 结构体变量的初始化和引用

在定义结构体变量时,可以对它初始化,即赋予初始值。然后可以引用这个变量,例如:

struct Student // 声明结构体类型 struct Student
{
long int num; // 以下4行为结构体的成员
char name[30];
char sex;
char addr[20];
}a = {10101,“Qiao Yi Bo Yi”,‘M’,“BeiJing”}; // 定义结构体变量 a 并初始化
printf(“NO.:%ld\nname:%s\nsex:%c\naddress:%s\n”,a.num,a.name,a.sex,a.addr);

变量引用:

(1) 在定义结构体变量时可以对它的成员初始化

初始化列表是用花括号括起来的一些常量,这些常量依次赋给结构体变量中的各成员。
注意:是对结构体变量初始化,而不是对结构体类型初始化。
C99 标准允许对某一成员初始化,如:
struct Student b = {.name = " Qiao Yi Bo Yi"} ; // 在成员名前有成员运算符 " . "
“ .name” 隐含代表结构体变量b中的成员 b.name 。

其他未被指定初始化的数值型成员被系统初始化为 0 ,字符型成员被系统初始化为’\0’ ,指针型成员被系统初始化为 NULL 。**

(2) 可以引用结构体变量中成员的值

引用方式为
结构体变量名.成员名
student1.num = 10010;
“ . ” 是成员运算符,它在所有的运算符中优先级最高,因此可以把 student1.num 作为一个整体来看待,相当于 一 个变量。上面赋值语句的作用是将整数10010赋给student1 变量中的成员 num 。
注意不能企图通过输出结构体变量名来达到输出结构体变量所有成员的值。

下面用法不正确:
printf(“%s\n”,student1); // 企图用结构体变量名输出所有成员的值
只能对结构体变量中的各个成员分别进行输入和输出。

(3)只能对最低级的成员进行赋值或存取以及运算。
struct Studentstruct datebirthday
student1.num (结构体变量 student1 中的成员 num)
student1.birthday.month (结构体变量 studebt1 中的成员 birthday 中的成员 month)
student1.birthdaystudent1birthdaybirthday
(4) 对结构体变量的成员可以像普通变量一样进行各种运算

(根据其类型决定可以进行的运算)
例如:

student2.score = student1.score; //(赋值运算)
sum = student1.score + student2.score; //(加法运算)
student1.age++// (自加运算)

由于 “ . ” 运算符的优先级最高,因此student1.age++是对(student1.age)进行自加运算,而不是先对age进行自加运算。

(5) 同类的结构体变量可以互相赋值

(整体引用仅限赋值)
如:

`student1 = student2 ;` // 假设 student1 和 student2 已定义为同类型的结构体变量
(6) 可以引用结构体变量成员的地址,也可以引用结构体变量的地址。

例如:

scanf(“%d”,&student1.num); //(输入student1.num的值)
printf(“%o”,&student1);// (输出结构体变量 student1 的起始地址)

不能用以下语句整体读入结构体变量,即不能将结构体变量作为一个整体进行输入/输出,例如:

scanf(“%d,%s,%c,%d,%f,%s\n”,&student1);//错误的
printf("%d %s %c %d %f\n", stu1);//错误的


//正确的

说明: 结构体变量的地址主要用作函数参数,传递结构体变量的地址。

二、 使用结构体数组

一个结构体变量中可以存放一组有关联的数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是 结构体数组。 结构体数组与以前介绍过的数值型数组的不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。

(1) 定义结构体数组一般形式是

 struct 结构体名
{成员表列} 数组名[数组长度];

先声明一个结构体类型(如 struct Person) ,然后再用此类型定义结构体数组:
结构体类型 数组名[数组长度];
如:

struct Perdson leader[3]; //leader 是结构体数组名

(2) 对结构体数组初始化的形式

是在定义数组的后面加上:= {初始表列};
如:

struct Person leader[3] = {“LI”,0,“Zhang”,0,“Sun”,0};

三、 结构体指针

所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。

1.指向结构体变量的指针

指向结构体对象的指针变量既可指向结构体变量,也可以指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型相同。
例如:

`struct Student *pt; // pt` 可以指向 `struct Student` 类型的变量或数组元素

说明: 为了使用方便和直观,C语言允许(*p).num 用 p->num 代替,“ -> ” 代表一个箭头,p->num 表示p 所指向的结构体变量中的 num成员。同样,(*p).name等价于p->name。 “ -> ” 称为指向运算符。(p).name和p->name是等价的;( p).name和p.name是不同的(前者是使用解引用运算符()和点运算符(.)来访问指针p所指向的结构体中的name成员,而后者是直接访问结构体变量p中的name成员)。

如果 p 指向一个结构体变量 stu ,以下3种用法等价:
① stu.成员名(如 stu.num);
② (*p).成员名(如 (*p).num);
③ p->成员名(如 p->num);

2. 指向结构体数组的指针

可以用指针变量指向结构体数组的元素。
(1) 声明结构体类型 struct Student ,并定义结构体数组,同时使之初始化;
(2) 定义一个指向 struct Student 类型数据的指针变量 p;
(3) 使P指向结构体数组的首元素,输出它指向的元素中的有关信息;
(4) 使P指向结构体数组的下一个元素,输出它指向的元素中的有关信息;

3. 用结构体变量和结构体变量的指针作函数参数

将一个结构体变量的值传递给另一个函数,有3个方法:
(1) 用结构体变量的成员作参数。例如用 stu[1]. num 或 stu[2]. name 作函数实参,将实参值传给形参。用法和用普通变量作实参是一样的,属于 值传递 方式。应当注意实参与形参的类型保持一致。
(2) 用结构体变量作实参。用结构体变量作实参时,采取的也是 值传递 的方式,将结构体变量所占的内存单元的内容全部按顺序传递给形参,形参也必须是同类型的结构体变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的。此外,由于采用值传递方式,如果在执行被调用函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便。因此一般较少用这种方法。
(3) 用指向结构体变量(或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。

四、 共用体

1.什么是共用体类型

有时想用同一段内存单元存放不同类型的变量。例如,把一个整型变量,和一个字符型变量放在同一个地址开始的内存单元中。
这种使不同的变量共享同一段内存的结构,称为 共用体类型的结构。
定义 共用体类型变量的一般形式为

union 共用体名
{
成员表列
} 变量表列;

例如:

union Data // 表示不同类型的变量i ,ch ,可以存放到同一段存储单元中
{
int i;
char ch;
}a,b,c; // 在声明类型同时定义变量

也可以将类型声明与变量定义分开:

union Data // 声明共用体类型
{
int i;
char ch;
};
union Data a,b,c; // 用共用体类型定义变量

即先声明一个union Data 类型,再将 a,b,c 定义为 union Data 类型的变量。 当然也可以直接定义共用体变量。例如:

union // 没有定义共用体类型名
{
int i;
char ch;
} a,b,c ;

可以看到,共用体 与 结构体 的定义形式相似。但它们的含义是不同的。
结构体变量所占内存长度是各成员占内存长度之和。每个成员分别占有其自己的内存单元。而共用体变量所占的内存长度等于最长的成员长度

2.引用共用体变量的方式

只有先定义了共用体变量才能引用它,但应注意,不能引用共用体变量,而只能引用共用体变量中的成员

a.i (引用共用体变量中的整型变量 i)
a.ch (引用共用体变量中的字符变量 ch)

不能只引用共用体变量,例如下面的引用是错误的:

printf(“%d”,a);

因为a的存储区可以按不同的类型存放数据,有不同的长度,仅写共用体变量名a,系统无法知道究竟应输出哪一个成员的值。应该写成

printf(“%d”,a.i);

printf(“%c”,a.ch);

3.共用体类型数据的特点

在使用共用体类型数据时要注意以下一些特点:
(1) 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。其道理是显然的,因为在每一个瞬时,存储单元只能有唯一的内容,也就是说,在共用体变量中只能存放一个值。
(2) 可以对共用体变量初始化,但初始化表中只能有一个常量。
(3) 共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就被取代。
(4) 共用体变量的地址和它的各成员的地址都是同一地址。
(5) 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
(6) 以前的C规定不能把共用体变量作为函数参数,但可以使用指向共用体变量的指针作函数参数。C99允许用共用体变量作为函数参数。
(7) 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

五、 枚举类型

如果一个变量只有几种可能的值,则可以定义为 枚举(enumeration)类型 ,所谓 枚举 就是指把可能的值 一 一 列举出来,变量的值只限于列举出来的值的范围内。

声明枚举类型用enum开头。例如:

enum Weekday {sun , mon , tue , wed , thu , fri , sat};
enum Weekday
enum Weekday workday , weekend;
workdayweekendsun ,mon , … ,sat
workday = mon; // 正确, mon 是指定的枚举常量之一
weekday = sun; // 正确,sun 是指定的枚举常量之一
weekday = monday; //不正确,monday 不是指定的枚举常量之一

枚举常量是由程序设计者命名的,用什么名字代表什么含义,完全由程序员根据自己的需要而定,并在程序中作相应处理。
也可以不声明有名字的枚举类型,而直接定义枚举变量,例如:

enum {sun , mon , tue , wed , thu , fri , sat } workday ,weekend ;

声明枚举类型的一般形式为

enum [枚举名] { 枚举元素列表 };

其中,枚举名应遵循 标识符 的命名规则,上面的Weekday 就是合法的枚举名。
说明:
(1) C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。例如:
sun = 0;mon = 1; // 错误,不能对枚举元素赋值
(2) 每一个枚举元素都代表一个整数,C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5…。在上面的定义中,sun 的值自动设为0, mon 的值为1,…,sat 的值为6。如果有赋值语句:
workday=mon;
相当于
workday=1;
枚举常量是可以引用和输出的。例如:
printf( “%d”, workday);
将输出整数1。

也可以人为地指定枚举元素的数值,在定义枚举类型时显式地指定,例如:

enum Weckday{ sun=7,mon=1,tue,wed,thu,fri,sat }workday,weekend;

指定枚举常量 sun 的值为 7, mon 为 1,以后顺序加 1,sat为6
由于枚举型变量的值是整数,因此C99把枚举类型也作为整型数据中的一种,即用户自行定义的整数类型。
(3)枚举元素可以用来作判断比较。例如:
if(workday==mon)…
if(workday>sun)…
枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。如果定义时未人为指定,则按上面的默认规则处理,即第1个枚举元素的值为0,故 mon>sun,sat>fri。

六、 用typedef申明新类型名

除了可以直接使用C提供的标准类型名(如 int ,char ,float ,double 和 long 等)和程序编写者自己声明的结构体、共用体、枚举类型外,还可以用 typedef 指定新的类型名来代替已有的类型名。有以下两种情况:

1. 简单地用一个新的类型名代替原有的类型名

例如:
typedef int Integer; //指定用 Integer 为类型名,作用与 int 相同
typedef float Real; //指定用 Real 为类型名,作用与 float 相同
指定用 Integer 代表 int 类型,Real 代表 float。这样,以下两行等价:
①int i,j; float a,b;
②Inteser i,j; Real a,b;
这样可以使熟悉 FORTRAN的人能用 Integer 和 Real 定义变量,以适应他们的习惯。
又如在一个程序中,用一个整型变量来计数,则可以命名 Count 为新的类型名,代表 int 类型:
typedef int Count; // 指定Count 代表 int
Count i , j ; // 用Count 定义变量 i 和 j ,相当于 int i,j;
将变量 i , j 定义为Count 类型,而Count 等价于 int ,因此 i , j 是整型。 在程序中将 i , j 定义为 Count 类型,可以使人更一目了然地知道它们是用于计数的。

2. 命名一个简单的类型名代替复杂的类型表示方法

归纳起来,声明一个新的类型名的方法是:
① 先按定义变量的方法写出定义体(如:int i;)。
② 将变量名换成新类型名(例如: 将 i 换成 Count)。
③ 在最前面加 typedef (例如:typedef int Count)。
④ 然后可以用新类型名去定义变量。
简单地说,就是按定义变量的方式,把变量名换上新类型名,并且在最前面加 typedef ,就声明了新类型名代表原来的类型。