本章重点有:结构体类型的声明;结构体的自引用;结构体变量的定义和初始化;结构体内存对齐;结构体传参;结构体实现位段。
目录
结构体类型的声明:
结构体类型的自引用
结构体变量的定义和初始化
结构体内存对齐
修改默认对齐数
结构体传参
结构体实现位段
位段的内存分配
位段的跨平台问题
结构体类型的声明:
数组是一组相同类型的元素的集合;结构体也是一些值的集合,结构体的成员可以是不同类型的。
生活中:对象是复杂的,比如书:书名+作者+出版社+书号......
struct book struct tag { { char name[20]; member_list; int price; char id[12]; }b4,b5,b6; }list; //b4,b5,b6是全局变量 int main() { struct book b1; struct book b2; struct book b3; //b1,b2,b3是局部变量 return 0; }
特殊的结构体类型;匿名结构体类型,可以不完全声明,只能用一次
struct //匿名结构体类型 { //在声明的时候省略了结构体标签 char c; int i; }s; struct { char c; int i; }* ps; int main() { ps = &s; //编译器会将两个声明当成完全不同的类型 return 0; //是非法的 }
结构体类型的自引用
结构体类型的自引用,不是包含同类型的变量而是包含同类型的指针。
struct N { int d; struct N n; }; //这种写法是错误的,会死循环
数据结构(数据在内存中存储的结构)中有以下:线性数据结构和树形数据结构。
线性数据结构有:顺序表和链表。
结构体变量的定义和初始化
结构体内存对齐
vs的默认对齐数是8,Linux没有默认对齐数的概念。
创建一个结构体,会给它分配多少空间。结构体的空间要通过内存对齐原则:
1.第一个成员放在结构体变量偏移量为0的地址处;2.从第二个成员往后的所有成员,都放在一个对齐数(成员的大小和默认对齐数的较小值)的整数的整数倍的地址处;3.结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍;4.如果嵌套结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含结构体的对齐数)的整数倍。
内存对齐会浪费一些空间,为什么要内存对齐呢?
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说,结构体的内存对齐是拿空间换时间的做法。
在设计结构体的时候,既要满足对齐,也要节省空间;让占用空间小的成员尽量集中在一起。
看下面这个例子:
struct a struct A { { char c1; char c1; int b; char c2; char c2; int b; }; };
这两个结构体的成员是一样的,只是每个成员放置的顺序不一样,导致两个结构体所占内存的大小不一样,所以要让占用空间小的成员集中在一起,与顺序无关。
修改默认对齐数
#pragma预处理指令
#pragma pack(2) //修改默认对齐数为2 struct a { char c1; char c2; int b; }; #pragma pack() //恢复默认对齐数为8 int main() { return 0; }
结论:结构在对齐方式不合适的时候,我们可以修改默认对齐数的大小。
介绍一下宏offsetof(要引用头文件);在cplusplus.com中,宏offsetof的定义是offsetof( type,member )。它是用来计算地址偏移量的大小。在代码中如何计算的呢?
#include <stdio.h> #incldue <stddef.h> struct a { char c1; int b; char c2; }; int main() { printf("%d\n",offsetof(struct a,c1)); //0 printf("%d\n",offsetof(struct a,b)); //4 printf("%d\n",offsetof(struct a,c2)); //8 return 0; }
结构体传参
结构体传参有两种传参方式:传结构体和传地址;这两个方式哪个更好呢?
传结构体需要再开辟一个相同空间用来传递结构体的内容,浪费空间且效率不高;而传地址只需要传递4或8个字节,不浪费太多空间且效率更高。所以结构体传参传递地址,效率更高,基本什么功能都能实现,也不浪费空间。指针变量也可以找会结构体本身。函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
结构体实现位段
先来介绍一下位段,位段依附于结构体。位段的声明和结构体是类似的,但有两个不同:
1.位段的成员必须是int , unsigned int , signed int , char 的整数类型;
2.位段的成员名后面有一个冒号和一个数字。
struct A { int _a:2;//成员占2个bite位 int _b:5;//成员占5个bite位 int _c:10;//成员占10个bite位 int _d:30;//成员占30个bite位 };
位段的内存分配
1.位段的成员可以是整形家族类型,基本是一个结构中位段的类型是一样的;
2.位段的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的,位段的大小不能超过int 或char类型的大小;
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段的跨平台问题
1.int 位段被挡成有符号数还是无符号数是不确定的;
2.位段中最大为的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)
3.位段的成员在内存中从左向右分配,还是从右向左分配尚未定义;
4.当一个结构包含两个位段,第二个位段成员过大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以更好的节省空间,但有跨平台的问题存在。
网络中数据包越小越好,是位段的应用。