跟濤哥一起學嵌入式 30:C語言枚舉類型深入剖析

枚舉(enum)是C語言的一種特殊類型。當我們在編程中遇到定義一些固定長度或範圍的數值時,可以考慮使用枚舉類型。使用枚舉可以讓我們的程序可讀性更強、看起來更加直觀。舉個例子,如果我們在編程中需要使用數字0~6分別表示星期日~星期六,程序的可讀性就不高,我們需要翻手冊或者看程序註釋才能知道每個數字具體代表什麼意思。如果我們使用枚舉呢,基本上不需要看註釋或手冊就可知曉其大意。

enum week    // enum 枚舉類型{枚舉值列表};
{
    SUN,MON,TUE,WED,THU,FRI,SAT,
};
enum week today = SUN; //使用枚舉類型定義一個變量

使用enum定義的枚舉值列表中,默認值是從0開始,然後依次遞增:SUN=0,MON=1...。當然我們也可以顯式指定枚舉值:

enum week
{
    SUN = 1,MON,TUE,WED,THU = 7,FRI,SAT,
};
//SUN=1,那麼接下來MON=2,TUE=3,WED=4
//THU=7,那麼接下來FRI=8,SAT=9

 

1. enum經常使用的三種方法

使用枚舉類型定義變量,使用方法跟結構體、共用體類似,經常使用的三種方法如下:

enum week //定義枚舉類型的同時,定義枚舉變量
{
    SUN,MON,TUE,WED,THU,FRI,SAT,
}today, tomorrow;
​
enum //可以省去枚舉類型名,直接定義變量
{
    SUN,MON,TUE,WED,THU,FRI,SAT,
}today, tomorrow;
​
enum week //先定義枚舉類型,再定義枚舉變量
{
    SUN,MON,TUE,WED,THU,FRI,SAT,
};
enum week today, tomorrow;

 

2. 枚舉的本質

在C語言中,枚舉是一種類型,屬於整型的範疇,使用enum定義的枚舉值列表,其實就是從0開始的一系列整數序列。整型除了short、int、long、long long外,還包括char、_Bool(C99標準新增)和enum。因此,枚舉的使用其實和整數值其實沒啥區別:我們使用枚舉類型定義的變量,同樣可以作爲函數參數、函數返回值、用來定義數組、甚至和結構體混用等。

enum week get_week_time (void);
int set_week_time (enum week time_set);
int change_week_time (enum week *p);
enum week a[10];
struct student
{
    char name[20];
    int age;
    enum week birthday;
};

枚舉有點類似於typedef,給一個數值添加一個別名,讓我們的程序更加直觀、可讀性更高。枚舉類型在本質上就是有命名的整數,屬於整型的一種,在代碼中是可以和整型互換的。

enum week t = SUN;
int  t2 = SUN;
enum week t3 = t2;
enum week t4 = 100;

在上面的代碼中,枚舉變量和整型變量相互賦值,都是可以正常編譯和運行的。我們在代碼中使用枚舉類型,在最終編譯生成的可執行文件中都會被整型數值代替。

enum week 
{
    SUN = 5,MON,TUE,WED,THU,FRI,SAT,
};
​
int main (void)
{
    enum week today = THU;
    return 0;
}

在上面的示例代碼中,我們定義了一個枚舉類型week,然後定義了一個枚舉變量today,並賦值爲THU。反彙編上面的代碼,我們可以看到彙編代碼:

00010400 <main>:
   10400:   e52db004    push    {fp}        
   10404:   e28db000    add fp, sp, #0
   10408:   e24dd00c    sub sp, sp, #12
   1040c:   e3a03009    mov r3, #9  
   10410:   e50b3008    str r3, [fp, #-8]
   10414:   e3a03000    mov r3, #0
   10418:   e1a00003    mov r0, r3
   1041c:   e24bd000    sub sp, fp, #0
   10420:   e49db004    pop {fp}        ;
   10424:   e12fff1e    bx  lr

在C程序中定義的枚舉變量today,在彙編代碼的第1040c處,我們可以看到:枚舉值THU被替換爲整型數值9。使用枚舉的唯一好處就是增加代碼的可讀性,它的作用跟宏定義的作用有異曲同工之妙。

 

3. 枚舉和宏

枚舉與預處理指令#define的作用差不多,都是爲了增加代碼的可讀性。但在實際使用中,兩者還是有些差別的:宏在預處理階段,通過簡單的字符串替換就全部被替換掉了,編譯器根本不知道有宏這麼一個玩意;而枚舉類型則在編譯階段全部替換爲整型。

跟宏相比,枚舉的優勢是:枚舉可以自動賦值,而宏則需要一個一個單獨定義。因此,在自定義一些有規則的類型值的時候,使用枚舉會更加方便。枚舉可以自定義的變量值來代替數字值,使我們的程序代碼有更高的可讀性。

 

4. Linux內核中的枚舉類型

在Linux內核代碼中,充斥着大量的枚舉類型數據,有些枚舉類型的定義看起來很奇怪,比如:

enum 
{
    MM_FILEPAGES,
    MM_ANONPAGES,
    MM_SWAPENTS,
    NR_MM_COUNTERS
};
enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

Linux內核中使用enum定義的枚舉類型大部分是沒有枚舉名的,而且通常會在一串枚舉值之後帶上一個NR*的元素用來表示枚舉值的數量。當我們不需要使用枚舉類型去定義一個枚舉變量時,枚舉並不需要一個名字,這些無名的枚舉類型其實就相當於宏定義。而最後一個元素NR或MAX,一般可以用來記載枚舉列表中元素的個數,或者作爲循環判斷的邊界值。

 

5. 使用枚舉需要注意的地方

什麼是類型?類型是一定範圍的數值及方法的集合。枚舉作爲整型類型的一種,在編程使用過程中,也有一些注意的地方,比如作用域。使用枚舉定義的常量也遵循數據作用域規:包括文件作用域、代碼塊作用域等,在同一個作用域能不能出現重名的枚舉常量名。

enum week1
{
    SUN,MON,TUE,WED,THU,FRI,SAT,
};
enum week2
{
    SAT,UNKNOW,
};
int main (void)
{   
    return 0;
}

在上面的代碼中,我們定義了兩個枚舉類型,其中枚舉常量SAT重名,編譯時就會發生如下錯誤:

error: redeclaration of enumerator `SAT'
error: previous definition of 'SAT' was here

出現錯誤的原因是,我們定義的不同枚舉類型中的兩個枚舉常量名同在一個作用域:文件作用域,我們稍微改一下代碼就可以避免衝突:

#include <stdio.h>
enum week1
{
    SUN,MON,TUE,WED,THU,FRI,SAT,
};
int main (void)
{
    printf("%d\n", SAT);
    enum week2
    {
        SAT,UNKNOW,
    };
    printf("%d\n", SAT);    
    return 0;
}

我們將枚舉類型week2的定義放到了main函數內,week2的作用域就從文件作用域變爲代碼塊作用域。這個時候,兩個枚舉類型中的同名枚舉常量就不會再發生衝突,程序的運行結果爲:

6
0

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章