欢迎来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宏和#pragma预处理指令
offsetof宏是用来计算偏移量的,在cplusplus网站可以查询
offsetof - C++ Reference (cplusplus.com)
offsetof的使用如下:
#include<stdio.h>
#include<stddef.h>
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;
}
感谢各位同伴的支持,本期结构体篇就讲解到这啦,如果你觉得写的不错的话,可以给个一键三连,点赞关注+收藏,若有不足,欢迎各位在评论区讨论。