引言:
在C语言中,我们都知道整型(int)、字符型(char)、浮点型(float、double)这些基本数据类型;但是在实际解决问题时,我们会发现只有这几种数据类型是非常单一的,有时候要一次性用到多种数据来修饰一个变量;例如,一个学生有姓名、学号、身高、体重、各科成绩等信息,这时候我们就要用到一个新的数据类型——结构体
1. 结构体类型的声明
1.1. 结构的声明
struct tag
{
member-list;
}variable-list;//注意分号不能少
注意,结构体的声明描述了结构体的组成,有哪些成员变量,但是并没有创建一个实际的数据对象(因此,结构体的声明也被叫做模板)
例如描述一个学生时:
struct student
{
char name[20];//名字
int age;//年龄
char id[20];//学号
int height;//身高
};
1.2. 结构体变量的创建和初始化
#include <stdio.h>
struct student
{
char name[20];
int age;
char id[20];
int height;
};
int main()
{
//按照结构体成员的顺序初始化
struct student s = { "zhangsan", 20, "23012299", 170 };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("height : %s\n", s.height);
printf("id : %s\n", s.id);
return 0;
}
1.3. 结构的特殊声明
在声明结构的时候有一种特殊的声明——不完全声明
举个例子:
struct
{
char c;
int i;
double d;
} s = {'x', 100, 3.14};
int main()
{
printf("%c %d %lf\n", s.c, s.i, s.d);
return 0;
}
运行截图如下:
我们可以看到struct后的标志是忽略后,代码依然能正常运行这即是结构的不完全声明,但是这样做也是有一定的缺陷的,我们接着往下看
举个例子:
struct
{
char c;
int i;
double d;
}s;
struct
{
char c;
int i;
double d;
}*ps;
int main()
{
ps = &s;
return 0;
}
两个结构都省略了标签并且里面的成员一模一样,那么ps=&s;这条语句是否也成立了,我们运行一下试试
我们看到了编译器给出了警告,编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
1.4.结构的自引用
结构的自引用是指在一个结构内部包含一个类型为该结构本身的成员
比如
struct Node
{
int date;
struct Node next;
};
请思考该自引用是否正确,若正确请思考该结构的内存大小是多少?
仔细分析后,会发现其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤ ⼩就会⽆穷的⼤,是不合理的
正确的自引用方式:
struct Node
{
int date;
struct Node* next;
};
注意:匿名的结构体无法自引用
typedef 对匿名结构体类型重命名,也容易引⼊问题,看看下⾯的代码可行吗?
typedef struct Node
{
int data;
Node* next;
}Node;
答案是不⾏的,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使 ⽤Node类型来创建成员变量,这是不⾏的
解决方案如下:
typedef struct Node
{
int data;
struct Node* next;
}Node;
2. 结构体内存对齐
2.1. 对齐原则
⾸先得掌握结构体的对⻬规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。 VS 中默认的值为 8 - Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
简单举个例子:
2.2.修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对⻬数
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
struct A
{
char c1;
int i;
char c2;
};
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
printf("%d\n", sizeof(struct A));
return 0;
}
我们从上述例子中可以看到我们通过#pragma pack(1)将默认对齐数设置为1,从而改变了结构体的内存从原来的12字节变为了6字节,再通过#pragma pack()将对齐数恢复到默认值(因为这两个结构除名字外一模一样,按道理内存也应该一样,但是通过修改了对齐数的值改变了它们的内存大小)
所以结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。