32.共用體和大小端及枚舉


32.1.共用體基本特性概述
(1)共用體union和結構體struct在類型定義和變量定義和使用方法上很相似;結構體類似於包裹,結構體中的成員彼此獨立存在於不同的內存單元中,它們只是被打包成整體被稱爲結構體而已;共用體中的各個成員彼此存在於相同的內存單元中,該內存單元空間有多種解釋方式,共用體union就是對同1塊內存中存儲的二進制的不同的理解方式。
(2)union的sizeof測到的大小實際是union中各個元素裏面佔用內存最大的那個元素的大小;union中的元素不存在內存對齊的問題,因爲union中實際只有1個內存空間,開始地址均相同(開始地址即整個union佔有的內存空間的首地址),則不涉及內存對齊。


32.2.共用體的主要用途
(1)共用體和結構體的相同點是操作語法幾乎相同;不同點是struct是多個獨立元素(內存空間)打包;而union是1個元素(內存空間)的多種不同解析方式。
(2)共用體就用在那種對同1個內存單元進行多種不同規則解析的這種情況下;C語言中其實是可以沒有共用體的,用指針和強制類型轉換可以替代共用體完成同樣的功能,但是共用體的方式更簡單、更便捷、更好理解。


32.3.大小端模式概述
(1)在計算機串行通信中(如串口)每次只能發送1個字節,發送1個int類型的數就存在byte0、byte1、byte2、byte3字節發送和接收順序問題,即發送方和接收方必須按照同樣的字節順序來通信,否則就會出現錯誤,此即通信系統中的大小端模式(大端模式(big endian)和小端模式(little endian))。
(2)此處的大小端模式,更多是指計算機存儲系統的大小端,在計算機內存/硬盤/Nnad中,因爲存儲系統是32位的,但數據仍然是以字節爲單位的,則1個32位的二進制在內存中存儲時有2種分佈方式:高字節對應高地址(小端模式)、高字節對應低地址(大端模式)。
(3)大端模式和小端模式本身沒有對錯,沒有優劣,理論上按照大端或小端都可以,但是要求必須存儲時和讀取時按照同樣的大小端模式來進行,否則會出錯。
(4)現實中的情況:有些CPU用大端(譬如C51單片機);有些CPU用小端(譬如ARM);普遍來說大部分CPU是用小端模式,大端模式的CPU不算多;當程序員不知道當前環境的大小端模式時就需要用代碼來檢測當前環境的大小端模式。


32.4.測試機器大小端模式
(1)在不知道當前機器的大小端模式時,我們可通過指針方式或union共用體方式測試機器的大小端模式。
(2)位與運算:位與的方式無法測試機器的大小端模式(表現就是大端機器和小端機器的&運算後的值相同的);因爲位與運算是編譯器提供的運算,該運算是高於內存層次的(&運算在二進制層次具有可移植性,即&運算時是高字節&高字節,低字節&低字節,和二進制存儲順序無關)。
(3)移位:移位的方式也不能測試機器大小端模式;因爲C語言對運算符的級別是高於二進制層次的,右移運算永遠是將低字節移除,而和二進制存儲時該低字節在高位還是低位無關的。
(4)強制類型轉換:強制類型轉換也不能測試機器大小端模式;因爲C語言處理強制類型轉換時在二進制層次具有可移植性。


32.5.通信系統中的大小端
(1)譬如要通過串口發送1個0x12345678給接收方,但是因爲串口本身限制,只能以字節爲單位來發送,所以需要發4次;接收方分4次接收,內容分別是0x12、0x34、0x56、0x78,接收方接收到這4個字節之後需要去重組得到0x12345678(而不是得到0x78563412);則在通信雙方需要約定好先發/先接的是高位還是低位,此即通信中的大小端問題。
(2)先發低字節叫小端;先發高字節就叫大端;實際操作中,在通信協議裏面會去定義大小端,其會明確告訴你先發的是低字節還是高字節。
(3)在通信協議中,大小端是非常重要的,無論是使用別人定義的通信協議或者自己要去定義通信協議,一定都要注意標明通信協議中大小端的問題。


32.6.枚舉的概述
(1)枚舉在C語言中是符號常量集,即枚舉定義了某些符號,這些符號的本質就是int類型的常量,每個符號和想對應的常量綁定,編譯器對枚舉的認知就是符號常量所綁定的那個int類型的數字。
(2)枚舉中的枚舉值都是常量數字(枚舉值是全局的,可直接單獨使用);枚舉符號常量和其對應的常量數字相對來說,數字不重要,符號才重要;符號對應的數字只要彼此不相同即可,沒有別的要求;則程序員都不明確指定該符號所對應的數字,而讓編譯器自動分配(編譯器自動分配的原則:從0開始依次增加,若用戶自己定義了某個值,則從那個值開始往後依次增加)。
(3)C語言沒有枚舉是可以的,使用枚舉其實就是對1、0這些數字進行符號化編碼,這樣的好處就是編程時可以不用看數字而直接看符號,符號的意義是顯然的,一眼可以看出,而數字所代表的含義除非看文檔或者註釋。
(4)宏定義的目的和意義是:不用數字而用符號,宏定義和枚舉有內在聯繫;宏定義和枚舉經常用來解決類似的問題,宏定義和枚舉基本相當可以互換,但是有一些細微差別。


32.7.宏定義和枚舉的區別
(1)枚舉是將多個有關聯的符號封裝在某個枚舉中(枚舉即多選1,限定了枚舉變量的取值範圍);而宏定義是完全分散定義各個符號常量的。
(2)當定義的常量符號是某個有限集合時(譬如1星期有7天,譬如1月有31天,譬如1年有12個月···),推薦用枚舉;當定義的常量符號之間無關聯,或者無限的(此時無法使用枚舉),推薦使用宏定義。
(3)宏定義先出現,用來解決符號常量的問題;後來發現有時候定義的符號常量彼此之間有關聯(多選1的關係),用宏定義來實現極其不貼切,則發明了枚舉。


32.union
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:共同體和大小端及枚舉
 * 功能:測試共用體的基本特性。
 */
#include <stdio.h>

// 結構體類型定義
struct mystruct
{
    int a;
    char b;
};

// 共用體類型定義
union myunion
{
    int a;
    char b;
};

// 使用sizeof測試union
typedef union xx
{
    int a;
    char b;
}xxx;

// 測試同一內存空間使用不同方式去解析
union test
{
    int a;
    float b;
};

int main(int argc, char **argv)
{
    // 測試結構體類型
    struct mystruct s1;
    s1.a = 88;
    printf("s1.b = %d.\n", s1.b);                           // s1.b = -119.
    printf("&s1.a = %p. &s1.b = %p.\n", &s1.a, &s1.b);      // &s1.a = 0xbfa5c674. &s1.b = 0xbfa5c678.

    // 測試共用體類型
    union myunion u1;
    u1.a = 88;
    printf("u1.b = %d.\n", u1.b);                           // u1.b = 88.
    printf("&u1.a = %p. &u1.b = %p.\n", &u1.a, &u1.b);      // &u1.a = 0xbfa5c67c. &u1.b = 0xbfa5c67c.

    // 使用sizeof測試
    printf("sizeof(union xx) = %d.\n", sizeof(union xx));   // sizeof(union xx) = 4.
    printf("sizeof(xxx) = %d.\n", sizeof(xxx));             // sizeof(xxx) = 4.

    // 測試同一內存空間使用不同方式去解析
    union test t1;
    t1.a = 1123477881;
    printf("t1.b = %f.\n", t1.b);                           // t1.b = 123.456001.

    // 使用指針和強制類型轉換替代共用體 
    int a = 1123477881;
    printf("*((float *)&a) = %f.\n", *((float *)&a));       // *((float *)&a) = 123.456001.

    return 0;
}

32.endian_test_error
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:共用體和大小端及枚舉
 * 功能:演示通過位與、移位、強制類型轉化測試機器的大小端模式。
 */

#include <stdio.h>

int main(void)
{
    // 位與測試機器大小端模式錯誤
    int a = 1;
    int b = a & 0xff;       
    if (1 == b)
    {
        printf("當前環境爲小端模式\n");
    }
    else
    {
        printf("當前環境爲大端模式\n");
    }

    // 移位測試機器大小端模式錯誤
    int c = 1, d = 0;
    d = c >> 1;
    if (0 == d)
    {
        printf("當前環境爲小端模式\n");
    }
    else
    {
        printf("當前環境爲大端模式\n");
    }


    // 強制類型轉換測試機器大小端模式錯誤
    int e = 1;
    char f = (char)e;
    if (1 == f)
    {
        printf("當前環境爲小端模式\n");
    }
    else
    {
        printf("當前環境爲大端模式\n");
    }

    return 0;
}


32.endian_test_ok
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:共用體和大小端及枚舉
 * 功能:通過共用體和指針方式分別測試當前環境的大小端模式。
 */
#include <stdio.h>

union myunion 
{
    int a;
    char b;
};

// 通過共用體測試當前環境的大小端模式
// 若當前環境是小端模式則返回1,若當前環境是大端模式返回0
int is_little_endian1(void)
{
    union myunion u1;
    u1.a = 1;                       // 起始地址處的那個字節內是1(小端)或者0(大端)
    return u1.b;
}

// 通過指針方式測試當前環境的大小端模式
// 若當前環境是小端模式則返回1,若當前環境是大端模式返回0
int is_little_endian2(void)
{
    int a = 1;
    int b = *((char *)&a);          // 共用體的本質即通過指針方式訪問同一內存單元
    return b;
}

int main(int argc, char **argv)
{
    int ret = is_little_endian2();
    if (0 == ret)
    {
        printf("當前環境是大端模式\n");
    }
    else 
    {
        printf("當前環境是小端模式\n");
    }

    return 0;
}

32.enum_define
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:共用體和大小端及枚舉
 * 功能:枚舉的定義和使用詳解。
 */
#include <stdio.h>

/*
 ****************************************************************
 *  enumeration 類型定義
 ****************************************************************
 */
// 定義類型和定義變量分離開
enum week
{
    SUN,            // SUN = 0
    MON,            // MON = 1
    TUE,
    WEN,
    THU,
    FRI,
    SAT,
};
enum week today;

// 定義類型的同時定義變量
enum gender
{
    WOMEN,      // WOMEN = 0 因沒有手動給成員WOMEN和MAN賦值,
    MAN,        // MAN = 1 導致WOMEN和SUN無法區分,MAN和MON無法區分
}rston,lucy; 

// 定義類型的同時定義變量
enum
{
    APPLE = 23,     // APPLE = 23
    BANANA = 24,    // BANANA = 24
}fruit;

// 使用typedef定義枚舉類型別名,並在後面使用別名定義枚舉變量
typedef enum color
{
    BLUE = 25,      // BLUE = 25
    YELLOW = 26,    // YELLOW = 26
}color;

// 使用typedef定義枚舉類型別名,並在後面使用別名定義枚舉變量 
typedef enum 
{
    MOUSE = 27,     // MOUSE = 27
    CAT = 28,       // CAT = 28
}animal;

/*
 ****************************************************************
 *  錯誤類型舉例
 ****************************************************************
 */ 
#if 0
//枚舉類型重名,編譯時報錯:error: conflicting types for ‘DAY’
typedef enum workday
{
    MON,        // MON = 1;
    TUE,
    WEN,
    THU,
    FRI,
}DAY;

typedef enum weekend
{
    SAT,
    SUN,
}DAY;
#endif

#if 0   
// 枚舉成員重名,編譯時報錯:redeclaration of enumerator ‘MON’
typedef enum workday
{
    MON,        
    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;
#endif

#if 0       
// 宏定義可以重複定義(沒有error但是有warning),結果以最後一次定義爲準
#define MACRO1  12
#define MACRO1  24
#endif

int main(int argc, char **argv)
{
    // SUN = 0. WOMEN = 0. APPLE = 23.
    printf("SUN = %d. WOMEN = %d. APPLE = %d.\n", SUN, WOMEN, APPLE);
    // BLUE = 25. MOUSE = 27.
    printf("BLUE = %d. MOUSE = %d.\n", BLUE, MOUSE);

    // 使用typedef重命名的別名定義變量並使用
    color c1 = BLUE;
    printf("c1 = %d.\n", BLUE);     // c1 = 25.
    animal a1 = MOUSE;
    printf("a1 = %d.\n", MOUSE);    // a1 = 27.

    // 測試綜合使用枚舉
    // 在定義多個不同的枚舉類型時,注意應手動給每個枚舉成員賦值,
    // 以便在程序中區分不同枚舉類型中的每個成員。
    today = SUN;                    // SUN = 0.
    lucy = WOMEN;                   // WOMEN = 0.
    fruit = APPLE;                  // APPLE = 23.
    // today = 0. lucy = 0. fruit = 23.
    printf("today = %d. lucy = %d. fruit = %d.\n", today, lucy, fruit);
    switch (fruit)
    {
        case SUN:
            printf("SUN.\n");       
            break;
        /*
        case WOMEN:
            printf("WOMEN.\n");     
            break;
        */
        case APPLE:
            printf("APPLE.\n");     // APPLE.
            break;
        default:
            printf("default.\n");
            break;
    }

    return 0;
}

32.enumeration
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:共用體和大小端及枚舉
 * 功能:演示枚舉的初步使用及枚舉和宏定義的區別和聯繫。
 */

#include <stdio.h>

// 使用宏定義解決函數返回值問題
#define TRUE    1
#define FALSE   0

// 該枚舉用來表示函數返回值,ERROR表示錯誤,RIGHT表示正確
enum return_value
{
    ERROR = 20,     // 枚舉值是全局的,可直接單獨使用
    RIGHT = 25,
};

enum return_value func1(void);

int main(int argc, char **argv)
{
    // 通過枚舉判斷函數執行正確與否
    enum return_value r = func1();
    if (RIGHT == r)
    {
        printf("函數執行正確\n");
    }
    else 
    {
        printf("函數執行錯誤\n");
    }

    // 枚舉中的枚舉值都是常量 ERROR = 20. RIGHT = 25.
    printf("ERROR = %d. RIGHT = %d.\n", ERROR, RIGHT);

    // 通過宏定義判斷函數執行正確與否
    int ret = func2();
    if (TRUE == ret)
    {
        printf("函數執行正確\n");
    }
    else 
    {
        printf("函數執行錯誤\n");
    }

    return 0;
}

// 通過枚舉返回函數執行結果
enum return_value func1(void)
{
    enum return_value r = ERROR;
    return r;
}

// 通過宏定義返回函數執行結果
int func2(void)
{
    return TRUE;
}

發佈了104 篇原創文章 · 獲贊 29 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章