结构体类型,结构体变量的创建和初始化
结构体变量的创建
结构体类型
C语言中,“struct” 是一种数据类型定义关键字,用于定义结构体。
而struct stu是结构体类型,而stu称为结构体类型名称。
这么说有点抽象,例如我们想描述一个学生的信息:
之后在这里我们就需要创建学生信息变量了,在这里也就是结构体类类型变量:
上图创建的是全局结构体类型变量s1,s2,s3
下图创建的是局部结构体变量:
在这里的s4是局部结构体变量,
s1,s2,s3,s4也可以叫做变量名。
还有几种结构体特殊声明,例如;
一:
上面的是两个匿名结构体,并且p = &x 这种操作是不可行的,因为编译器会认为这两个结构体类型是不同的,因为结构的名字都没有,编译器是识别不了的。
二:
在结构中包含⼀个类型为该结构本身的成员是否可以呢? 比如,定义⼀个链表的节点:
其实是不行的,因为⼀个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。
那么可以怎么使用呢,像这样:
同类型的结构体指针变量:next
的类型是struct s*
,即指向struct s
类型的指针。这意味着它可以指向任何一个struct s
类型的结构体变量,从而建立起链表等数据结构。
例如,在链表中,每个节点都是一个struct s
类型的结构体,一个节点的next
指针指向链表中的下一个节点,下一个节点也是struct s
类型的结构体,所以next
是同类型的结构体指针变量。
三:
typedef是可以对结构体类型重命名的,那么:
是否可以?
其实是是不可以的,因为他是对这块结构体重命名:
struct s
{
int data;
Node* next;
}
成了Node,在重命名前成员名都有了Node肯定是不行的,所以要:
四:
这里 a[20] 和 *p 代表什么呢?
定义了一个由 20 个这种结构体组成的数组 a 和一个指向这种结构体的指针 p
结构体变量初始化
结构体变量的创建和初始化
对于结构体变量的初始化,一般有两种方式 :
一.按照结构体里成员顺序初始化:
可以看到,这种方式是按照结构体里面的成员顺序来初始化的。
二.使用结构体里成员名指引来初始化:
可以看到,这种方式是按照结构体里成员名指引来初始化:
那么,我们应该怎么打印出来呢?
可以这样:
可以看到,我们是利用 结构体变量名 . 结构体成员名 这种形式来打印的。
结构体中存在的内存对齐
结构体中存在的内存对齐
也就是结构体的大小占多少个字节,这里有如下几条规则:
1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。
2. 其他成员要对齐到某个数字(对齐数)的整数倍地址处开始存放成员大小。
对齐数 = 编译器默认的⼀个对齐数 与 该成员(类型)大小的较小值。
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
我们来一个例子说明:
这个结构体是 s1 大小是多少字节?
答案是 16
我们来画图解释这个问题:
这里有16个连续的内存空间,从偏移量0开始。
首先 结构体 s1 里面的成员第一个是 double 类型的 变量 d ,double 类型大小是8个字节,vs编译器默认对齐数是 8 ,则 对齐数为 编译器默认的⼀个对齐数 与 该成员(类型)大小的较小值,就是 8,但是因为是第一个成员,可以直接从 0 偏移量开始存放。
第二个成员是 char 类型的 c 变量 ,自身大小 1 个字节 ,vs编译器默认对齐数是 8 ,则 对齐数为 编译器默认的⼀个对齐数 与 该成员(类型)大小的较小值,就是 1 ,刚好这里面的偏移量都是 1 的倍数。即:
第三个成员是 int 类型的 i变量,自身大小占4个字节,vs编译器默认对齐数是 8 ,则 对齐数为 编译器默认的⼀个对齐数 与 该成员(类型)大小的较小值,就是 4 ,而且要对准到偏移量 为 4的倍数位置开始存放 ,即:
最后一个问题,结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的 整数倍。比如上面的 成员的对齐数中最大的是 8 ,所以整体大小要是8的倍数,即:
还有一种,就是如果结构体嵌套了结构体的情况,应该怎么解决呢?
比如:
首先,结构体类型 struct s4 里面的成员第一个是 char 类型的 变量 c1 ,变量 c1 大小是 1 个字节,可以直接从 0 偏移量开始存放:
第二个成员是一个 结构体类型 struct s1 的变量 s3 ,他嵌套在结构体 struct s4 里面了,那么,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,上面我们知道了结构体类型 struct s1 的变量 s3里面的成员最大对齐数是 double 成员的对齐数 是8,而且 结构体类型 struct s1 大小 16个字节是应该这样存放:
剩最后一个 double 类型的 d ,他 类型大小是 8个字节,vs编译器默认对齐数为8,则取他们两个最小的作为对齐数,那就是8,要对齐到8的倍数上面:
最后 结构体的整体大小 就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍,这里嵌套的结构体 struct s1 他自身里面的 double d 与 char c 与 int i 中最大对齐数成员应该是double 的8,再与 struct s4 结构体 里面其他成员比较取得最大对齐数,可以得到是 8 ,结构体的整体大小就是8的整数倍,就刚好是 32
最后一种,就是结构体内包含了数组应该怎么判断:
首先,char c 作为第一个成员,从偏移量0的位置开始存放:
然后,是 int 类型的数组 arr ,int 大小为4个字节,vs编译器默认对齐数为8,取他们最小值,也就是 4,接着 数组 arr 的大小为 4*7=28 个字节大小,要从 4 的整数倍开始存放:
然后,是 double 类型,大小是8个字节,vs编译器默认对齐数为8,取他们的最小值,就是8,变量 double k 大小占8个字节,要从到 偏移量为 8 的整数倍的地方开始 存放:
最后,还要看 结构体整体大小 有没有对齐 他们之中成员对齐数最大的 double 为 8 的整数倍处, 。
结果刚好是40,所以这个结构体大小就是40个字节。
修改默认对齐数 #pragma 这个预处理指令,可以改变编译器的默认对齐数。
结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。
结构体传参
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结论: 结构体传参的时候,要传结构体的地址。
结构体实现位段
1.位段的成员名后边有⼀个冒号和⼀个数字。
2.一般会将相同类型的数据放在一起定义为位段。
3.成员名后面的数字代表的是占比特位的大小。
像这样:
那么,struct A 的大小是多少个字节呢?
答案是:8
分析:
首先,因为是,int 类型的结构体位段,内存会先创建 4 个字节来尝试存放这些成员变量:
第一个成员 _a 占2个比特位:
第二个成员 _b占 5 个比特位:
第三个成员 _c占 10 个比特位:
第四个成员 _d 占 30 个比特位,由于这四个字节不够存储了,所以继续创建4个字节并重新开始存放:
所以这个结构体一位段一共占8个字节:
注意事项:
1.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式一次一次(也就是四个四个字节地开辟或者一个一个字节开辟,具体看成员类型)来开辟的。
2.int 位段被当成有符号数还是无符号数是不确定的。
3.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
4.位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
5.当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
6.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段使用的注意事项:
位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在⼀个变量中,然后赋值给位段的成员。
像这样:
那么,赋值之后如果比特位不够怎么办?
这时就要发生截断了,假如要存十进制 42 进去 上图的 变量 _b , 十进制 42 表示 的二进制:00101010 ,因为只能存5个比特位,这时候截断把前面001去处了,只存了后面01010 进去 ,是不完整的。
好了,这就是本期的重点内容了,如果有错误的地方,欢迎大家指出来。