C语言中结构体类型,结构体变量的创建和初始化,结构体中存在的内存对齐,结构体传参, 结构体实现位段

             结构体类型,结构体变量的创建和初始化

     结构体变量的创建

        结构体类型 

        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 进去 ,是不完整的。

        好了,这就是本期的重点内容了,如果有错误的地方,欢迎大家指出来。

 

        

        

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值