结构体
程序设计时,最重要的步骤之一是选择表示数据的方法。
许多情况下,简单变量甚至是数组还不够;为此,c提供了*结构变量(structure variable)*提高数据的表达能力,能让你创造新的形式。
建立结构声明
*结构声明(structure declaration)*描述了一个结构的组织布局,声明如下:
struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
- 该声明并未创建实际的数据对象,只是描述了该对象由什么组成(可把结构声明称为模板,因其勾勒出结构是如何储存数据的)。
- 关键字
struct
–表明跟在其后的是一个结构,后面是一个可选的标记(book),稍后程序中可以使用该标记引用该结构。eg:struct book library; //把library声明为一个使用book结构布局的结构变量。
- 结构声明中,用一对花括号括起来的是结构成员列表。
- 每个成员都用自己的声明来描述;成员可以是任意一种C的数据类型,可以是其它结构。
- 可以把结构声明放在所有函数外部(标记,所有函数都能使用),也可放在函数内部(标记只限于函数内部使用)。
定义结构变量
-
结构有两层含义:
- 结构布局–告诉编译器如何表示数据,但并未让编译器为数据分配空间。
- 创建一个结构变量,程序中创建结构变量语句
struct book library;
,编译器执行代码,以book为模板,创建一个结构变量library
。
-
在结构变量声明中,
struct book
所起的作用相当于一般声明中的int或float。eg:struct book doyle, panshin, * ptbook;
-
声明简化:
struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
}library; //声明的右花括号后跟变量名
struct { //无结构标记
char title[MAXTITL];
char author[MAXAUTL];
float value;
}library;
初始化结构
与初始化数组的语法类似:
struct book library = {
"c primer plus",
"Stephen Prata",
1.95
};
- 用
{}
;各初始化项用逗号分隔。
访问结构体成员
- 结构类似于一个"超级数组"。
- 使用结构成员运算符
.
访问结构中的成员。eg:library.value
–>访问library的value部分。 - 本质上
.title, .author, .value
的作用相当于book结构的下标。 .
运算符优先级高于&
;&library.float
等价于&(library.float)
。
结构的初始化器
- 指定初始化器(designated initializer),语法与数组的指定初始化器类似。
- 结构的指定初始化,用
.
和成员名 标识特定的元素。eg:struct book surprise = {.value = 10.99};
。可按任意顺序使用指定初始化器:
struct book gift = {
.value = 93.88,
.author = "Jeff",
.title = "tensorflow"
};
结构数组
声明结构数组
声明结构数组和声明其它类型数组类似,eg:
struct book library[MAXBKS];
- 把library声明为一个内含MAXBKS个元素的数组,数组的每个元素都是一个book类型的数组。
- library[0]是第一个book类型的结构变量,library[1]是第2个...
- 数组名library本身不是结构变量名,是数组名,数组的每一个元素都是struct book 类型的结构变量;library[0]是结构变量名。
标识结构数组的成员
用如下方式标识结构数组的成员:
library[0].value
library[0].title
library //一个book结构的数组
library[2] //一个数组元素,该元素是book结构
library[2].title //一个char数组(library[2]的title成员)
library[2].title[4] //library数组中library[2]元素的title成员的一个字符
指向结构的指针
使用指向结构的指针的原因:
- 指向结构的指针通常比结构本身更容易操控。(指向数组的指针比数组本身更容易操控(eg:排序问题))
- 早期c中结构不能作为参数传递给函数,但可以传递指向结构的指针。
- 即使能传递结构,传递指针也更有效。
- 一些用于表示数据的结构中包含指向其它结构的指针。
声明和初始化结构指针
- 声明:
struct guy * him;
- 初始化:假设
barney
是一个guy类型的结构;和数组不同,结构名不是结构的地址,因此要在结构名前面加上&
运算符。
him = &barney;
用指针访问成员
eg:
struct guy { //第2个结构
struct names handle; //嵌套结构
char favfood[LEN];
char job[LEN];
float incom;
};
int main(void)
{
struct guy fellow[2] = { //初始化一个结构数组
{
{ "Ewen", "Villard" },
"grilled salmon",
"personality coach",
100000.00
},
{
{ "Rodney", "Swillbelly" },
"tripe",
"tabloid editor",
500000.00
}
};
struct guy * him;
him = &fellow[0];
- 使用
->
运算符:
若 him == &barney; 那么him->income即是 barney.income
若 him == &fellow[0]; 那么him->income即是 fellow[0].income
- him是一个指针,但是him->income是该指针所指向结构的一个成员
- 顺序指定结构变量的值;若
him == &fellow[0]
,那么*him == fellow[0]
;&
和*
是一对互逆运算符。
barney.income == (*him).income == him->income //假设him == &barney
向函数传递结构的信息
传递结构信息给函数有3中方式:传递结构本身、传递指向结构的指针、传递结构的成员。
传递结构成员
- 只要结构成员是一个具有单个值的数据类型(即:int及其相关类型或指针),便可把它作为参数传递给接受该特定类型的函数。
结构的其它特性
- 可以把一个结构赋值给另一个结构,但是数组不能这样。eg:n_data和o_data都是相同类型的结构。
o_data = n_data; //把n_data的每个成员都赋值给 o_data,即使成员是数组也能完成赋值
- 可以把一个结构初始化为相同类型的另一个结构:
struct names right_field = {"Ruthie", "George"};
struct names captain = right_field; //把一个结构初始化为另一个结构
- 函数不仅能把结构作为参数传递,还能把结构作为返回值返回。
结构中的字符数组和字符指针
结构、指针和malloc()
- 使用
malloc()
分配内存并使用指针存储该地址,在结构中使用指针处理字符串比较合理。 - malloc()和free()应该成对使用。
伸缩型数组成员
- 伸缩型数组成员(flexible array member),利用这项特性声明的结构,其最后一个数组成员具有一些特性。
- 该数组不会立即存在。
- 可使用这个成员编写合适的代码,就好像它确实存在,有所需数目的元素一样。
- 声明一个伸缩型数组成员的规则:
- 伸缩型数组成员必须是结构的最后一个成员;
- 结构中必须至少有一个成员;
- 伸缩型数组的声明类似于普通数组,只是方括号中是空的。
- 示列:
struct flex
{
int count;
double average;
double scores[]; //伸缩型数组成员
};
- 声明一个`struct flex`类型的结构变量时,不能用scores做任何事,因为还没给这个数组预留存储空间。
- 可以声明一个指向`struct flex`类型的指针,然后用`malloc()`来分配足够的空间,以储存`struct flex`类型结构的常规内容和伸缩型数组成员所需的额外空间。eg:用scores表示内含5个double类型的数组:
```
struct flex * pf; //声明一个指针
//请求为一个结构和一个数组分配空间
pf = malloc(sizeof(struct flex) + 5 * sizeof(double));
//现有足够空间存储count、average和一个内含5个double类型的数组。可用指针pf访问这些成员。
pf->count = 5; //设置count成员
pf->scores[2] = 18.5 //访问数组成员的一个元素
```
- 带伸缩性数组成员的结构有一些特殊要求:
- 不能用结构进行赋值或拷贝:
struct flex * pf1 * pf2; // *pf1 和 *pf2都是结构 ... *pf1 = *pf2; //不能如此操作;这样只能拷贝除伸缩性数组成员以外的其它成员。应该用memcpy()
- 不能以按值方式把这种结构传递给结构
- 不能使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。
匿名结构
创建嵌套结构:
struct names
{
char first[20];
char last[20];
};
struct person
{
int id;
struct names name; //嵌套结构成员
};
struct person ted = {8483, {"Ted", "Grass"}};
/*
name 是一个嵌套结构,可以通过ted.name.first的表达式访问"ted"
*/
用嵌套的匿名结构定义person如下:
struct person
{
int id;
struct {char first[20]; char last[20];}; //匿名结构
}
//初始化方式与非匿名结构相同:
struct person ted = {8483, {"Ted", "Grass"}};
//访问ted时简化了步骤:
ted.first; //即可访问"Ted"
使用结构数组的函数
- 可以把数组名作为数组中第1个结构体的地址传递给函数。
- 可以用数组表示法访问数组 中的其它结构
把结构内容保存到文件中
链式结构
- 结构的多种用途之一:创建新的数据形式
- 队列、二叉树、堆、哈希表、图,等许多数据结构都是由*链式结构(linked structure)*组成的。
- 通常,链式结构的每个结构包含 一两个数据项和一两个指向其它同类型结构的指针,这些指针把一个结构和另一个结构链接起来,并提供一种路径能遍历整个彼此链接的结构。
联合(union)
- *联合(union)*是一种数据类型,–可以在同一个内存空间中储存不同的数据类型(不是同时储存)。
- 典型用法:设计一种表以储存既无规律、事先也不知道顺序的混合类型。
- 使用联合类型的数组,其中的联合都大小相等,每个联合都可以储存各种数据类型。
- 创建联合:与创建结构的方式相同,需要一个联合模板和联合变量。
- 用一个步骤定义联合:
- 用一个联合标记,分两步定义:
union hold { int digit; double bigfl; char letter; }; //此联合可以储存一个int类型或一个double类型或一个char类型的值。 union hold fit; //创建一个单独的联合变量fit,编译器分配足够的空间以便它能储存联合声明中占用最大字节的类型。 union hold save[10]; //创建一个save数组,内含10个元素,每个元素都是8字节 union hold * pu; //创建一个指针,该指针变量储存hold类型联合变量的地址。
- 初始化联合:有3中初始化方法:(note:联合只能存储一个值)
- 把联合初始化为另一个同类型的联合
- 初始化联合的第一个元素
- 使用指定初始化器
union hold valA; valA.letter = 'R'; union hold valB = valA; //用另一个联合来初始化 union hold valC = {88}; //初始化联合的第一个元素(digit) union hold valD = {.bigfl = 118.2}; //指定初始化器
- 使用联合
- 联合中一次只储存一个值,用
.
运算符表示正在使用哪种数据类型。
fit.digit = 23; //把23储存在fit,占2字节 fit.bigft = 2.0; //清楚23,储存2.0,占8字节 fit.letter = 'h'; //清楚2.0,储存h,占1字节
- 用指针访问联合:与指针访问结构相同用
->
运算符
pu = &fit; x = pu->digit; //相当于 x = fit.digit
- 在结构中储存与其成员有从属关系的信息。(用结构表示一辆汽车,需要用一个成员来描述这个所有者,若汽车属于驾驶者,所有者为驾驶者,若被租赁,所有者为租赁公司)
struct owner { char socsecurity[12]; ... }; struct leasecompany{ char name[40]; char headquarters[40]; ... }; union data { struct owner owncar; struct leasecompany leasecar; }; struct car_data { char make[15]; int status; //私有为0,租赁为1 union data ownerinfo; //若status为0程序使用xxx.ownerinfo.owncar.socsecurity;为1,使用xxx.ownerinfo.leasecar.name ... };
- 联合中一次只储存一个值,用
- 匿名联合:与匿名结构的原理相同
struct owner {
char socsecurity[12];
...
};
struct leasecompany{
char name[40];
char headquarters[40];
...
};
union data {
struct owner owncar;
struct leasecompany leasecar;
};
struct car_data {
char make[15];
int status; //私有为0,租赁为1
union {
struct owner owncar;
struct leasecompany leasecar;
}; //若status为0程序使用xxx.owncar.socsecurity;为1,使用xxx.leasecar.name
...
};
结构&union小结
struct {
int code;
float cost;
} item, * ptrst;
ptrst = &item;
ptrst->code = 3451;
下面3个表达式等价:
ptrst->code item.code (*ptrst).code
枚举类型
- 可使用*枚举类型(enumerated type)*声明符号名称来表示整型常量。使用
enum
关键字,可创建一个新"类型"并制定它可具有的值(实际上enum是int类型) - 枚举类型的目的是提高程序的可读性。
- 声明语法:
enum spectrum {red, yellow, orange, green, blue, violet}; //创建 spectrum作为标记名,允许把enum spectrum作为一个类型名使用,花括号内枚举了spectrum变量可能的值,括号中的常量被称为 枚举符(enumerator)
enum spectrum color; //使用color作为该类型的变量。
int c;
color = blue;
if (color == yellow)
...;
for (color = red; color <= violet; color++)
...;
-
enum常量
- blue, red等枚举值他们是int类型的常量。
- 只要能使用整型常量的地方就能使用枚举常量。
- 在声明数组时,可用枚举常量表示数组的大小,在switch语句中,可把枚举常量作为标签。
- morning情况下,枚举列表中的值都被赋予0,1,2等
- 枚举声明中,可以为枚举常量指定整数值:
enum levels {low = 100, media = 500, high = 1000};
- 若只给一个枚举常量赋值,没有给后面的枚举常量赋值,后面的常量会被 赋予后续的值。
-
enum的用法:
- 枚举类型的目的:提高程序的可读性和可维护性
- 枚举类型只能在内部使用
共享名称空间
###typedef简介
- 利用typedef可以为某一类型自定义名称;即:为现有类型创建一个名称
- 与#define类似,但有3处不同:
- typedef创建的符号名只受限于类型,不能用于值。
- typedef由编译器解释,不是预处理器。
- 在受限范围内,typedef比#define更灵活。
-使用示列:
typedef char * STRING;
STRING name, sign; //相当于 char * name, * sign;
//-------------------------------
#define STRING char * //预处理器用 char * 替代 STRING
STRING name, sign; //相当于 char * name, sign;
//typedef 用于结构体:
typedef struct complex {
float real;
float imag;
} COMPLEX; //可用COMPLEX代替struct complex创建结构变量
//用typedef命名一个结构类型时,可以省略该结构的标签:
typedef struct {double x; double y;} rect;
- 使用typedef的原因:
- 为经常出现的类型创建一个方便、易识别的类型名。
- typedef 常用于给复杂的类型命名。
- 提高代码的可移植性。
- note:typedef并没有创建任何新类型,它只是为某个已存在的类型增加了一个方便使用的标签。
参考资料:C primer Plus