欢迎来CILMY23的博客喔,本篇为【C语言】新类型,结构体篇-----深入理解结构体,结构体成员的访问,结构体的大小和新概念偏移量!【图文详解】,感谢观看,支持的可以给个一键三连,点赞关注+收藏。
前言
C语言中的数据类型有内置类型,和用户自定义类型,内置类型包括char ,short,int,double,float,long long……,用户自定义类型包括,结构体,枚举,联合体,数组……本篇博客将会带大家了解用户自定义类型中的结构体。
目录
在过去我们经常使用数组存储同一类型的数据,但我们不仅仅只有同一类型的数据,于是C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它被称为结构体
- struct Student
- {
- int num; //学号为整型
- char name[20]; //姓名为字符串
- char sex; //性别为字符型
- int age; //年龄为整型
- float score; //成绩为浮点型
- };
结构体类型的声明是用一个关键字struct +结构体名字,结构体名字是用户指定的。
typedef也可以对结构体重命名:
- typedef struct Student
- {
- int num;
- }Stu;
-
- int main()
- {
- Stu n;
- return 0;
- }
- int main()
- {
- struct Student s = {23,"zhangsan",'m',21,88.9};
- return 0;
- }
这样我们就创建了一个结构体变量,这个变量的信息就如{}中所示。
我们也可以在声明结构体类型的时候声明变量
- struct Student
- {
- int num; //学号为整型
- char name[20]; //姓名为字符串
- char sex; //性别为字符型
- int age; //年龄为整型
- float score; //成绩为浮点型
- }s1, s2;
这两种变量还是有差别的,s1,s2是全局变量,s只是局部变量
我们创建变量就相当于在内存中开辟了空间,这么多数据类型就构成一个s结构体变量,其中的分布如下:
我们可以把结构体类型名称省去,这种情况叫做匿名结构体,用匿名结构体类型创建了一个s对象
- struct
- {
- int num;
- }s;
-
- struct
- {
- int num;
- }*ps;
-
- int main()
- {
- ps = &s;
- return 0;
- }
但是同样要注意,在vs编译器看来这两个匿名结构体每个都是单独的个体,我们并不能把s的地址给到指针ps。
我们有两种访问方式,一种是.,一种是->
我们把.和->叫做结构成员访问操作符
那如果我们得到的是s 的地址呢,就可以用到->操作符
- int main()
- {
- struct Student s = {23,"zhangsan",'m',21,88.9};
- printf("%d %s\n", s.num, s.name);
- struct Student* ps = &s;
- printf("%d %s\n", (*ps).num, (*ps).name);
- printf("%d %s\n", ps->num, ps->name);
- return 0;
- }
结果如下:
首先我们来看以下代码:
- struct s1
- {
- char c1;
- char c2;
- int i1;
- };
-
- struct s2
- {
- char c1;
- int i1;
- char c2;
- };
-
- struct s3
- {
- double d;
- char c;
- int i;
- };
-
- int main()
- {
- struct s1 st1;
- struct s2 st2;
- struct s3 st3;
-
- printf("%d\n", sizeof(st1));
- printf("%d\n", sizeof(st2));
- printf("%d\n", sizeof(st3));
-
- return 0;
- }
结果如下:
那为什么会这样呢?这就涉及结构体的大小规则了-----内存对齐
内存对齐的规则:
1.结构体的第⼀个成员对齐到相对结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
VS中默认的值为8
Linux中没有默认对齐数,对齐数就是成员自身的大小
3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
st1
偏移量是什么?假设我们有一个内存,偏移量为1个字节就是指向1这个地方了,所以偏移量为0的话就是指从起始位置开始.(可以理解为距离起始位置有多远)
所以第一个成员c1就存放在第一个字节里
其余成员要按照对齐数来对齐, 根据下面图片来看,c2只需要对齐到1的倍数即可,而i1需要对应到地址字节数4的倍数即可,也就是从第四个字节开始
如下所示:
结构体总大小为最大对齐数,所有成员最大对齐数是4,st1的大小刚好是4的倍数,所以st1的结构体大小就为8
st2
所以我们看st2就容易多了
首先第一个成员要对齐偏移量为0的位置,c1的位置就确定了
其余成员要按照对齐数来对齐, 根据下面图片来看,c2只需要对齐到1的倍数即可,而i1需要对应到地址字节数4的倍数即可,也就是从第四个字节开始,
总的大小为9,结构体总大小为最大对齐数,所有成员最大对齐数是4,所以str2的大小为12.
st3
首先第一个成员要对齐偏移量为0的位置,d的位置就确定了,其余成员要按照对齐数来对齐, 根据下面图片来看,c只需要对齐到1的倍数即可,而i需要对应到地址字节数4的倍数即可,也就是从第12字节开始。
总的大小为16,结构体总大小为最大对齐数,所有成员最大对齐数是8,所以str2的大小为16.
- struct S3
- {
- double d;
- char c;
- int i;
- };
-
- struct s4
- {
- char c1;
- struct S3 s3;
- double d;
- };
-
-
- int main()
- {
- struct s4 st4;
-
- printf("%d\n", sizeof(st4));
-
- return 0;
- }
结果如下:
st4
如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数,也就是对齐到s3当中所有最大成员的对齐数,也就是十六的对齐数。
总的大小为16,结构体总大小为最大对齐数,所有成员最大对齐数是16,所以str2的大小为32.
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。最后,尽量将字节数小的成员安排在前面,防止空间浪费
offsetof宏是用来计算偏移量的,在cplusplus网站可以查询
offsetof - C++ Reference (cplusplus.com)
offsetof的使用如下:
- #include
- #include
-
- struct S3
- {
- double d;
- char c;
- int i;
- };
-
- struct s4
- {
- char c1;
- struct S3 s3;
- double d;
- };
-
- int main()
- {
- printf("%zd\n", offsetof(struct s4, c1));
- printf("%zd\n", offsetof(struct s4, s3));
- printf("%zd\n", offsetof(struct s4, d));
-
- return 0;
- }
结果如下:
#pragma预处理指令
- #pragma pack()//取消设置的默认对⻬数,还原为默认
- struct S3
- {
- double d;
- char c;
- int i;
- };
-
- #pragma pack(1)//设置默认对⻬数为1
- struct s4
- {
- char c1;
- struct S3 s3;
- double d;
- };
-
- int main()
- {
- printf("%d\n", sizeof(struct s4));
- return 0;
- }
一般使用都会将其设置成2,4,6,8,这样的数值。
可以看以下这篇补充一下链表的结构知识点 ,然后再看结构体的自引用
假设我们这里有个链表,定义⼀个链表的节点如下所示:
- struct Node
- {
- int num;
- struct Node next;
- };
-
- int main()
- {
- struct Node n;
- return 0;
- }
仔细分析,其实是不行的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的,一个结构体中有一个Node,Node中又有一个Node如此无限循环下去,就会造成结构体变量的大小无穷大了。
正确的自引用应该如下所示:
- struct Node
- {
- int data;
- struct Node* next;
- };
-
- int main()
- {
- struct Node n;
- return 0;
- }
同样对tepedef我们仍然要注意以下情况是不能使用的
看以下两个代码对比例子:
- typedef struct Node
- {
- int data;
- Node* next;
- }Node;
-
- typedef struct
- {
- int data;
- Node* next;
- }Node;
我们得先有结构体类型重命名后,得到的名字,然后再去命名next。
正确引用如下:
- typedef struct Node
- {
- int data;
- struct Node* next;
- }Node;
-
- int main()
- {
- Node n = {0};
- return 0;
- }
感谢各位同伴的支持,本期结构体篇就讲解到这啦,如果你觉得写的不错的话,可以给个一键三连,点赞关注+收藏,若有不足,欢迎各位在评论区讨论。