c語言之共用體union、枚舉、大小端模式

上一個專題咱們詳細的分享了c語言裏面的結構體用法,讀者在看這些用法的時候,能夠一邊看一邊試驗,掌握了這些基本用法就徹底夠用了,固然在之後的工做中,若是有遇到了更高級的用法,咱們能夠再來總結學習概括。好了,開始咱們今天的主題分享。
c++


1、共用體union:編程


一、什麼是共用體union?數組


    這個共用體,估計你們平時在代碼也比較少見,我去看了一下stm32的例程裏面沒怎麼看到這個用法(下面的示例分享是在stm32裏面找的);其實這個共用體union(也叫聯合體)跟咱們上次分享的結構體定義是很是像的,好比說:類型定義、變量定義、使用方法上很類似。就像下面兩個例子同樣,把許多類型聯合在一塊兒(不過雖然形式上相似,可是具體用法仍是有區別的,下面會講他們之間的區別):app

union st{
   int a;
   char b;
};


圖片


二、共用體與結構體的區別:
ide

      

     結構體相似於一個包裹,結構體中的成員彼此是獨立存在的,分佈在內存的不一樣單元中,他們只是被打包成一個總體叫作結構體而已共用體中的各個成員實際上是一體的,彼此不獨立,他們使用同一個內存單元。能夠理解爲:有時候是這個元素,有時候是那個元素。更準確的說法是同一個內存空間有多種解釋方式。因此共用體用法總結以下:學習


  • union中能夠定義多個成員,union的內存大小由最大的成員的大小來決定。 測試


  • union成員共享同一塊大小的內存,一次只能使用其中的一個成員。編碼


  • 對某一個成員賦值,會覆蓋其餘成員的值(這是爲啥呢?,簡單來說就是由於他們共享一塊內存。但前提是成員所佔字節數相同,當成員所佔字節數不一樣時只會覆蓋相應字節上的值,好比對char成員賦值就不會把整個int成員覆蓋掉,由於char只佔一個字節,而int佔四個字節)。spa


  • 共用體union的存放順序是全部成員都從低地址開始存放的。
    指針


三、代碼實戰:


    #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.四、
&u1.a = 0061FEA0.
&u1.b = 0061FEA0.
the sizeof u1 is 8


四、小結:


  • union的sizeof測到的大小實際是union中各個元素裏面佔用內存最大的那個元素的大小。由於能夠存的下這個就必定可以存的下其餘的元素。


  • union中的元素不存在內存對齊的問題,由於union中實際只有1個內存空間,都是從同一個地址開始的(開始地址就是整個union佔有的內存空間的首地址),因此不涉及內存對齊。


2、枚舉


一、什麼是枚舉?


     枚舉在C語言中實際上是一些符號常量集。直白點說:枚舉定義了一些符號,這些符號的本質就是int類型的常量,每一個符號和一個常量綁定。這個符號就表示一個自定義的一個識別碼,編譯器對枚舉的認知就是符號常量所綁定的那個int類型的數字。枚舉符號常量和其對應的常量數字相對來講,數字不重要,符號才重要。符號對應的數字只要彼此不相同便可,沒有別的要求。因此通常狀況下咱們都不明確指定這個符號所對應的數字,而讓編譯器自動分配。(編譯器自動分配的原則是:從0開始依次增長。若是用戶本身定義了一個值,則從那個值開始日後依次增長)。


二、爲何要用枚舉,和宏定義作對比:

  

     (1)C語言沒有枚舉是能夠的。使用枚舉其實就是對一、0這些數字進行符號化編碼,這樣的好處就是編程時能夠不用看數字而直接看符號。符號的意義是顯然的,一眼能夠看出。而數字所表明的含義除非看文檔或者註釋。


    (2)宏定義的目的和意義是:不用數字而用符號。從這裏能夠看出:宏定義和枚舉有內在聯繫。宏定義和枚舉常常用來解決相似的問題,他們倆基本至關能夠互換,可是有一些細微差異。


    (3)宏定義和枚舉的區別:


  • 枚舉是將多個有關聯的符號封裝在一個枚舉中,而宏定義是徹底散的。也就是說枚舉實際上是多選一。


    (4)使用枚舉狀況:


  • 什麼狀況下用枚舉?當咱們要定義的常量是一個有限集合時(譬如一星期有7天,譬如一個月有31天,譬如一年有12個月····),最適合用枚舉。(其實宏定義也行,可是枚舉更好)


  • 不能用枚舉的狀況下(定義的常量符號之間無關聯,或者無限的),這個時候就用宏定義。


總結:

 

       宏定義先出現,用來解決符號常量的問題;後來人們發現有時候定義的符號常量彼此之間有關聯(多選一的關係),用宏定義來作雖然能夠可是不貼切,因而乎發明了枚舉來解決這種狀況。


三、代碼示例:


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++裏面不能夠這樣操做,必須強制類型轉換)。


  • 枚舉元素不是字符常量也不是字符串常量,使用時不要加單、雙引號。


  • 枚舉類型是一種基本數據類型,而不是一種構造類型,由於它不能再分解爲任何基本類型。


  • 枚舉值是常量,不是變量


3、大小端模式:


一、什麼是叫大小端模式?


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


           在這種格式中,字數據的高字節存儲在低地址中,而字數據的低字節則存放在高地址中。


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


    與大端存儲格式相反,在小端存儲格式中,低地址中存放的是字數據的低字節,高地址存放的是字數據的高字節。


二、實際解釋:


  ----- 咱們把一個16位的整數0x1234存放到一個短整型變量(short)中。這個短整型變量在內存中的存儲在大小端模式由下表所示:


地址偏移

大端模式

小端模式

0x00

12

34

0x01

34

12


 說明:

        由上表所知,採用大小模式對數據進行存放的主要區別在於在存放的字節順序,大端方式將高位存放在低地址,小端方式將低位存放在低地址。


三、代碼實戰來判斷大小端模式:


#include <stdio.h>

    // 共用體中很重要的一點:a和b都是從u1的低地址開始的。
   // 假設u1所在的4字節地址分別是:0、一、二、3的話,那麼a天然就是0、一、二、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;
    }


演示結果:

                 

  這是小端模式


四、看似可行實則不行的測試大小端方式:位與、移位、強制類型轉化:


    #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)強制類型轉換和上面分析同樣的。


五、通訊系統中的大小端(數組的大小端)

(1)譬如要經過串口發送一個0x12345678給接收方,可是由於串口自己限制,只能以字節爲單位來發送,因此須要發4次;接收方分4次接收,內容分別是:0x十二、0x3四、0x5六、0x78.接收方接收到這4個字節以後須要去重組獲得0x12345678(而不是獲得0x78563412)。


(2)因此在通訊雙方須要有一個默契,就是:先發/先接的是高位仍是低位?這就是通訊中的大小端問題。


(3)通常來講是:先發低字節叫小端;先發高字節就叫大端。在實際操做中,在通訊協議裏面會去定義大小端,明確告訴你先發的是低字節仍是高字節。


(4)在通訊協議中,大小端是很是重要的,你們使用別人定義的通訊協議仍是本身要去定義通訊協議,必定都要注意標明通訊協議中大小端的問題。


4、總結:


        上面分享了一些咱們經常使用的一些用法,掌握了這些就能夠了,當往後工做中有其餘用法,再總結概括,完善本身的知識體系。

相關文章
相關標籤/搜索