【C语言】自定义类型详解--结构体

1.结构体

1.1结构体的声明

    //结构体的每个成员可以是不同的类型

      struct   结构体名

         {

         成员列表 

     }变量列表;        //变量列表可有可无       

  例如,描述日期:

      4a7d26e71be74f3a8821289fe0c69097.png

  ■特殊的声明(匿名声明):该结构体只能访问一次

      c316ef954a264f27aae9f3845272f9cd.png

          省略了结构体名。只能在结构体声明时使用,别处无法访问。

     f2976edd36464892b6ee559ee48c6f76.png

*p=&b1;编译器会将这条语句判错。虽然两个结构体的成员列表一致,但在编译器看来b1和*p属于两个不同的结构体。

1.2结构体的自引用

在结构中包含一个类型为该结构本身的成员

     87bb08f9965646a1bd88d20d50d1dfd5.png

     a409b773c7504869bd1c0d4215bd0638.png

     比较两个代码段,1是错误的。结构体内只能包含同类型的结构体指针,不能包含同类型的结构体变量。且使用时不能用匿名结构体,应写为:

     77f7b81e8be847b288963171a124feee.png

1.3结构体变量的定义和初始化

    1.3.1结构体变量的定义

          可在声明结构体类型的同时或者声明后再定义。

    2d792a7f5f12422ab2750dbfef878665.png

       1.3.2结构体变量的初始化

初始化:定义变量的同时赋初值

    017581a33e364d8e8d84a1ea2e88a98e.png

       初始化时也可不按照结构体内成员的声明顺序来初始化。在要赋的值前加".成员名"便可实现初始化。

    16842ee83d3343a487e8aa325294b48a.png

1.4结构体内存对齐

    b1dbcb24f86d4401815ef792662bfd5f.png

      请看结构体S1和S2包含的成员是一致的,那么在计算sizeof时你是否会根据字符型数据占1个字节,整型占4个字节而计算出结果为6呢?我们来看一下运行结果。

    000a5c5f43a54e3fbae17699bb4d3afb.png

   为什么会出现这种结果呢?这是因为结构体的内存对齐。

   首先我们看一下内存对齐的规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。

   //VS中默认的值为8 

   //Linux中没有对齐数

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

       结合刚开始给出的例子,画出下图(上图为S1的成员在内存中开辟的空间的位置,下图为S2成员在内存中开辟的空间的位置)。

      S1所占空间为9个字节,根据内存对齐的规则3,结构体总大小为最大对齐数的整数倍,对于结构体S1,其三个成员变量中最大对齐数为4,所以S1的总大小应为12字节;同理,图中S2占6字节,其成员变量中最大对齐数为4,那么S2的总大小为8字节。

29c67f6fa3f841ceb07fde07c73cbe89.png fb50b7b655504d108888cac5cf8f1e06.png     

      由于存在内存对齐,所以对于拥有相同成员变量的结构体而言,不同的成员声明顺序会导致结构体所占空间大小不同。

      为什么存在内存对齐? 

      1. 平台原因(移植原因):

        不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。

      2. 性能原因:

        数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

     即结构体的内存对齐就是拿空间时间的做法。

        可以通过预处理指令#pragma pack() 自己修改对齐数,方式如下:

          

1.5结构体传参

             

      如上代码,结构体传参可将结构体作为参数传递过去,也可将结构体的地址作为参数传递过去,猜想哪种方法更好一些?

     答案是应选函数print2()的传址方式,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

    所以结构体传参时尽量选择传结构体的地址。

2.位段

2.1什么是位段

     位段与结构体的声明类似,有两处不同:

        1.位段对成员的类型有要求。其成员只能是int,unsigned  int或signed int;

        2.成员名后要加上冒号和数字。(数字表示变量所占的比特位数,其值不能超过该变量类型的大小)

     例如,结构体S就是一个位段类型。

         

2.2位段的内存分配

     1. 位段的成员可以是 int、unsigned int、signed int 或者是 char (属于整形家族)类型。
     2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。 若成员为int型,则以4个字节的方式开辟,若成员为char型,则以1字节的方式开辟。
     3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
 

    举个例子:

       

         最终位段变量s在内存中的二进制序列为0000 1100 0000 0110 0000 1000 0000 1001

转换位16进制在内存中即为0C 06 08 09。通过vs进行调试验证结果。

     

     

      所以位段的内存是如何开辟的呢?


      1.因为位段中的成员类型为char型,所以空间上是按照1个字节来开辟的,而位段 S 类型中第一个成员定义的大小为 5 bit, 在赋值时将 a 赋值为12,10的二进制序列为:0000 1100 ,受定义大小的限制,需舍弃一部分序列再存入内存中,所以最终存入内存中的序列为:01100,且规定在分配的内存中是从后向前放入序列(如上图红色方框)。
      2.当成员 a 放入内存中后,第一个字节还剩下3个bit位,而第二个成员占4个bit位,剩下的内存不能放下成员 b,所以新开辟1个字节的空间用来存放成员b,按照步骤1的方式依次放入;
      3.若放入前一个成员后该字节所剩的bit位还能放入下一个成员,则将其继续按从后向前放入序列,否则就开辟新的空间依次存放,最终整个位段所占的内存就为4个字节大小。

   结果如图:

       


      总结:

      跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天学习了吗•

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值