前言
列舉 (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;
}
如果沒很在意型別安全的議題,就不必要特地寫這樣的程式碼。因為這樣寫程式碼變長,而且效能不會比直接用常數來得好。