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

C 語言列舉
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)
{
    /* Wrongly correct. */
    direction_t d = 6;

    return 0;
}

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

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

/* `char` is a small-range number. */
typedef char DIRECTION;

#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;
}

如果沒很在意型別安全的議題,就不必要特地寫這樣的程式碼。因為這樣寫程式碼變長,而且效能不會比直接用常數來得好。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。