純虛擬函式(一)


在〈虛擬函式〉中,將 to_string 設成 virtual 了,然而你可能會發現,Role 的子類別都有 fight 方法,為什麼不將它們提昇至父類別並設為 virtual?可以是可以,不過提昇之後,在 Role 中的方法該寫什麼呢?空的方法本體?如果是這樣的話,不如將它設為純虛擬函式(pure virtual function),也就是沒有任何實作的方法:

class Role {
    ...略

public:
    Role(string name, int level, int blood)
     : name(name), level(level), blood(blood) {}

    virtual void fight() = 0;

    …略
};

這麼一來,就可以如下寫個 doFight 了:

...略

void doFight(Role &role) {
    role.fight();
}

int main() { 
    SwordsMan swordsMan("Justin", 1, 1000);
    Magician magician("Magician", 1, 800);

    fight(swordsMan);
    fight(magician);

    return 0;
}

被設為 0 的 virtual 函式,沒有任何實作,是個抽象方法,而擁有抽象方法的類別,是個抽象類別(abstract class),不能用來實例化:

Role role("Justin", 1, 1000); // error: cannot declare variable 'role' to be of abstract type 'Role'

因此,也不能如下指定:

Role role = swordsMan;

也就是說,因為抽象類別不是個完整的類別定義,只用來宣告參考或指標,而繼承抽象類別的子類,一定要重新定義抽象方法,否則該子類也會是抽象類別,無法用來實例化。

來看看抽象方法與抽象類別的另一個應用,如果要你開發一個猜數字遊戲,會隨機產生 0 到 9 的數字,使用者輸入的數字與隨機產生的數字相比,如果相同就顯示「猜中了」,如果不同就繼續讓使用者輸入數字,直到猜中為止。

這程式有什麼難的?相信現在的你也可以實作出來:

#include <iostream>
#include <string> 
#include <chrono>
#include <random>
#include <functional>
using namespace std;

function<int()> rand(int from, int to) {
    unsigned seed = chrono::system_clock::now()
                        .time_since_epoch().count(); // 隨機數種子
    default_random_engine uniform(seed);             // 產生均勻分佈隨機數的引擎
    uniform_int_distribution<int> dist(from, to);    // 範圍為 0 到 9 均勻分佈
    return bind(dist, uniform);                      // 綁定引擎與範圍
}

int main() { 
    function<int()> randZeroToNine = rand(0, 9);

    int number = randZeroToNine();
    int guess = 0;
    do {
        cout << "輸入數字:";
        cin >> guess;
    } while(guess != number);
    cout << "猜中了!" << endl;

    return 0;
}

不過,需求中並沒有說要在文字模式下執行這個遊戲,也就是說取得使用者輸入、顯示結果的環境未定,但你負責的這部份還是要先實作,怎麼辦呢?可以先設計個抽象類別:

class GuessGame {
public:
    void go() {
        function<int()> randZeroToNine = rand(0, 9);

        int number = randZeroToNine();
        int guess = 0;
        do {
            this->print("輸入數字:");
            guess = this->nextInt();
        } while(guess != number);
        println("猜中了!");
    }

    void println(string text) {
        this->print(text + "\n");
    }

    virtual void print(string text) = 0;
    virtual int nextInt() = 0;

    virtual ~GuessGame() = default;
};

對於文字模式中的遊戲,可以繼承 GuessGame

...略

class ConsoleGame : public GuessGame {
public:
    void print(string text) {
        cout << text;
    }

    int nextInt() {
        int guess = 0;
        cin >> guess;
        return guess;
    }
};

int main() { 
    GuessGame &&game = ConsoleGame();
    game.go();

    return 0;
}

一個執行的結果如下:

輸入數字:3
輸入數字:5
輸入數字:6
輸入數字:7
輸入數字:8
猜中了!