【C++ 筆記】列舉(Enumeration) - part 34

很感謝你點進來這篇文章。

你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!

Introduction

列舉(Enumeration),簡稱 Enum,是 C++ 中的一種使用者自定義資料型態(User-Defined Type)。可用於定義一組命名的整數常數,這些常數通常用來表示狀態、選項或模式。

  • Enum 有助於為整數值賦予有意義的名稱,以提升程式碼的可讀性與可維護性。
  • 主要適用於我們對某項有少量可能數值(如方向、星期幾等)時。

使用 Enum 的情境大致上有以下這三種(可能更多,只是舉例而已):

  • 方向:東、西、南、北
  • 遊戲狀態:選單中、遊戲中、暫停、遊戲結束
  • 星期幾:週一到週日

定義 enum 與建立一個 enum

在 C++98/03 舊標準中,用關鍵字 enum 來定義一個 enum,被稱為「非強型別列舉」或「傳統列舉」。

基本語法:

1
2
3
4
5
enum EnumName {
Value1,
Value2,
Value3
};

Value1, Value2, Value3 等等是常數的名稱,都是唯一的識別字(Identifiers)。預設列舉中第一個名稱會被賦予整數值 0,後續名稱則以 1 遞增。

Value1 = 0, Value2 = 1, Value3 = 2 以此類推。

也可以直接手動賦值:

1
2
3
4
5
enum EnumName {
Value1 = v1,
Value2 = v2,
Value3 = v3
};

v1, v2, v3 的值應為整數。

也不用定義所有常數名稱的值,如果目前常數名稱的值是 x,則後續的值會持續 + 1。

若要建立一個 enum,則要在變數前加上先前定義的 enum 名稱,例如:EnumName x;

也可賦予 enum 內的值作為初始值:EnumName x = Value1;

範例 1:星期幾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

using namespace std;

enum Day{
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
};

int main(){
Day today = WEDNESDAY;

cout << "Today's index is " << today << '\n';
}

範例 2:東西南北

example from : https://www.geeksforgeeks.org/cpp/enumeration-in-cpp/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

// 定義一個 enum 列舉資料型態
// Defining enum
enum direction
{
EAST,
NORTH,
WEST,
SOUTH
};

int main()
{
// 建立 enum 變數
// Creating enum variable
direction dir = NORTH;

cout << dir;
return 0;
}

強型別列舉(Enum Classes)

C++ 11 引入了這樣的特性,主要彌補先前傳統 enum 的兩個缺點:

  1. 命名空間汙染(Scope Pollution):傳統列舉的成員名稱是全域的,因此不能在兩個不同的列舉中使用相同的名稱(如 enum Color { RED };enum Alert { RED }; 會衝突)。
  2. 隱式轉換(Implicit Conversion):傳統列舉會自動轉換成 int,可能導致比較錯誤(如拿「顏色」去跟「星期」做比較,編譯器不會報錯)。

C++ 11 引入 Enum Classes 提供安全性更高的型態,也解決上述這些問題。

接下來看看 Enum Classes 的程式怎麼寫吧!

範例 3:以 Enum Classes 撰寫的星期幾程式

example from : https://www.geeksforgeeks.org/cpp/enumeration-in-cpp/

在以下的範例中,只要在 enum 關鍵字後面再加上一個 class 即為 Enum Classes 的撰寫方式。

但需要注意有作用域(Scope)的限制,所以要寫成像這樣的形式去存取 enum class 的值:EnumName::Value

在印出 enum 變數時不會自動轉換型態,因此要透過 static_cast 去做顯式轉換,才能做下一步的印出動作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

// 定義 enum class
// Define the enum class
enum class Day
{
Sunday = 1,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};

int main()
{
// 初始化
// initializing
Day today = Day::Thursday;

// 印出 enum
// Print the enum
cout << static_cast<int>(today);

return 0;
}

如果直接印出,寫成像這樣子:cout << today; 則會發生以下錯誤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ERROR!
/tmp/h7o8IuG5pb/main.cpp: In function 'int main()':
/tmp/h7o8IuG5pb/main.cpp:25:10: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'Day')
25 | cout << today;
| ~~~~ ^~ ~~~~~
| | |
| | Day
| std::ostream {aka std::basic_ostream<char>}
In file included from /usr/local/include/c++/14.2.0/iostream:41,
from /tmp/h7o8IuG5pb/main.cpp:1:
/usr/local/include/c++/14.2.0/ostream:116:7: note: candidate: 'std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(__ostream_type& (*)(__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; __ostream_type = std::basic_ostream<char>]'
116 | operator<<(__ostream_type& (*__pf)(__ostream_type&))
| ^~~~~~~~
.
.
.
.

然後初始化時也不小心寫成傳統形式 Day today = Thursday;,沒有考慮到作用域規則的話,也會產生錯誤:

1
2
3
4
5
6
7
8
9
ERROR!
/tmp/286k2LGl0z/main.cpp: In function 'int main()':
/tmp/286k2LGl0z/main.cpp:21:17: error: 'Thursday' was not declared in this scope; did you mean 'Day::Thursday'?
21 | Day today = Thursday;
| ^~~~~~~~
| Day::Thursday
/tmp/286k2LGl0z/main.cpp:12:5: note: 'Day::Thursday' declared here
12 | Thursday,
| ^~~~~~~~

範例 4:利用運算子重載直接印出

如果覺得顯式轉換太麻煩的話,沒關係!!還有更麻煩的寫法,讓你直接回到傳統的 enum,一勞永逸的直接把變數 cout 印出來。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

using namespace std;

enum class Color {
RED,
GREEN,
BLUE
};

ostream& operator<<(std::ostream& os, Color c) {
switch (c) {
case Color::RED: os << "RED"; break;
case Color::GREEN: os << "GREEN"; break;
case Color::BLUE: os << "BLUE"; break;
default: os << "UNKNOWN"; break;
}
return os;
}

int main() {
Color myColor = Color::BLUE;

cout << "My color is: " << myColor;

return 0;
}

範例 5:遊戲角色狀態

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <string>

using namespace std;

enum class CharacterState {
IDLE, // 閒置
RUNNING, // 奔跑
ATTACKING, // 攻擊
DEAD // 死亡
};

void handleState(CharacterState state) {
switch (state) {
case CharacterState::IDLE:
cout << "[角色] 正在待機,觀察四周..." << endl;
break;

case CharacterState::RUNNING:
cout << "[角色] 快速奔跑中!消耗耐力。" << endl;
break;

case CharacterState::ATTACKING:
cout << "[角色] 發動攻擊!造成傷害。" << endl;
break;

case CharacterState::DEAD:
cout << "[角色] 已死亡。請按 R 重新開始。" << endl;
break;

default:
cout << "[系統] 未知狀態錯誤!" << endl;
break;
}
}

int main() {
CharacterState myHero = CharacterState::IDLE;
handleState(myHero);

cout << "--- 玩家按下前進鍵 ---" << endl;
myHero = CharacterState::RUNNING;
handleState(myHero);

cout << "--- 玩家遇到敵人 ---" << endl;
myHero = CharacterState::ATTACKING;
handleState(myHero);

cout << "--- 玩家掛機中 ---" << endl;
myHero = CharacterState::IDLE;
handleState(myHero);

cout << "--- 玩家被打死了 ---" << endl;
myHero = CharacterState::DEAD;
handleState(myHero);

return 0;
}

參考資料

Enumeration in C++ - GeeksforGeeks

C++ 枚举类型详解 | 菜鸟教程

C/C++ enum 用法與範例 | ShengYu Talk

列舉