語法書 / AA 競程語法書 下冊 / 第十三單元 / 建構子(constructor)

13.5 建構子(constructor)

目前的 Fraction 有一個小尷尬:在 main 裡宣告 Fraction f; 之後、read() 之前,pq 是沒人保證的垃圾值(上冊第二單元的老規則:沒初始化的變數,內容不明)。兩個願望自然浮現:

  1. Fraction f;——希望它自動有個合理的預設值,例如 0(也就是 \frac{0}{1})。
  2. Fraction f(3, 5);——宣告的當下就把值帶進去,最好連約分都自動做掉。

實現願望的機制叫建構子(constructor):一種在「變數誕生的瞬間」自動執行的特殊成員函式。語法兩條鐵律:

  • 名稱跟結構體同名
  • 沒有回傳型態——連 void 都不寫。寫了就是編譯錯誤:return type specification for constructor invalid
#include <iostream>
#include <numeric>
#include <cstdlib>
using namespace std;

struct Fraction {
    long long p;    // 分子
    long long q;    // 分母

    Fraction() {                         // 無參數建構子
        p = 0;                           // 沒特別交代時,這個分數就是 0/1
        q = 1;
    }
    Fraction(long long a, long long b) { // 有參數建構子
        p = a;
        q = b;
        reduce();                        // 建構子裡也能呼叫成員函式:宣告完就是最簡分數
    }
    void reduce() {
        long long g = gcd(abs(p), abs(q));
        p /= g;
        q /= g;
        if (q < 0) {
            p = -p;
            q = -q;
        }
    }
    void print() {
        cout << p << '/' << q << '\n';
    }
};

int main() {
    Fraction x;             // 沒有小括號:呼叫無參數建構子
    Fraction y(9, -12);     // 呼叫有參數建構子:自動約分
    x.print();
    y.print();
    return 0;
}

執行結果:

0/1
-3/4

幾個觀察:

  • 一個結構體可以同時擁有多個建構子,只要參數的個數或型態不同;宣告時給了什麼參數,編譯器就挑對應的那個執行。
  • 13.3 的大括號寫法在有建構子之後仍然能用:Fraction y = {9, -12}; 會轉去呼叫對應參數的建構子,效果跟 Fraction y(9, -12); 相同。
  • 建構子裡呼叫 reduce()——「宣告即最簡分數」,之後任何人拿到一個 Fraction,都不用擔心它還沒約分。

動手試試看:幫通訊錄的 student 加一個 student(string n, int s, string ph) 建構子,然後故意宣告 student x;,看編譯器抱怨什麼;再補一個無參數建構子(或改用預設參數)把它修好。