學習軌道
/
C++
C++
/
練習
/
Troll the Trolls
Troll the Trolls

Troll the Trolls

學習練習

簡介

列舉

到目前為止,我們處理的大多是數字和字串。 為了模擬真實世界,我們可能會希望某個變數只能取有限個值。 你可能會想要一個專屬型別,它只有幾個名稱各異、彼此不同的值。 舉例來說,在滑板工廠裡,板身材質只能從 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 的原因。

Advanced

不具作用域的列舉

你可能會想,既然名字裡有 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>。

Switch 敘述

和其他語言一樣,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 發現封鎖酸民帳號根本沒用。 她的策略是讓酸民以為自己的時間「投資得很值得」,但他們的貼文只會顯示給其他酸民看。 只要有任何需要排序優先順序的地方,酸民一律排在最後。 當酸民進入遊戲時,可選的玩家人選也只限於其他酸民。

這種做法叫做影子封鎖。

1. 設定使用者類型與權限。

首先,定義一個AccountStatus列舉,代表四種帳號類型:troll、guest、user、mod。

接著,定義一個Action列舉,代表三種權限類型:read、write、remove。

2. 只把酸民的貼文給其他酸民看。

論壇上的每篇貼文都會在中繼資料裡存下發文者的AccountStatus。 請務必確保酸民的貼文只會顯示給其他酸民。

Helma 需要一個display_post函式,它會接收兩個AccountStatus引數,並回傳一個bool。 第一個引數是發文者的狀態,第二個則是瀏覽者的狀態。

using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true

3. 檢查不同使用者的權限。

Helma 需要一套方法,來檢查某個動作對某個使用者是否被允許。

請實作一個permission_check函式,第一個引數是Action,第二個是要檢查的AccountStatus。 它應該依照簡介中列出的權限回傳bool。

permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true

4. 開放遊戲存取並配對玩家。

為了讓遊戲裡的玩家對自己的行為負責,Hellmath 拒絕訪客使用者進入遊戲。 如上所述,Helma 想讓酸民只能去鬧其他酸民。 其他使用者之間的遊戲連線則沒有限制。

請實作valid_player_combination函式,檢查兩位玩家是否能加入同一場遊戲。 這個函式有兩個AccountStatus型別的參數,並回傳bool。

valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true

5. 建立優先佇列。

隨著遊戲和論壇大幅成長,Helma 現在必須在使用者之間分配運算資源和頻寬。 為了處理緊急狀況,版主獲得最高的優先順序。 訪客排在一般使用者之後,酸民則排在所有人後面。

請實作has_priority函式,它接收兩個AccountStatus引數,並且只在第一個帳號的優先順序嚴格高於第二個時回傳true。

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
C++ Exercism

準備好開始 Troll the Trolls 了嗎?

註冊 Exercism,透過 19 個概念100 個練習 和真人引導來學習並精通 C++,全部免費。