13.5 建構子(constructor)
目前的 Fraction 有一個小尷尬:在 main 裡宣告 Fraction f; 之後、read() 之前,p、q 是沒人保證的垃圾值(上冊第二單元的老規則:沒初始化的變數,內容不明)。兩個願望自然浮現:
Fraction f;——希望它自動有個合理的預設值,例如 0(也就是 \frac{0}{1})。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;,看編譯器抱怨什麼;再補一個無參數建構子(或改用預設參數)把它修好。