一.结构体
//结构体声明 struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 };
二.特殊的结构体声明
在声明结构的时候,可以不完全的声明——匿名结构体类型。
//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p;
上面的两个结构在声明的时候省略掉了结构体标签。
那么问题来了?在上面代码的基础上,下面的代码合法吗?
p = &x;
注意:编译器会把上面的两个声明当成完全不同的两个类型。所以是非法的。
三.结构体自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?
struct Node { int data; struct Node next; }; 可行否?如果可以,那sizeof(struct Node)是多少?
这里显然是不行的。但是是不是就意味着就不行了呢?还是有办法实现自引用的。
正确的结构体自引用方式 struct Node { int data; struct Node* next; };
四.注意:
如果加上 typedef 是不是就可以这样实现结构体自引用呢? typedef struct { int data; Node* next; }Node; 这样写代码是不行的 解决方案: typedef struct Node { int data; struct Node* next; }Node;
五.结构体的定义和初始化
struct Point { int x; int y; }p1; //声明类型的同时定义变量p1 struct Point p2; //定义结构体变量p2 //初始化:定义变量的同时赋初值。 struct Point p3 = {x, y}; struct Stu //类型声明 { char name[15];//名字 int age; //年龄 }; struct Stu s = {"zhangsan", 20};//初始化 struct Node { int data; struct Point p; struct Node* next; }n1 = {10, {4,5}, NULL}; //结构体嵌套初始化 struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
struct Stu { char name[20]; int age; char ID[20]; }; int main() { 按照结构体顺序初始化变量 struct Stu stu2 = { "ikun",20,"JNTM123" }; 自定义顺序初始化变量 struct Stu stu1 = { .ID = "CZU123",.age = 19,.name = "张三" }; return 0; }
六.结构体内存对齐
我们已经掌握了结构体的基本使用了,那么一个结构体到底时多大呢?到底怎么计算呢?
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
大家可以猜一下结果
结果是不是出乎大家的意料了,虽然结构的成员都是一样的,但是结构的大小却不一样。
到底是为什么造成这样的结果呢?其实结构体的大小可不是简单的结构体成员大小加在一起的。
而是有一种内存对齐的规则。
结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。(VS中默认的值为8)
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面我们就对着这个规则来做几个例子:
(1)例一:
struct S1 { char c1; int a; char c2; }; 求结构体s1的大小
运行效果:
(2)例二:
1.题目:
struct S2 { char c1; char c2; int i; }; 求结构体 S2 的大小
2.图解:
3.运行效果:
(3)例三:
1.题目:
struct S1 { char c1; int a; char c2; }; struct S2 { char c1; struct S1 S; char c2; int i; }; //求结构体S2的大小
2.图解:
运行结果:
七、为什么存在内存对齐
(1) 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
(2) 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
例如:
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };
s2就比s1更节省空间。
四.修改默认对齐数
#pragma pack();
例一:
#include <stdio.h> #pragma pack(8)//设置默认对齐数为8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 #pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 int main() { //输出的结果是什么? printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
运行结果:
例二:
#include <stdio.h> #pragma pack(1)//设置默认对齐数为8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 #pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 int main() { //输出的结果是什么? printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
运行结果:
最后:
不操千曲而后晓声,观千剑而后识器。