列舉二:有範圍的列舉 Enum Class

列舉有兩種,一種是無範圍的 unscoped enum,第二種則是有範圍的 scoped enum。

上一章 列舉一:無範圍的列舉 Enum 我們講解了無範圍的列舉,並提到它的功能、局限性、以及缺點。

今天這章就是來介紹什麼是有範圍的 scoped enum,並且了解它如何處理了我們在無範圍的列舉所遇到的問題。

有範圍 scoped enum

首先,讓我們先來理解 scoped enum 和 unscoped enum 的差別。

其實,兩者的功能幾乎是一樣的。唯獨有兩個最大的差別:

  1. 型態安全
  2. 獨立名稱空間

第一點基本上就解決了前一章最後我們遇到型態轉換的問題。

第二點則解決了編譯器無法識別擁有同一個名稱的符號的問題。

讓我們拿上一章最後一個的程式碼,並將關鍵字 enum 更改為 enum class

enum class Animal
{
    bird,
    cat
};

enum class Color
{
    red,
    green
};

int main()
{
    Animal animal { Animal::bird }; // 需要使用::來讀取列舉值
    Color color { Color::red };     // 需要使用::來讀取列舉值

    if (animal == color) // 編譯錯誤!因為編譯器知道兩個是不同型別,所以無法比較
        std::cout << "color and animal are equal\n";
    else
        std::cout << "color and animal are not equal\n";

    return 0;
}

運行如上程式碼後,我們會發現,編譯器這次就知道要報錯了!

原因就是因為 enum class 是型態安全的,編譯器並不會隨意將列舉值轉為整數型態,而是使用原本的型態。

在這個例子中,原本的型態分別為 Animal 和 Color,編譯器不知道該如何比較兩個不同型態的東西,因此只能報錯。

另外值得注意的一點,當我們使用 enum class 時,我們必須使用 :: 運算子加上其名稱空間才能讀取列舉值。這和 名稱空間 namespace 的使用方法相同。

這是因為上面提到的第二點差異:獨立的命名空間。現在因為每一個列舉都是獨立的名稱空間,所以我們必須藉由特別標示其所在的名稱空間才能使用。

常見應用

講了這麼多,所以列舉到底有什麼用呢?

列舉是程式中非常有用的工具,因為它可以幫助我們提升程式碼的可讀性使其更容易理解。當我們需要為一小組相關的事物命名時,例如星期幾或指南針方向,我們就可以使用列舉!

因為我們不需要記住每個事物的各種不同名稱,而是只需使用一個可以代表它們所有的列舉就可以了。

讓我們來看看下面的例子:

enum class Directions
{
    up,
    down,
    right,
    left
};

enum class FamilyMembers
{
    dad,
    mom,
    sister,
    brother
};

enum class DaysOfWeek
{
    monday,
    tuesday,
    wednesday,
    thursday,
    friday,
    saturday,
    sunday
};

或著另一個實用的例子,我們可以建立一個可以提供讀檔是否成功的列舉,大家可以體會看看這樣程式碼是不是更易讀了一些!比如說:

enum class Result
{
    Success,
    ErrorOpen,
    ErrorRead
};

int main()
{
    FileReadResult result {Result::Success};
    if (!openFile())
        result = Result::ErrorOpen;
    if (!readFile())
        result = Result::ErrorRead;

    return 0;
}

總結

好啦講到這裡就差不多講完列舉了!花了兩個篇章講了什麼是列舉。

其實重點很簡單,就是 enum class 相較於一般 enum 在於他有更高的安全性因為編譯器並不會隨意將列舉值轉換為整數,以及每個列舉都存在於獨立的命名空間!

想當初我在面試工作的時候被問到這時雖然知道 enum class 比較安全,但卻不知道為什麼。

希望大家看完這兩篇後能夠清楚了解 enum 與 enum class!