[C 語言] 程式設計教學:如何使用列舉 (Enumeration)

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    列舉 (enum 或 enumeration) 是另一種複合型別,主要是用在宣告僅有少數值的型別,像是一星期內的日期 (day of week) 或是一年內的月份等。透過列舉,我們可以在程式中定義數個獨一無二的符號 (symbol),且該符號享有型別檢查的優點。

    宣告列舉

    使用 enum 保留字可以宣告列舉,如下例:

    enum direction {
        North,
        South,
        East,
        West
    };
    
    int main(void)
    {
        enum direction dest = East;
        
        return 0;
    }

    列舉同樣可用 typedef 簡化型別名稱,如下例:

    // Foreward declaration.
    typedef enum direction Direction;
    
    enum direction {
        North,
        South,
        East,
        West
    };
    
    int main(void)
    {
        Direction dest = East;
        
        return 0;
    }

    由於 C 語言的列舉本身沒有前綴,可以自行加入前綴,如下例:

    typedef enum direction Direction;
    
    // Enum with prefix.
    enum direction {
        Direction_North,
        Direction_South,
        Direction_East,
        Direction_West
    };
    
    int main(void)
    {
        Direction dest = Direction_East;
        
        return 0;
    }

    雖然前綴不是強制規定,有些程式設計者偏好此種風格,以減少命名空間衝突。

    有些程式設計者會將列舉用全大寫來表示:

    enum direction {
        DIRECTION_NORTH,
        DIRECTION_SOUTH,
        DIRECTION_EAST,
        DIRECTION_WEST
    };

    這種觀點將列舉視為一種常數 (constant),這些不是硬性規定,而屬個人風格,讀者可自由選用。

    讀取列舉的數字

    一般來說,我們使用列舉時,將其視為一種符號,不會在意其內部的數值;但必要時也可指定列舉的值,如下例:

    #include <assert.h>
    
    enum mode {
        MODE_READ = 4,
        MODE_WRITE = 2,
        MODE_EXEC = 1
    };
    
    int main(void)
    {
        assert(MODE_READ ^ MODE_WRITE == 6);
        
        return 0;
    }

    在這個例子中,我們刻意安排列舉的值,透過二進位運算,就可以把列舉的項目視為旗標 (flag) 使用。

    列舉不具有型別安全

    以下理當要引發錯誤的程式碼,其實是「正確」的:

    typedef enum direction_t direction_t;
    
    enum direction_t {
        DIRECTION_NORTH,
        DIRECTION_SOUTH,
        DIRECTION_EAST,
        DIRECTION_WEST,
    };
    
    int main(void)
    {
        direction_t d = 6;  /* Wrongly correct. */
    
        return 0;
    }
    

    這是因為 C 語言的列舉在內部是以 int 儲存,而且整數值會自動轉型成相對應的列舉型別。由於這項奇異的特性是 C 標準的一部分,為了相容性考量,基本上是不會修改的。

    有一派的程式人直接放棄列舉,改用巨集宣告:

    #define DIRECTION_NORTH 0
    #define DIRECTION_SOUTH 1
    #define DIRECTION_EAST  2
    #define DIRECTION_WEST  3
    

    使用巨集未嘗不可。但不論使用列舉或是巨集,我們的目的都是在創造符號,而且兩者都沒有型別安全。

    另一個替代的方式是改用結構體包住列舉。將本節的列舉改寫如下:

    typedef struct direction_t direction_t;
    
    struct direction_t {
        enum {
            _DIRECTION_NORTH,
            _DIRECTION_SOUTH,
            _DIRECTION_EAST,
            _DIRECTION_WEST,
        } value;
    };
    
    const direction_t DIRECTION_NORTH = { _DIRECTION_NORTH };
    const direction_t DIRECTION_SOUTH = { _DIRECTION_SOUTH };
    const direction_t DIRECTION_EAST = { _DIRECTION_EAST };
    const direction_t DIRECTION_WEST = { _DIRECTION_WEST };
    

    由於整數不會轉型成結構體,這樣做的確可以達到型別安全的要求。但是結構體間無法直接比較,程式人得自己寫工具函式來比較。這樣的工具函式不會太難寫:

    bool is_direction_equal(direction_t a, direction_t b)
    {
        return a.value == b.value;
    }
    

    如果沒很在意型別安全的議題,就不需要特地寫這樣的程式碼。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk