结构体
结构体的声明
在C语言中,有自己的内置类型,如int 、double、 float、 等等,这些类型只能解决一些问题,但是还有一些问题无法解决,比如定义一个人,而有头、手…等许多的特征,如果光靠这里些类型来描述这些很难完成,而结构体就是为了解决这个问题出现的
结构的基础知识
结构是一些值的集合,这些值称为成员变量,结构体的成员可以是不同的变量,这跟为我们学习过的数组很像,数组是一组相同类型元素的集合。
声明
#include<stdio.h> struct tag1 { int hand; int head; int leg; } person; person = { 1,2,3 }; struct tag2 { int hand; int head; int leg; } Person2 = { 2,1,3 }; typedef struct tag3 { int hand; int head; int leg; }st; int main() { struct tag1 Person1 = { 2,1,2 }; st Person3 = { 2,1,3 }; return 0; }
这里我只是列举了两三种结构类型的声明方式
还有一种匿名结构体声明,只能使用一次,就是在声明的时候进行使用,一旦结束声明就会销毁
#include<stdio.h> struct { char name[20]; char auother[30]; int a; } ar = {"fdfdg", "fg", 5}, *ps = &ar; int main() { printf("%p\n", ps); printf("%s\n", ps->name); printf("%s\n", ps->auother); return 0; }
这种结构体只能使用一次
结构的自引用
小小插曲:
数据结构:描述 的是数据在内存中存储和组织的结构
例如 我们存储12345在内存中是连续存放
安照这种结构(线性数据结构)我们称之为顺序表
如果按照这个结构(线性数据结构),我们称之为链表,图中的每一个存储的方框都是一个节点
#include<stdio.h> struct Node { int num;//存放数据--数据域 struct Node* pa;//存放下一个结构体的地址--指针域 }; int main() { struct Node a5 = { 5, NULL }; struct Node a4 = { 4, &a5}; struct Node a3 = { 3, &a4 }; struct Node a2 = { 2, &a3 }; struct Node a1 = { 1, &a2 }; struct Node* p = &a1; while (p) { printf("%d\n", p->num); p = (*p).pa; } return 0; }
而结构体的自引用也就是自己的成员有自己类型的指针
自引用方式:
struct Node { int num; struct Node* pa; };
需要注意的是匿名结构体不行,即使使用了typedef类型重定义也不行,
typedef struct Node { int num; Node* pa; }Node;
这种写法是错误的,因为类型重定义还没有执行完就直接使用就会报错,代码从上往下执行,
结构体变量的定义和初始化
在前面我写的结构体声明有很多种,初始化也有多种
#include<stdio.h> struct Point { int a; int b; } a1 = {1,2}, s1; //1 struct Sp { int a; }; struct Sd { int b; struct Sp c; }; struct Point a2 = { 1,2 };//2 int main() { struct Point s = { 3,4 };//3 s1.a = 3;//4 s1.b = 6; struct Point s3 = { s3.a = 5, s3.b = 9 };//5 struct Point s4 = { .a = 5, .b = 9 }; struct Sd s5 = { 4, {6} }; printf("%d", s5.c.a); return 0; }
这里我列举了一些定义和初始化的情况,包含结构体嵌套结构体
结构体内存对齐
#include<stdio.h> struct S1 { int a; char b; short c; }; struct S2 { short c; int a; char b; }; int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
可以看到两个结构体,大小不一样,但是成员是一样的,是啥造成这个原因?
其实就是关于结构体内存对齐
首先得掌握结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
也就是说在结构体的大小是由内存对齐决定的,结构体的第一个成员的开辟的空间必须是在偏移量为0的地址处
这里我引入一个宏offsetof 计算结构体成员的偏移量
第一个参数就是结构体类型,第二个参数就是结构体成员
返回值:
类型为 size_t 的值,其偏移值为类型中的成员。
#include<stdio.h> #include<stddef.h> struct S1 { int a; char b; short c; }; int main() { printf("%d\n", offsetof(struct S1, a)); printf("%d\n", offsetof(struct S1, b)); printf("%d\n", offsetof(struct S1, c)); printf("%d", sizeof(struct S1)); return 0; }
我们来模拟一下这个宏
#include<stdio.h> #include<stddef.h> #define OFFSETOF(type, money) (size_t)(&(((type*)0) ->money))//设计一个结构体初始地址,使用 ->可以找到对应的成员,然后取地址,因为offsetof的返回值的类型为size_t struct stu { char name[20]; int money; }; int main() { printf("%zd\n", OFFSETOF(struct stu, money)); printf("%zd\n", offsetof(struct stu, money)); return 0; }
如果我们要计算出结构体的内存对齐情况,不可能全部根据offsetof来确定,所以我们要学会计算结构体内存对齐
在vs编译器中默认的对齐数是8,而成员的对齐数是成员的大小(空间大小)和vs编译器默认的对齐数进行比较,取最小的为该成员的对齐数
变量a从结构体变量的偏移量为0的地址处开始,存放4个字节,当我们遇到变量b对齐时要判断这个地址处是否是b的对齐数的整数倍,图中的地址4是1的整数倍,所以可以对齐,遇到变量c时,因为c的对齐数是2.而5不是2的倍数,所以要往后找。直到找到是2的倍数的地址,当我们把所有的成员对齐后,计算出目前结构体的大小,结构体的大小是该结构体最大对齐数的整数倍,而图中的当我们对齐完刚刚好,大小为8,而结构体的最大对齐数为4