到目前為止,我們處理的大多是數字和字串。 為了模擬真實世界,我們可能會希望某個變數只能取有限個值。 你可能會想要一個專屬型別,它只有幾個名稱各異、彼此不同的值。 舉例來說,在滑板工廠裡,板身材質只能從 maple、bamboo 或 plastic 之中選擇。
你可以用整數來編碼這些值,但這樣一來,就得額外寫程式碼來檢查系統傳進來的材質是不是無效的值。
這些魔術數字的含義很難在原始碼中追溯,而且很容易混淆。
enumerations 可以讓程式碼更具表達力,也能避免不小心發生的比較錯誤。
這種列舉的專門術語是 scoped enumeration。
下面的程式碼片段示範如何撰寫一個 DeckMaterial enumeration。
請注意 enum class 關鍵字,以及定義結尾的 ;:
enum class DeckMaterial {
maple,
bamboo,
plastic
};
現在,看看滑板店裡的定價函式,並留意作用域解析運算子(::),它指明了 enumeration 裡的某個 enumerator:
double deck_price(double base_price, DeckMaterial material) {
if(material == DeckMaterial::plastic) {
return base_price * 0.9;
}
return base_price * 1.3;
}
想像一下,你有第二個 enumeration,用來表示輪子材質:
enum class WheelMaterial {
steel,
clay,
plastic
};
雖然輪子和板身都可以用 plastic 製成,但這兩者不會被搞混。
它們是不同的 型別:DeckMaterial 的 plastic 和 WheelMaterial 的 plastic。
每個 enumeration 的 enumerators 都會位在自己的作用域裡,也就是它自己的 namespace。
這就是它們被稱為 scoped enumerations 的原因。
你可能會想,既然名字裡有 scoped,那應該也有 unscoped 列舉吧,你沒想錯。
Unscoped enumerations 越來越少人使用,因為它們全都共用同一個全域命名空間。
正因為共用,你無法讓兩個 unscoped enumerations 擁有相同的 enumerators,像上面例子裡的 plastic 那樣。
此外,unscoped enumerations 會隱式轉換成整數。
看看下面這個會出現意外結果的例子:
enum CitrusFruits {
lemons, // 0
oranges, //1
};
enum IceCream {
walnut, // 0
apples, // 1
};
bool comparison{apples == oranges};
// => true
// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!
如果你想將 scoped enumerations 轉換成整數,可以使用 static_cast<int>。
和其他語言一樣,C++ 也提供了switch敘述。
switch 敘述是撰寫冗長 if ... else if 敘述時更簡短的寫法。
要建立一個 switch,我們先使用關鍵字 switch,後面接著一個整數。
接著用 case 關鍵字宣告每一個條件。
我們也可以宣告一個 default 情況,當前面所有 case 條件都不符合時,它就會執行。
每個情況都應該以 break(或 return)敘述結尾。
int price{0};
int adults{3};
int kids{2};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
break;
case 2:
price = 70;
break;
default:
price = group_size * 30;
}
switch 結構有一個很重要的地方:程式碼會繼續執行,直到被 break(或 return)敘述擋下為止。
這可能會導致非預期的行為。
int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
case 2:
price = 70;
default:
price = group_size * 30;
}
// price will be 30!
這種繼續執行的特性,主要用在一個敘述帶有多個標籤的情況。 多個 switch 結果可以對應到同一段要執行的程式碼。 這樣一來,舉例來說,在訂房應用程式裡,團體人數為 2 或 3 時所呼叫的函式就可以是同一個:
switch (group_size) {
case 1:
book_room();
break;
case 2:
case 3:
book_apartment(group_size);
break;
default:
book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3
你的朋友 Helma 做了一款小型的線上遊戲,很快就爆紅了。 它叫做 HellMath。 這個小社群吸引了一些酸民,把遊戲和論壇搞得烏煙瘴氣。 Helma 請你著手開發一套新的權限系統,把搗亂的人隔開來。
論壇支援三種不同的動作:
一共有四種帳號類型,每種都有不同的預設權限:
Helma 發現封鎖酸民帳號根本沒用。 她的策略是讓酸民以為自己的時間「投資得很值得」,但他們的貼文只會顯示給其他酸民看。 只要有任何需要排序優先順序的地方,酸民一律排在最後。 當酸民進入遊戲時,可選的玩家人選也只限於其他酸民。
這種做法叫做影子封鎖。
首先,定義一個AccountStatus列舉,代表四種帳號類型:troll、guest、user、mod。
接著,定義一個Action列舉,代表三種權限類型:read、write、remove。
論壇上的每篇貼文都會在中繼資料裡存下發文者的AccountStatus。
請務必確保酸民的貼文只會顯示給其他酸民。
Helma 需要一個display_post函式,它會接收兩個AccountStatus引數,並回傳一個bool。
第一個引數是發文者的狀態,第二個則是瀏覽者的狀態。
using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true
Helma 需要一套方法,來檢查某個動作對某個使用者是否被允許。
請實作一個permission_check函式,第一個引數是Action,第二個是要檢查的AccountStatus。
它應該依照簡介中列出的權限回傳bool。
permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true
為了讓遊戲裡的玩家對自己的行為負責,Hellmath 拒絕訪客使用者進入遊戲。 如上所述,Helma 想讓酸民只能去鬧其他酸民。 其他使用者之間的遊戲連線則沒有限制。
請實作valid_player_combination函式,檢查兩位玩家是否能加入同一場遊戲。
這個函式有兩個AccountStatus型別的參數,並回傳bool。
valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true
隨著遊戲和論壇大幅成長,Helma 現在必須在使用者之間分配運算資源和頻寬。 為了處理緊急狀況,版主獲得最高的優先順序。 訪客排在一般使用者之後,酸民則排在所有人後面。
請實作has_priority函式,它接收兩個AccountStatus引數,並且只在第一個帳號的優先順序嚴格高於第二個時回傳true。
has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true