0x01 为什么使用结构体

假设:现在需要存储多条数据,如学生的学号,和姓名,一个是整型,一个是字符串
存储多条数据可以使用数组,但是数组存储的数据必须是同一类型
这时候结构就是用来解决这个问题,结构可以用于存储不同类型的多条数据,学生的学号和姓名都可以放在一个结构中保存

0x02 结构体的定义

C语言中可以使用结构(也叫结构体),定义用户自己的类型
结构体的一般格式如下:

1
2
3
struct 结构名{
各种成员变量;
};

其中,结构名可以不写,但一般都建议写上
结构体类型存储区里可以包含多个子存储区,结构体子存储区的类型可以不同,子存储区也可以是结构体类型
成员变量声明语句不会分配存储区,它们只是用来表示子存储区的类型和名称
结构体声明语句不会分配内存,所以可以写在头文件里
可以把结构体作为类型声明变量,这种变量叫结构体变量
声明结构变量时,变量类型必须是 struct 结构名, 而不能只是结构名.

1
2
3
4
5
6
7
8
9
10
#include "stdio.h"
strcut Prson{
int age;
char name[10];
};

int main(){
struct Prson Pn; //结构体变量声明
return 0;
}

上述需要用到两个单词才能声明结构体变量,(struct Prson)
C语言支持 typedef 关键字可以给结构体定义别名,这样可以省略结构体名称

1
2
3
4
5
6
7
8
9
10
11
#include "stdio.h"
typedef strcut Prson{
int age;
char name[10];
}SPrson;

int main(){
struct Prson Pn; //结构体变量声明
Sprson Pn1; //使用别名声明结构体变量
return 0;
}

声明结构体变量时,应该进行初始化,可以向初始化数组一样初始化结构体变量

1
2
Sprson Pn1 = {20,"D4rk"};
Sprson Pn2 = {0};

0x03 结构体子存储区访问

同类型结构体变量之间可以直接赋值
结构体指针可以和结构体存储区捆绑
当结构体指针和结构体存储区捆绑后,就可以采用以下写法表示子存储区

1
p_person->age; //其中 p_person 是一个结构体,age是一个成员变量名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//demo
#include "stdio.h"
typedef struct person{
int age;
float height;
char name[10];
}sperson;
int main(){
sperson prsn = {18,1.75f,"d4rk"};
printf("age == %d\n",prsn.age);
printf("height == %.2f\n",prsn.height);
printf("name == %s\n",prsn.name);

sperson *p_prsn = NULL;
p_prsn = &prsn;
printf("age == %d\n",p_prsn->age);
printf("height == %.2f\n",p_prsn->height);
printf("name == %s\n",p_prsn->name);

return 0;
}

0x04 结构体变量做形式参数

结构体变量可以直接作为形式参数使用,直接使用结构体变量做形式参数会导致时间和空间的浪费
采用结构体指针作为形式参数可以避免这些问题
注:结构体指针做形式参数的时候 尽量使用 const 关键字声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//demo
#include "stdio.h"
typedef struct{
int row,col;
}pt;
void print01(pt pt1){ //结构体变量做形式参数
printf("行列 == (%d,%d)\n",pt1.row,pt1.col);
}
void print02(const pt *p_pt1){ //结构体指针做形式参数
printf("行列 == (%d,%d)\n",p_pt1->row,p_pt1->col);
}
int main(){
pt pt1 = {0};
printf("输入行列:");
scanf("%d%d",&(pt1.row),&(pt1.col));
print01(pt1);

print02(&pt1);

return 0;
}

0x05 结构体变量做返回值

可以把结构体变量作为返回值使用,这个时候需要被调用函数提供一个结构体类型的存储区用来存放返回值
不过这也会造成时间和空间的浪费
使用结构体存储区地址作为返回值可以避免这个问题(被调用函数需要提供一个结构体指针记录这个返回值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//demo
#include "stdio.h"
typedef struct{
int row, col;
}pt;

pt *read(void){
pt pt1 = { 0 };
printf("输入行列:");
scanf_s("%d%d", &(pt1.row), &(pt1.col));
return &pt1;
}
int main(){
pt *pt1 = read();
printf("行列 == (%d,%d)\n", pt1->row,pt1->col);
return 0;
}

不要使用局部结构体的变量作为返回值,因此可以将上述修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//demo
#include "stdio.h"
typedef struct{
int row, col;
}pt;
pt *read(pt *p_pt1){
printf("输入行列:");
scanf_s("%d%d", &(p_pt1->row), &(p_pt1->col));
return p_pt1;
}
int main(){
pt pt1 = { 0 };
read(&pt1);
printf("行列 == (%d,%d)\n", pt1.row,pt1.col);
return 0;
}

0x06 数据对齐和数据补齐

一个存储区的地址一定是它自身大小的整数倍,双精度存储区的地址只需要是 4 的整数倍就可以了
这种规则叫数据对齐
结构体的子存储区通常也需要遵循数据对齐的规定
数据对齐可能导致结构体的子存储区之间有空隙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "stdio.h"
/* 数据对齐和补齐演示 */
typedef struct{
char buf[2];
int num;
}tmp;
typedef struct{
char buf;
int num;
char buf1;
}tmp1;
int main(){
printf("sizeof(tmp)是%d\n",sizeof(tmp));
printf("sizeof(tmp)是%d\n",sizeof(tmp1));
return 0;
}

结构体存储区的大小必须是它所包含的占地最大的基本类型存储区大小的整数倍
如果这个基本数据类型子存储区的类型是 double 则结构体存储区的大小只需要是4的整数倍
这种规则叫数据补齐
数据补齐可能造成结构体最后有浪费的字节