相信很多人很多時候都會想要把 enum 列舉的數值轉換成 string,方便寫出檔案或顯示到 UI 上,這種轉換的方法在很多高階語言,例如 C#、Java 等都有方法直接支援,但是 C/C++ 就沒這麼方便了。以下介紹幾種在 C++ 實作 enum 轉 string 的方法。
方法 1. 使用 switch case
這個方法應該是最容易想到的方法了,以下是程式的範例:
enum EValue { KZero, KOne, KTwo };
const char* ToString(EValue value)
{
switch (value) {
case KZero:
return "Zero";
case KOne:
return "One";
case KTwo:
return "Two";
}
return "Not Defined";
}
使用的時候把想要轉換的 enum 當作引數呼叫 ToString 函數就可以了。實作簡單、直覺,但有個小缺點,當 enum 列舉的數量一大的時候,這個 switch case 會讓人覺得很討厭,有沒有辦法改善呢?
方法 2. 使用陣列索引進行查表
這個方法是利用 enum 會從 0 開始列舉的特性,直接將 enum 值當作陣列索引值來查表,如以下範例:
enum EValue { KZero, KOne, KTwo };
const char* ToString(EValue value)
{
static char *table[] = { "Zero", "One", "Two" };
return table[value];
}
使用方法一樣是呼叫 ToString 就可以了,但實作變得簡潔多了,是吧?不過這個方法一樣有個缺點,當 enum 列舉數量變多的時候,看程式碼時要比對就有點痛苦了,比如第 17 個列舉值對應的字串是什麼?是不是要慢慢數了呢?簡單的辦法是透過排版和註解來協助程式碼的閱讀,或者是使用 C99 語法,不過以下再介紹另一個辦法來改善這個問題。
方法 3. 使用 X Macro 來實作陣列查表
X macro 是一種 C/C++ 的技術,簡單的說明和範例可以在維基百科查到,這裡便是要運用這個技術來改善方法 2 的小問題,參考以下程式碼:
#define VALUE_TABLE \
X(KZero, "Zero") \
X(KOne, "One") \
X(KTwo, "Two")
#define X(a, b) a,
enum EValue { VALUE_TABLE };
#undef X
const char* ToString(EValue value)
{
#define X(a, b) b,
static char *table[] = { VALUE_TABLE };
#undef X
return table[value];
}
這個方法的關鍵在於 X macro 的定義,雖然看起來有點可怕,仔細推敲一下,會發現這裡定義的 EValue 和 table[] 與方法 2 所定義的完全一模一樣,只是中間的程式碼透過前處理器以 VALUE_TABLE 取代了,但確實改善了 “索引” 和 “值” 之間比對的問題,在 VALUE_TABLE 中可以很明確地看出每一個 enum 列舉值所對應的 string。
到這裡我們已經改善很多問題,但還有一個明顯的瑕疵,就是 enum 不一定要從 0 開始,也不一定要是連續整數,這時候該怎麼辦呢?
方法 4. 使用 X Macro 及 std::map 實作查表方法
如果要實作非連續整數的查表,最簡單的方法就是運用 std::map 了,他是一個紅黑樹的實作,使用索引查詢一個值的時間複雜度是 O(logN)。參考以下範例:
#define VALUE_TABLE \
X(KZero, = 0, "Zero") \
X(KOne, = 1, "One") \
X(KTwo, = 8, "Two")
#define X(a, b, c) a b,
enum EValue { VALUE_TABLE };
#undef X
const char* ToString(EValue value)
{
static std::map<EValue, const char*> table;
static bool isInit = false;
if (isInit)
return table[value];
#define X(a, b, c) table[a] = c;
VALUE_TABLE
#undef X
isInit = true;
return table[value];
}
我故意把 KTwo 的列舉值改成 8 ,以驗證這個函數是可以允許非連續的列舉值。這邊有運用了一個 isInit 的變數來避免表被重複初始化,X Macro 的部分,在 enum 是被展開成:
KOne = 1,
KTwo = 8,
在 table 的部分則是被展開成:
table[KOne] = "One";
table[KTwo] = "Two";
相信大家仔細想一下應該看得懂。這裡是我學到這個方法參考網站,其中裡面有 C99 的實作方法,另外上面的 std::map 也是可以用 switch case 來做。