c语言之共用体union、枚举、大小端模式

发表于 4年以前  | 总阅读数:587 次

上一个专题我们详细的分享了c语言里面的结构体用法,读者在看这些用法的时候,可以一边看一边试验,掌握了这些基本用法就完全够用了,当然在以后的工作中,如果有遇到了更高级的用法,我们可以再来总结学习归纳。好了,开始我们今天的主题分享。

一、共用体union:

1、什么是共用体union?

这个共用体,估计大家平时在代码也比较少见,我去看了一下stm32的例程里面没怎么看到这个用法(下面的示例分享是在stm32里面找的);其实这个共用体union(也叫联合体)跟我们上次分享的结构体定义是非常像的,比如说:类型定义、变量定义、使用方法上很相似。就像下面两个例子一样,把许多类型联合在一起(不过虽然形式上类似,但是具体用法还是有区别的,下面会讲他们之间的区别):

union st{
   int a;
   char b;
};

2、共用体与结构体的区别:

结构体类似于一个包裹,结构体中的成员彼此是独立存在的,分布在内存的不同单元中,他们只是被打包成一个整体叫做结构体而已共用体中的各个成员其实是一体的,彼此不独立,他们使用同一个内存单元。可以理解为:有时候是这个元素,有时候是那个元素。更准确的说法是同一个内存空间有多种解释方式。所以共用体用法总结如下:

  • union中可以定义多个成员,union的内存大小由最大的成员的大小来决定。
  • union成员共享同一块大小的内存,一次只能使用其中的一个成员。
  • 对某一个成员赋值,会覆盖其他成员的值(这是为啥呢?,简单来讲就是因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)。
  • 共用体union的存放顺序是所有成员都从低地址开始存放的。

3、代码实战:

    #include <stdio.h>

    typedef union{
        int a;
           char c;
          //int a;
         // int b;
  }st;
     int main(void)
    {
         st haha;
         haha.c='B';
        //  haha.a=10;
       //haha.b=60;

       printf("the haha size is %d\n",sizeof(haha));
       printf("haha.c=%d\n",haha.c);

       return 0;
} 
#include <stdio.h>

 typedef union{

   int a;
   char c;
   int b;
 }st;
 int main(void)
 {

           st haha;
           haha.c='B';
           haha.a=10;
           haha.b=60;

           printf("the haha size is %d\n",sizeof(haha));
           printf("haha.c=%d,haha.a=%d,haha.b=%d\n",haha.c,haha.a,haha.b);

          printf("the a is 0x%x\n",&haha.a);
          printf("the c is 0x%x\n",&haha.c);
          printf("the b is 0x%x\n",&haha.b);


         return 0;
 } 

演示结果:

  the haha size is 4
   haha.c=66
 the haha size is 4
 haha.c=60,haha.a=60,haha.b=60
 the a is 0x61feac
 the c is 0x61feac
 the b is 0x61feac

说明:

通过上面的代码示例,读者可以发现这个共用体的大小,并不是像我们之前结构体那样是把每个成员所占内存大小加起来,而是我们上面说的那样,共用体由成员占用内存大小最大的那个决定的,上面的示例中int 占用4个字节大小,为最大的,所以sizeof(haha)得出结果就是4个字节大小,而且读者细心可以发现到打印出来的结果a和b都是60,它是访问内存占用大小最大的那个成员的数值,因为那个'B'的acii码值是是66;通过示例,我们也发现共用体访问其成员方式跟结构体是一样的(上面也有说到过)。下面是和结构体做对比的代码示例:

       #include <stdio.h>
      // 共用体类型的定义
     struct mystruct
    {
           int a;
           char b;
     };
       // a和b其实指向同一块内存空间,只是对这块内存空间的2种不同的解析方式。
      // 如果我们使用u1.a那么就按照int类型来解析这个内存空间;如果我们使用 
     // u1.b那么就按照char类型
      // 来解析这块内存空间。
    union myunion
    {
       int a;
       char b;
       double c;
   };

   int main(void)
   {


        struct mystruct s1;
        s1.a = 23;
        printf("s1.b = %d.\n", s1.b);       // s1.b = 0. 结论是s1.a和s1.b是独立无关的
        printf("&s1.a = %p.\n", &s1.a);
        printf("&s1.b = %p.\n", &s1.b);

        union myunion u1;       // 共用体变量的定义
        u1.a = 23;  
        u1.b='B';
        u1.a=u1.b;          // 共用体元素的使用
        printf("u1.a = %d.\n", u1.a);
        printf("u1.b = %d.\n", u1.b);   
        printf("u1.c = %d.\n", u1.c);   
       // u1.b = 23.结论是u1.a和u1.b是相关的
       // a和b的地址一样,充分说明a和b指向同一块内存,只是对这块内存的不同解析规则    
        printf("&u1.a = %p.\n", &u1.a);
        printf("&u1.b = %p.\n", &u1.b);

        printf("the sizeof u1 is %d\n",sizeof(u1));

         return 0;
 }

演示结果:

s1.b = 22.
&s1.a = 0061FEA8.
&s1.b = 0061FEAC.
u1.a = 66.
u1.b = 66.
u1.c = 66.4、
&u1.a = 0061FEA0.
&u1.b = 0061FEA0.
the sizeof u1 is 8

4、小结:

  • union的sizeof测到的大小实际是union中各个元素里面占用内存最大的那个元素的大小。因为可以存的下这个就一定能够存的下其他的元素。
  • union中的元素不存在内存对齐的问题,因为union中实际只有1个内存空间,都是从同一个地址开始的(开始地址就是整个union占有的内存空间的首地址),所以不涉及内存对齐。

二、枚举

1、什么是枚举?

枚举在C语言中其实是一些符号常量集。直白点说:枚举定义了一些符号,这些符号的本质就是int类型的常量,每个符号和一个常量绑定。这个符号就表示一个自定义的一个识别码,编译器对枚举的认知就是符号常量所绑定的那个int类型的数字。枚举符号常量和其对应的常量数字相对来说,数字不重要,符号才重要。符号对应的数字只要彼此不相同即可,没有别的要求。所以一般情况下我们都不明确指定这个符号所对应的数字,而让编译器自动分配。(编译器自动分配的原则是:从0开始依次增加。如果用户自己定义了一个值,则从那个值开始往后依次增加)。

2、为什么要用枚举,和宏定义做对比:

(1)C语言没有枚举是可以的。使用枚举其实就是对1、0这些数字进行符号化编码,这样的好处就是编程时可以不用看数字而直接看符号。符号的意义是显然的,一眼可以看出。而数字所代表的含义除非看文档或者注释。

(2)宏定义的目的和意义是:不用数字而用符号。从这里可以看出:宏定义和枚举有内在联系。宏定义和枚举经常用来解决类似的问题,他们俩基本相当可以互换,但是有一些细微差别。

(3)宏定义和枚举的区别:

  • 枚举是将多个有关联的符号封装在一个枚举中,而宏定义是完全散的。也就是说枚举其实是多选一。

(4)使用枚举情况:

  • 什么情况下用枚举?当我们要定义的常量是一个有限集合时(譬如一星期有7天,譬如一个月有31天,譬如一年有12个月····),最适合用枚举。(其实宏定义也行,但是枚举更好)
  • 不能用枚举的情况下(定义的常量符号之间无关联,或者无限的),这个时候就用宏定义。

总结:

宏定义先出现,用来解决符号常量的问题;后来人们发现有时候定义的符号常量彼此之间有关联(多选一的关系),用宏定义来做虽然可以但是不贴切,于是乎发明了枚举来解决这种情况。

3、代码示例:

a、几种定义方法:

  /*        // 定义方法1,定义类型和定义变量分离开
   enum week
   {
            SUN,        // SUN = 0
            MON,        // MON = 1;
            TUE,
            WEN,
            THU,
            FRI,
            SAT,
  };

  enum week today;
  */

  /*        // 定义方法2,定义类型的同时定义变量
   enum week
   {
            SUN,        // SUN = 0
            MON,        // MON = 1;
                TUE,
                WEN,
            THU,
            FRI,
            SAT,
    }today,yesterday;
  */

   /*        // 定义方法3,定义类型的同时定义变量
     enum 
     {
            SUN,        // SUN = 0
            MON,        // MON = 1;
            TUE,  
            WEN,
            THU,
            FRI,
            SAT,
    }today,yesterday;
     */

    /*        // 定义方法4,用typedef定义枚举类型别名,并在后面使用别名进行变量定义
          typedef enum week
          {
            SUN,        // SUN = 0
            MON,        // MON = 1;
            TUE,
            WEN,
            THU,
            FRI,
            SAT,
          }week;
   */

  /*        // 定义方法5,用typedef定义枚举类型别名,并在后面使 
 用别名进行变量定义
        typedef enum 
        {
            SUN,        // SUN = 0
            MON,        // MON = 1;
            TUE,
            WEN,
            THU,
            FRI,
            SAT,
          }week;

b、错误类型举例(下面的举例中也加入了结构体作为对比):

 /*    // 错误1,枚举类型重名,编译时报错:error: conflicting
//    types for ‘DAY’
      typedef enum workday
      {
             MON,       // MON = 1;
             TUE,
             WEN,
             THU,
             FRI,
       }DAY;

     typedef enum weekend
         {
            SAT,
            SUN,
         }DAY;
      */

     /*    // 错误2,枚举成员重名,编译时报错:redeclaration //of
  // enumerator ‘MON’
   typedef enum workday
      {
            MON,       // MON = 1;
            TUE,
            WEN,
            THU,
            FRI,
       }workday;

      typedef enum weekend
      {
         MON,
         SAT,
         SUN,
       }weekend;
     // 结构体中元素可以重名
     typedef struct 
     {
        int a;
        char b;
     }st1;

      typedef struct 
      {
         int a;
         char b;
      }st2;
      */

说明:

经过测试,两个struct类型内的成员名称可以重名,而两个enum类型中的成员不可以重名。实际上从两者的成员在访问方式上的不同就可以看出了。struct类型成员的访问方式是:变量名.成员,而enum成员的访问方式为:成员名。因此若两个enum类型中有重名的成员,那代码中访问这个成员时到底指的是哪个enum中的成员呢?所以不能重名。但是两个#define宏定义是可以重名的,该宏名真正的值取决于最后一次定义的值。编译器会给出警告但不会error,下面的示例会让编译器发出A被重复定义的警告。

 #include <stdio.h>
 #define A  5
 #define A 7

 int main(void)
 {
    printf("hello world\n");

   return 0;
  } 

c、代码实战演示:

#include <stdio.h>

   typedef enum week
   {
         SUN,       // SUN = 0
         MON,       // MON = 1;
         TUE,        //2
         WEN,        //3
         THU,
         FRI,
         SAT,
   }week;

  int main(void)
  {



      // 测试定义方法4,5
        week today;
       today = WEN;
       printf("today is the %d th day in week\n", today);

       return 0;
  } 

演示结果:

 today is the 3 th day in week

d、接着我们把上面枚举变量改变它的值(不按照编译模式方式来),看看会发生什么变化:

  #include <stdio.h>

  typedef enum week
 {
        SUN,        // SUN = 0
        MON=8,      // MON = 1;
        TUE,        //2
        WEN,        //3
        THU,
        FRI,
      SAT,
  }week;

      int main(void)
      {

          // 测试定义方法4,5
       week today,hh;
       today = WEN;
       hh=SUN;
       printf("today is the %d th day in week\n", SUN);
       printf("today is the %d th day in week\n", today);
       return 0;
  } 

演示结果(我们可以看到改变了枚举成员值,它就在这个基础递增下面的成员值):

        today is the 0 th day in week
        today is the 10 th day in week

注意:

  • 这里要注意,只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量,如一定要把数值赋予枚举变量,则必须用强制类型转换,但是我在测试时,发现编译器居然可以这样赋值,读者最好自己测试一下(不过这里后面发现在c语言里面可以这样操作,在c++里面不可以这样操作,必须强制类型转换)。
  • 枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
  • 枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
  • 枚举值是常量,不是变量。

三、大小端模式:

1、什么是叫大小端模式?

a、什么叫大端模式(big-endian)?

在这种格式中,字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。

b、什么叫小端模式(little-endian)?

与大端存储格式相反,在小端存储格式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。

2、实际解释:

----- 我们把一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示:

地址偏移 大端模式 小端模式
0x00 12 34
0x01 34 12

说明:

由上表所知,采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将低位存放在低地址。

3、代码实战来判断大小端模式:

#include <stdio.h>

    // 共用体中很重要的一点:a和b都是从u1的低地址开始的。
   // 假设u1所在的4字节地址分别是:0、1、2、3的话,那么a自然就是0、1、2、3;
  // b所在的地址是0而不是3.
    union myunion
    {
        int a;
        char b;
     };

    // 如果是小端模式则返回1,小端模式则返回0
      int is_little_endian(void)
     {
          union myunion u1;
          u1.a = 1;             // 地址0的那个字节内是1(小端)或者0(大端)
          return u1.b;
   }

  int is_little_endian2(void)
  {
         int a = 1;
         char b = *((char *)(&a));      // 指针方式其实就是共用体的本质

         return b;
 }


      int main(void)
      {
       int i = is_little_endian2();
      if (i == 1)
     {
           printf("小端模式\n");
     }
     else
    {
          printf("大端模式\n");
    }

        return 0;
    }

演示结果:

  这是小端模式

4、看似可行实则不行的测试大小端方式:位与、移位、强制类型转化:

   #include <stdio.h>


    int main(void)
    {
              // 强制类型转换
              int a;
              char b;
              a = 1;
              b = (char)a;
             printf("b = %d.\n", b);  // b=1

                   /*
                // 移位
                 int a, b;
                 a = 1;
                 b = a >> 1;
                 printf("b = %d.\n", b);    //b=0
                  */
               /*
       // 位与
        int a = 1;
        int b = a & 0xff;       // 也可以写成:char b
        printf("b = %d.\n", b);   //b=1  
     */


           return 0;
     }

说明:

(1)位与运算:

结论:位与的方式无法测试机器的大小端模式。(表现就是大端机器和小 端机器的&运算后的值相同的)

理论分析:位与运算是编译器提供的运算,这个运算是高于内存层次的(或者说&运算在二进制层次具有可移植性,也就是说&的时候一定是高字节&高字节,低字节&低字节,和二进制存储无关)。

(2)移位:

结论:移位的方式也不能测试机器大小端。

理论分析:原因和&运算符不能测试一样,因为C语言对运算符的级别是高于二进制层次的。右移运算永远是将低字节移除,而和二进制存储时这个低字节在高位还是低位无关的。

(3)强制类型转换和上面分析一样的。

5、通信系统中的大小端(数组的大小端)

(1)譬如要通过串口发送一个0x12345678给接收方,但是因为串口本身限制,只能以字节为单位来发送,所以需要发4次;接收方分4次接收,内容分别是:0x12、0x34、0x56、0x78.接收方接收到这4个字节之后需要去重组得到0x12345678(而不是得到0x78563412)。

(2)所以在通信双方需要有一个默契,就是:先发/先接的是高位还是低位?这就是通信中的大小端问题。

(3)一般来说是:先发低字节叫小端;先发高字节就叫大端。在实际操作中,在通信协议里面会去定义大小端,明确告诉你先发的是低字节还是高字节。

(4)在通信协议中,大小端是非常重要的,大家使用别人定义的通信协议还是自己要去定义通信协议,一定都要注意标明通信协议中大小端的问题。

四、总结:

上面分享了一些我们常用的一些用法,掌握了这些就可以了,当日后工作中有其他用法,再总结归纳,完善自己的知识体系。

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/jtT8Z6qOmJcurBSDMFKY2w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录