C基础——结构struct
## 结构
用于表示一条记录,允许存储不同类型的数据项
### (1)结构声明
描述了一个结构的组织布局,并未创建实际的数据对象,是一种功能与int、float等基本数据类型相同的构造类型。
```C
struct [name] //声明结构体的关键字为struct,跟在其后面的name是一个可选的标记,可以通过此标记引用结构体
{ //结构体的成员列表被写在一对花括号内
datatype name1; //结构成员即为一条条声明描述,可以是任意一种C的数据类型的声明
datatype name1; //成员之间以分号(;)分隔
...
}; //在结构体的右花括号之后,必须有分号(;),表示声明结束
```
==结构声明只是声明了结构布局,并未让编译器分配内存空间。它只是一个由用户自定义的数据类型。==
### (2)定义结构体变量
在定义变量后,编译器会按照结构布局为结构体变量分配空间
```C
//方法1:通过引用标记创建结构体变量,方便在之后的代码中重复使用结构模板
struct book //此声明创建了一个结构布局为book的结构体
{
char book_title[51];
char bool_author[51];
float book_price;
};
...
struct book library; //此声明通过book标记引用结构体布局,创建了结构体变量library
struct book *ptb; //创建结构体类型的指针,指针ptb可以指向任何struct book类型的结构体变量
```
```C
//方法2:在省略标记的情况下,直接在结构体声明中创建结构体变量
struct
{
char book_title[51];
char book_author[51];
float book_price;
}library,*ptb; //可以在结构体的右花括号之后创建结构体变量,不要忘了标记结束的分号(;)
```
```C
//方法3:利用typedf为结构体定起一个别名
typedef struct [book] //使用typedef自定义结构体的名称为BOOK,在使用typedef时,可以省略结构体标记
{
char book_title[51];
char book_author[51];
float book_price;
}BOOK;
...
BOOK library; //可以通过自定义的名称BOOK定义结构体变量。BOOK等价于struct book
BOOK *ptb;
```
### (3)结构体初始化
```C
//方法1:如同为数组初始化一样,可以直接使用初始化列表来初始化结构体
struct book library =
{
"C Programming Language", //成员之间以逗号(,)分隔,且成员类型要与结构声明严格匹配
"K&R",
50.99
};
//方法2:指定初始化器
struct book library =
{
.book_price = 50.99, //利用点运算符(.)访问结构成员,对成员进行初始化
.book_title = "C Programming Language", //可以以任意顺序对成员进行初始化
.book_author = "K&R",
.book_price = 100.99 //并且可以多次对同一个成员进行初始化,该成员的值是最后一次初始化的值
};
```
==访问结构体成员:结构成员运算符——点(.)==。
```C
library.book_title;
library.book_author;
library.book_price;
// .结构体成员名,相当于结构体的下标。
//虽然library是一个结构体,但是library.book_title是一个字符数组
```
### (4)赋值
==C允许将结构体变量为其他同类型的结构体变量赋值,即便结构体成员列表中包含数组,也能成功赋值。==
### (5)结构体数组
```C
struct book library[100]; //声明一个struct book类型的数组library
```
==通过以上声明,要理解library是一个数组名!!==
==这个数组中的每一个成员,library[0]、library[1]、library[2]...才是struct book类型的结构体变量==
```C
//可以通过结构体数组中的元素访问结构体成员
library[index].book_title;
library[index].book_author;
library[index].book_price;
library[0].book_title[2];
//上面的语句表示:结构体数组中,第1个结构体变量中的成员book_title【字符数组】中的第3个字符
```
### (6)嵌套结构
在一个结构体声明中,可以包含另一个结构体
```C
struct name
{
char first_name[21];
char last_name[21];
};
struct friend
{
struct name fname;
char job[21];
float income;
};
...
//定义结构体变量以及初始化
struct friend myfriend =
{
{"wang", "wei"},
"game player",
654321.12
};
//访问结构成员
myfriend.jbo;
myfriend.income;
myfriend.fname.first_name; //访问结构体中结构体成员的成员,需要使用两次点运算符
myfriend.fname.last_name;
```
### (7)指向结构的指针
```C
//声明和初始化结构指针
struct book library;
struct book *ptb; //声明指向struct book类型结构指针ptb
ptb = &library //结构指针可以指向任意struct book类型的结构体变量
```
==使用结构指针访问结构体成员,运算符:->==
```C
ptb->book_title;
ptb->book_author;
ptb->book_price;
```
```C
struct friend myfriend[100];
struct friend *ptf;
ptf = &myfriend[1]; //这里要注意,myfriend是一个数组名,其中的数组元素才是结构体变量
ptf->fname.first_name; //利用pft访问结构体变量myfriend[1]中结构成员fname中的成员
ptf->fname.last_name;
```
### (8)向函数传递结构信息
可以直接传递==结构体成员==
可以直接传递==结构体本身==,旧式的编译器不允许这样做
也可以传递==结构体的地址==,即传递指向结构体的指针
----
传递指针比直接传递结构体本身更有效率,需要保护数据时,可以使用const限定符修饰指针
直接传递结构体的优势是代码风格更加清晰,而且修改的是原结构体的副本
----
### (9)结构体中的字符数组与字符指针
```C
//结构体中的允许有各种类型的成员,包括指针类型
struct book
{
char *pt_title;
char *pt_author;
float price;
};
```
在使用以上结构时需要注意:==pt_title与pt_author都是未经初始化的指针,不能直接使用!!!==
这种指针只适合处理已经分配好空间的数组,不能用于接收输入
```C
struct book library;
puts("Please enter book title:");
fgets(library.pt_title, 21, stdin); //pt_title是未初始化的指针,可能指向任何地址!!!
```
如果要以上述形式使用结构体中的指针,需要为其分配内存空间
```C
struct book library;
library.pt_title = malloc(21 * sizeof(char)); //在使用指针前,一定要对其进行初始化!!!
...
fgets(library.pt_title, 21, stdin);
```
### (10)伸缩型数组成员(C99)
==特性:==
==第一个特性,此数组成员不会立即存在==
==第二个特性:可以使用这个成员编写合适的代码,就好像它真实存在并具有指定的大小一样==
----
声明一个伸缩型数组成员有以下规则:
伸缩型数组成员必须是结构的最后一个成员
伸缩型数组成员的[]是中空的
结构体中必须至少有一个成员
```C
struct flex
{
int count;
double average;
double scores[]; //伸缩型数组成员
};
```
想要使用flex结构体的布局,需要声明一个指向此类型的指针,利用动态分配内存的方法对指针进行初始化,以存储struct flex类型中的常规成员和伸缩型数组成员。
```C
struct flex *ptf;
ptf = malloc(sizeof(struct flex) + 5 * sizeof(double));
```
以上语句,利用malloc()分配了一个【struct flex类型+5个double类型】大小的空间,并将结构体指针ptf指向了这个空间的首地址。其中,sizeof(struct flex)代表的是为成员count和average分配的内存空间,而5*sizeof(double)则是为伸缩型数组成员分配的内存空间。
所以,可以将带有伸缩型数组成员的结构体理解为一个普通结构体外带了一个不确定大小的数组,在为这一个“组合”分配内存空间时,率先为结构体中确定的成员分配内存,即sizeof(sturct name),然后再为伸缩型数组成员单独分配内存,即(元素个数)n x sizeof(datatype),然后将这两部分内存空间“合并”,将空间首地址分配给一个指针变量。
使用带有伸缩型数组成员的结构体时,会有一些特殊的要求。
第一,不能进行结构之间的拷贝或赋值。直接用结构赋值,只会除赋值伸缩型数组成员之外的成员。
第二,不要用按值传递的方式传递这种结构,原因与赋值相同。
第三,不要用这种结构作为其他结构或数组的成员。
### (11)匿名结构(C11)
匿名结构,即没有名字的结构。
在C11标准中,可以创建一下形势的嵌套结构:
```C
strcut friend
{
int id;
struct{char fname[20]; char lname[20]}; // 匿名结构
};
```
在使用匿名结构的成员时,可以将其当做friend的成员进行访问.
```C
struct friend myf =
{
1,
{"li", "si"}
};
puts(myf.fname);
```
### (12)将结构内容保存到文件中
```C
struct book
{
char name[51];
char author[51];
float value;
};
struct book library[100];
```
可以利用fwrite()和fread()函数将结构内容写入文件或从文件中读取结构内容。
```C
fwrite(&library[index], sizeof(struct book), 1, lib);
//从地址&library[index]的位置上读取1段sizeof(struct book)大小的数据,写入与lib相关联的文件
fread(&library[index], sizeof(struct book), 1, lib);
//从lib指向的文件中读取1段大小为sizeof(struct book)的数据,存入至地址为&library[index]的位置。
```
需要注意的是,fwrite()与fread()都是以二进制形式写入或读取数据的,而不同操作系统对数据的二进制表示可能不同,所以以二进制形式存储的数据可能不具有可移植性。
甚至于,在同一个系统中使用不同的编译器,也可能改变数据的二进制布局。