目录
一、结构体声明
1.1结构体基础知识
1.2 结构的声明
1.3 结构体自引用
1.4 结构体变量的定义和初始化
1.5 结构体内存对齐
结构体内存对齐出现的原因:
结构体内存对齐规则:
1.6 修改默认对齐数
小练习:offsetof 宏的实现
1.7 结构体传参
二、位段
2.1、位段的声明
2.2 位段的内存分配
2.3 位段的跨平台问题
结构体属于自定义类型,是用来描述复杂对象的,例如一个人的信息,需要各种不同类型的变量去描述它。
一般声明:
struct Student// 结构体标签
{
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;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
p = &x ???
不能这样做,因为匿名声明的结构体创建的实际上是两个不同的结构体类型。不能从结构体成员中判断是否相同,在编译器中,这两个结构体是不同类型,所以会报错。
结构体自引用是不能直接自己引用自己的结构体类型的,因为无法确定sizeof(结构体),会出现无限套娃的情况。所以需要创建一个自己结构体类型的指针,因为指针大小确定,通过指针作为成员变量实现结构体自引用。
struct Node
{
int data;
struct Node next;
};
//无法确定sizeof(struct Node),这样自引用不可行.
struct Node
{
int data;
struct Node* next;
};
//通过此结构体类型指针来进行自引用,sizeof(struct Node)为确定值,可行.
在自引用时,对结构体进行typedefine时要注意,不能将结构体成员中的struct省略掉。
typedef struct Node
{
int data;
struct Node* next;//此处struct不能省略,因为此处正在对结构体进行类型自定义的过程中,并没有完成。
}Node;
//case1:
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1;为全局变量
struct Point p2; //定义结构体变量p2;在主函数中创建为局部变量
struct Point p3 = {x, y};//初始化:定义变量的同时赋初值。
//case2:
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
}*p;(此处可不用创建结构体变量,可省略)
struct Stu s = {"zhangsan", 20};//初始化
struct Stu s = {.age=20, .name="zhangsan"};//自定义顺序初始化
printf("%s %d",s.name, s.age);//对结构体成员通过结构体变量访问
printf("%s %d",p->name, p->age);//对结构体成员通过结构体指针变量访问
printf("%s %d",(*p).name, (*p).age);//对结构体成员通过结构体指针解引用进行访问
//case3:
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
结构体访问:
1. 通过结构体变量
1.通过结构体指针
1、平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总结:结构体内存对齐目的是空间换时间,提升运行效率。
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在设计结构体时,需要将同类型的成员变量尽量连续放在一起。
可以通过offsetof函数来查看每个成员变量相对于结构体类型指针的偏移量
#include
printf("%d\n", offsetof(struct S2, c1));
通过#pragma 来修改默认对其数以及恢复默认
#include
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明,(在学习宏之后回来补充)
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。(需下来参考函数栈帧)
所以结构体传参的时候,要传结构体的地址。
位段实际上是用时间换空间,使结构体空间尽可能的小,不需要对齐。
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。char也可以,属于整型家族。
2.位段的成员名后边有一个冒号和一个数字。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct A
{
int _a:2; //先创建了一个int类型4个字节的空间,给int _a分配了2比特位的空间大小,还剩30位.
int _b:5; //给int _b分配了5比特位的空间大小,还剩25位
int _c:10; //给int _c分配了10比特位的空间大小,还剩15位
int _d:30; //给int _d分配了30比特位的空间大小,因为前面不够30位,所以重新再开辟一个int类型空间,总共剩17位。
};
看看位段在vs2022中是如何开辟的
但是这只是位段在vs中内存中开辟情况,在跨平台中还会有很多问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
比如在网络中的数据传输,就需要位段的方式来存储数据,使得对数据存储的空间利用率最大,减少数据传输时的压力。