BaseDerived Derived
PREREQ P9

物件導向(進階):繼承、多型與運算子多載

先備知識 · 語法與互動
繼承|virtual|operator==|operator<|迭代器|組合 vs 繼承|template
向下捲動開始互動
📌 本頁使用方式(先備知識 · 語法與互動)

先讀語法與例子,再操作互動圖解並完成四選一自測。忘記寫法時回看速查表,最後用中英詞彙卡複習。

CONTENTS · 內容目錄
PROLOGUE · 開場

從一個類別,到一整族類別

P8 教你把「資料 + 操作」包成一個 class。這一頁處理接下來必然發生的三件事:

問題C++ 的答案本頁哪一節
兩個類別有一半的程式碼一樣,要複製貼上嗎?繼承、virtual、多型P01–P04
怎麼讓 sortsetpriority_queue 吃我自己寫的型別?運算子多載P05–P07
怎麼讓一份程式碼同時服務 intstringtemplateP10
💡 一句話抓重點

本頁先介紹繼承如何建立共用介面,再說明虛擬函式如何依物件的實際型別選擇版本。這是執行期多型;C++ 也能用稍後介紹的模板表達編譯期多型。

📌 本頁定位
PART 01 · 繼承

is-a:以基底介面建立衍生類別

下例的 Dog(string n) : Animal(n) 使用 P8 的建構式初始化列表;這次冒號後指定的是基底 Animal 的建構式。它與 class Dog : public Animal 宣告繼承關係的冒號不同。下一節再整理執行順序。

「有序串列是一種串列」—— 這種 is-a 關係就用繼承。子類別自動擁有父類別所有的成員,只需要寫「不一樣的那部分」。

繼承與覆寫
#include <iostream> #include <string> using namespace std; class Animal { // 父類別(基底類別) protected: string name; // protected:自己與子類別看得到,外人看不到 public: Animal(string n) { name = n; } void eat() { cout << name << " 在吃東西" << endl; } void speak() { cout << name << " 發出聲音" << endl; } }; class Dog : public Animal { // 子類別,繼承 Animal 的一切 public: Dog(string n) : Animal(n) { } // 成員初始化列表:先蓋好父類別的部分 void speak() { cout << name << " 汪汪叫" << endl; } // 覆寫 }; int main() { Dog d("小黑"); d.eat(); // 繼承來的,不用重寫 d.speak(); // 用自己覆寫的版本 return 0; }
預期輸出
小黑 在吃東西
小黑 汪汪叫

Dog 沒有自己寫 eat(),直接用繼承來的;但它寫了自己的 speak(),蓋掉父類別的版本,這叫覆寫。另外注意 protected:它讓子類別碰得到 name,但外人還是碰不到。

寫法意思
class Dog : public AnimalDog 繼承 Animal,父類別的 public 仍是 public
protected:自己與子類別看得到,外人看不到
Dog(string n) : Animal(n)成員初始化列表:先把父類別的部分蓋好
void speak() override明說「我是在覆寫」,名字打錯編譯器會抓出來
PART 02 · 建構順序

先蓋父類別,再蓋子類別

建立子類別物件時,C++ 一定先把父類別的部分建好,再執行子類別的建構式主體。父類別如果沒有無參數建構式,你就必須用成員初始化列表明說要呼叫哪一個。

#include <string> class Package { public: std::string label; Package(std::string text) : label(text) {} }; class TrackedPackage : public Package { int checkpoints; public: TrackedPackage(std::string text) : Package(text), checkpoints(0) {} };
⚠️ 最常見的錯誤

初始化列表沒有指定基底時,會嘗試呼叫基底的預設建構式;若沒有可用版本,程式無法編譯。若預設建構式存在,欄位是否得到適當初值取決於它的實作及成員初值。本例明確傳入 Package(label),說明基底需要哪些建立資料。

PART 03 · virtual

virtual:依物件的動態型別選擇成員函式

Python 的多型是免費的:方法名字對得上就能互換。C++ 不是。

沒有 virtual:用基底指標呼叫,跑的是父類別的版本
#include <iostream> using namespace std; class Base { public: void who() { cout << "我是 Base" << endl; } // 沒有 virtual }; class Derived : public Base { public: void who() { cout << "我是 Derived" << endl; } }; int main() { Derived d; Base *p = &d; // 用父類別的指標指向子類別物件 p->who(); // 沒有 virtual:編譯期就決定,跑父類別的版本 d.who(); // 直接用物件:跑子類別的版本 return 0; }
預期輸出
我是 Base
我是 Derived

同一個物件 d,用 Base* 指標呼叫跑父類別、直接用物件呼叫跑子類別。因為沒有 virtual 時,編譯器在編譯期就照指標的型別決定要呼叫誰。

加上 virtual:執行期才決定,跑的是子類別的版本
#include <iostream> using namespace std; class Base { public: virtual void who() { cout << "我是 Base" << endl; } // 加上 virtual }; class Derived : public Base { public: void who() override { cout << "我是 Derived" << endl; } }; int main() { Derived d; Base *p = &d; p->who(); // 有 virtual:執行期查表,跑子類別的版本 return 0; }
預期輸出
我是 Derived

加了 virtual,C++ 會在執行期查一張表(虛擬函式表)找出物件真正的型別。這就是動態繫結,也是多型能運作的機制。子類別那邊建議加 override:名字或簽章打錯時編譯器會直接抓出來。

💡 兩個條件缺一不可

① 方法要標 virtual;② 要透過基底類別的指標或參考呼叫。
少了第二點,多型也不會發生 —— 而且如果你是用值傳遞子類別物件給一個吃父類別的函式,子類別多出來的部分會被直接切掉(叫做 object slicing)。所以多型場合一律傳指標或參考。

PART 04 · 純虛擬

= 0:宣告純虛擬函式

virtual void deliver() = 0; 宣告純虛擬操作。~Notification() 是解構式,負責物件生命週期結束時的收尾;= default 要求編譯器提供預設實作。基底的虛擬解構式允許透過基底指標刪除衍生物件時正確執行解構;本例沒有額外資源需要手動清理。

例如通知介面規定「可以傳送」,不同通知類型卻有不同傳送方式。基底可用純虛擬函式表達這個操作;尚未提供實作的衍生類別仍可保持抽象,只有要建立具體物件的型別才必須完成相應介面。

#include <iostream> class Notification { public: virtual void deliver() = 0; // 純虛擬函式;具體型別需提供實作 virtual ~Notification() = default; }; class ConsoleNotice : public Notification { public: void deliver() override { std::cout << "新通知\n"; } };
✅ 有純虛擬函式的類別叫抽象類別

抽象類別不能直接建立物件,但可以作為基底介面。上例 ConsoleNotice 完成 deliver,才成為可以實例化的具體類別;這是獨立通知範例,不需其他標頭。

PART 05 · template

一份模板,供符合需求的型別使用

模板讓型別成為編譯期參數。template <typename T> 宣告一個型別參數 T,Box<int> 就是以 int 替換 T 的具體型別。先讀下面完整的 Box,再用同一套尖括號讀後續容器與比較器的型別。

同一個 Box,兩種型別各產生一份
#include <iostream> #include <string> using namespace std; template <typename T> // T 是型別參數 class Box { private: T item; public: Box(T v) { item = v; } T get() const { return item; } }; int main() { Box<int> a(42); Box<string> b("hello"); cout << a.get() << " / " << b.get() << endl; // 編譯期各自產生一份程式碼,是兩個不同的型別 cout << "sizeof(Box<int>) = " << sizeof(Box<int>) << endl; cout << "sizeof(Box<string>) = " << sizeof(Box<string>) << endl; return 0; }
預期輸出
42 / hello
sizeof(Box<int>)    = [依平台而異]
sizeof(Box<string>) = [依平台而異]

Box<int> 與 Box<string> 是不同的模板實例化型別。它們的物件大小依成員與實作而定,不保證一定不同;sizeof 只用來觀察目前平台的物件大小,不能拿大小差異當作判斷型別是否相同的標準。

💡 為什麼模板實作通常放在 .hpp

編譯器在實例化 Container<int> 的位置通常必須看得到完整模板定義。因此模板類別常把宣告與定義一起放在標頭;也可使用明確實例化等進階方式分離,但那會限制預先提供的型別組合。這接續 P8 的多檔組織

⚠️ 某些 notebook 包裝器會改變程式碼所在範圍

沒有 main( 的 cell 會被 kernel 自動包進一個 main(),於是你的 template 類別變成區域類別 —— 而區域類別不能是 template,直接編譯失敗。詳見 00B 的常見問題

PART 06 · 相等

operator==:定義「什麼叫一樣」

若類別沒有提供可用的相等運算,兩個物件便不能直接以 == 比較。要讓「內容一樣就算相等」,必須定義符合型別語意的比較;以下採用 C++17 可用的明確多載。

class GridPosition { public: GridPosition(int r, int c) : row(r), column(c) {} bool operator==(const GridPosition& other) const { return row == other.row && column == other.column; } private: int row, column; };
💡 相等規則必須符合型別的意義

GridPosition 而言,兩個物件只有在 rowcolumn 都相同時才位於同一格,因此逐欄比較正好符合型別語意。「什麼叫一樣」是類別介面的一部分,應由設計者明確定義。

PART 07 · 排序的關鍵

operator<:讓排序與預設有序容器比較物件

沒有 operator< 的編譯結果
#include <iostream> #include <vector> #include <algorithm> #include <string> using namespace std; class Task { public: string name; int priority; Task(string n, int p) { name = n; priority = p; } }; int main() { vector<Task> v = { Task("備份", 5), Task("修 bug", 1) }; sort(v.begin(), v.end()); // 沒有 operator<,編譯器不知道怎麼比 return 0; }
預期輸出
[編譯失敗]
predefined_ops.h:45:23: error: no match for ‘operator<’ (operand types are ‘Task’ and ‘Task’)
predefined_ops.h:98:22: error: no match for ‘operator<’ (operand types are ‘Task’ and ‘Task’)

sort、min_element、priority_queue 與 set 都需要合適的比較規則。自訂型別若沒有可用的比較,使用預設排序就可能在編譯時失敗;下面保留失敗與成功版本作對照。

補上一個成員函式,四個地方同時能用
#include <iostream> #include <vector> #include <algorithm> #include <queue> #include <string> using namespace std; class Task { public: string name; int priority; Task(string n, int p) { name = n; priority = p; } bool operator<(const Task& other) const { // 定義「什麼叫比較小」 return priority < other.priority; } }; int main() { vector<Task> v = { Task("備份", 5), Task("修 bug", 1), Task("回信", 3) }; sort(v.begin(), v.end()); cout << "sort -> "; for (const Task& t : v) cout << t.name << "(" << t.priority << ") "; cout << endl; cout << "min -> " << min_element(v.begin(), v.end())->name << endl; priority_queue<Task> pq(v.begin(), v.end()); // 同一份物件直接丟進優先佇列 cout << "pq 頂 -> " << pq.top().name << "(預設是最大優先)" << endl; return 0; }
預期輸出
sort  -> 修 bug(1) 回信(3) 備份(5)
min   -> 修 bug
pq 頂 -> 備份(預設是最大優先)

只寫了一個 operator<sortmin_elementpriority_queue 就全部能用了。注意 priority_queue 預設是最大優先,所以頂端是 priority 最大的那個。

逐步比較四筆工作的優先序,觀察 operator< 的回傳值如何決定排列順序。動畫示範比較關係;std::sort 的實際比較步驟由標準函式庫實作決定。

排序前
備份(5)
修bug(1)
回信(3)
寫報告(2)
圖中每次兩兩比較都可呼叫 operator<;六次只是本示範序列,std::sort 的實際比較次序與次數由實作決定。
排序後
修bug(1)
寫報告(2)
回信(3)
備份(5)
把比較寫成一般運算式 CODE
Task backup{"備份", 5}; Task fix{"修 bug", 1}; Task reply{"回信", 3}; Task report{"寫報告", 2}; bool c1 = fix < backup; // true:1 < 5 bool c2 = reply < backup; // true:3 < 5 bool c3 = reply < fix; // false:3 < 1 bool c4 = report < reply; // true:2 < 3 bool c5 = report < fix; // false:2 < 1 bool c6 = fix < report; // true:1 < 2 // std::sort 可呼叫同一個 operator<,但比較順序由實作決定
關鍵那一行 WHY

fix < backup 相當於 fix.operator<(backup),本例比較 priority。比較應符合嚴格弱序:例如不能讓 a < a 為真,也不能互相認定 a < b 且 b < a。這些規則錯了,即使語法合法,標準排序的前提也不成立。

順便:priority_queue 那三個尖括號參數是什麼

優先佇列的型別可以明寫以下三個參數,先逐個讀「元素、底層容器、比較器」。它是一般標準庫介面,不依賴特定演算法。

priority_queue<pair<int,string>, vector<pair<int,string>>, greater<pair<int,string>>> pq;

位置這裡的值意思
第 1 參數pair<int,string>元素型別 —— 存的是什麼。pair 比較時先比 first(就是這一節的 operator<
第 2 參數vector<pair<int,string>>保存元素的底層容器;此處用 vector,滿足 priority_queue 對容器的需求
第 3 參數greater<pair<int,string>>比較器 —— 決定誰排在頂端

預設比較方式下 priority_queue 讓較大的值在頂端。若情境是「較早的預約時間先處理」,可用適當的 greater 比較器讓較小時間先出現。比較器影響取出規則,不需先了解底層如何維護順序。

同一批資料,預設版跟 greater 版 top() 不一樣
#include <iostream> #include <queue> #include <vector> #include <string> using namespace std; int main() { vector<pair<int,string>> data = { {5,"C"}, {2,"A"}, {8,"D"}, {3,"B"} }; priority_queue<pair<int,string>> pqMax(data.begin(), data.end()); // 預設 less<> cout << "預設(最大值優先) top -> " << pqMax.top().first << " " << pqMax.top().second << endl; priority_queue<pair<int,string>, vector<pair<int,string>>, greater<pair<int,string>>> pqMin(data.begin(), data.end()); cout << "greater(最小值優先) top -> " << pqMin.top().first << " " << pqMin.top().second << endl; return 0; }
預期輸出
預設(最大值優先) top -> 8 D
greater(最小值優先) top -> 2 A

pair 的預設比較先比 first,相同時再比 second。less 使用 <,greater 使用 >;本例的 pair 已提供對應比較。自訂型別若只有 operator<,不能因此假設所有方向的運算子都自動存在。

PART 08 · 分岔

有序容器要 operator<,無序容器要雜湊

下面把已學過的模板延伸為「替特定型別提供版本」:template<> struct hash<Point> 是 std::hash 對 Point 的特化。它放在 namespace std,是為自訂型別提供標準雜湊器的指定用法,不代表可以任意往 std 加入宣告。operator() 是呼叫運算子,因此雜湊器物件可寫成 hasher(point)std::size_t 是表示大小的無號整數型別。

本例先用 std::hash<int>{} 建立標準整數雜湊器,再對座標呼叫它。^ 是逐位元 XOR,<< 1 把無號數的位元向左移一格;這裡只是簡單組合兩個欄位,並不保證不同座標得到不同雜湊值。相等的座標必須得到相同值,而碰撞時容器仍會比較鍵。

這是 C++ 與 Python 一個結構性的差別。Python 的 dictset 一律要求鍵可雜湊;C++ 分成兩套:

容器底層你的型別要提供查詢成本
set / map依比較器排序operator< 或自訂比較器$O(\log n)$,走訪有序
unordered_set / unordered_map雜湊表operator==std::hash 特化平均 O(1)、最壞 O(n);走訪順序未指定
#include <cstddef> #include <functional> #include <iostream> #include <unordered_set> struct Point { int x, y; bool operator==(const Point& other) const { return x == other.x && y == other.y; } }; namespace std { template<> struct hash<Point> { size_t operator()(const Point& p) const { size_t hx = hash<int>{}(p.x); size_t hy = hash<int>{}(p.y); return hx ^ (hy << 1); } }; } int main() { std::unordered_set<Point> seen; seen.insert(Point{2, 3}); seen.insert(Point{2, 3}); std::cout << "unique = " << seen.size() << "\n"; std::cout << "found = " << seen.count(Point{2, 3}) << "\n"; }
預期輸出
unique = 1
found = 1
💡 鐵則跟 Python 一樣

a == b 為真時,hash(a) 必須等於 hash(b)。所以雜湊值要從「決定相等的那些欄位」算出來。這條規則違反了,同一個鍵會查不到、或在容器裡出現兩份。

PART 09 · 容器協定

讓自訂結構支援 size()[]for

C++ 沒有像 Python 那樣一組固定的「協定方法」,但 STL 有一套慣例。照著提供,你的類別就能用得跟內建容器一樣。

你想支援的寫法要提供什麼獨立例子
x.size()一個 size() 成員函式名單人數
x[i]operator[]每小時溫度
x[i] = v回傳參考operator[]更新某時段讀數
a == boperator==相同格子位置
a < boperator<工作優先順序
for (auto x : c)begin()end()逐一讀取記錄
operator[] 要寫兩個版本

一個回傳參考(T& operator[](int i))給人改,一個是 const 版本(const T& operator[](int i) const)給唯讀情境用。少了 const 版本,const 物件就下標不了。

PART 10 · 設計抉擇

組合 vs 繼承:Inbox 該繼承 vector 嗎?

做一個必須保留收件順序、只允許依序接收與取件的收件箱,最省事的寫法看起來是直接繼承 vector。試試看會怎樣:

繼承 vector 的陷阱
#include <iostream> #include <vector> #include <algorithm> using namespace std; class Inbox : public vector<int> { public: void receive(int number) { push_back(number); } }; int main() { Inbox box; box.receive(20); box.receive(10); cout << "收到的順序: "; for (int x : box) cout << x << " "; cout << endl; sort(box.begin(), box.end()); box.insert(box.begin() + 1, 99); cout << "外部任意改動後: "; for (int x : box) cout << x << " "; cout << endl; }
預期輸出
收到的順序: 20 10
外部任意改動後: 10 99 20 

問題不在它不能動,而在它能做的事太多了:使用者可以對 Inbox 呼叫 sort()insert(begin()+1, x),任意改變收件順序,破壞類別只允許依序收件與取件的承諾。

繼承(is-a)組合(has-a)
寫法class Inbox : public vector<int>vector<T> items;
對外露出什麼父類別所有方法,擋不掉只有你自己寫的
能換底層實作嗎公開介面已依賴基底型別,替換較受限制可在維持公開操作的前提下調整內部實作
什麼時候該用子類別真的是一種父類別,而且父類別每個方法對它都合理絕大多數的資料結構
🎯 判斷法則

先問基底的公開操作是否都符合衍生型別的承諾。收件箱若必須保留收到順序,公開繼承 vector 就會把任意插入、索引與重排等能力一併暴露;用成員組合可以只公開需要的操作。

⚠️ 直接管理動態資源時要一起考慮:Rule of Three

類別若直接擁有動態資源,就要一起考慮釋放、複製建構與複製賦值,不能只補解構式。預設逐成員複製指標可能讓兩個物件共用同一配置,造成相互影響與重複釋放,這是 Rule of Three 要提醒的責任。短程式也可能在結束前就出錯;實務可優先使用標準容器或合適的所有權管理型別。

整理自己的類別:介面與資源檢查

以下是一般 C++ 程式常見的介面與資源檢查點。每項都有相應的語法或生命週期理由,不能以「程式很短」作為忽略它的理由。

  • 標頭中保留 std:: 前綴,避免 using namespace std; 影響每個引入它的來源檔。命名空間與標頭的關係見 P1
  • 若類別直接擁有 new 配置的資源,要一起安排釋放、複製建構與複製賦值,或明確禁止複製;不能只複製原始指標。這是上面的 Rule of Three
  • 標準 stacktop()pop() 要求非空,使用前先檢查 empty()。自己的介面若選擇以例外回報空資料,就要清楚說明契約;例外處理見 P7
  • 只查詢一般成員的 getter 通常加 const,讓唯讀物件也能使用;會更新一般成員的 setter 不加。對照例子見 P8
  • 唯讀且不需保存副本的 string 參數,可用 const string& 避免複製;需要獨立副本時再選傳值。參數傳遞見 P3
  • 容器 size() 使用無號的 size_type,可用 auto 保留回傳型別;不要在未確認範圍時轉成 int,也要避免空容器的 size()-1。大小與容量見 P5
  • nullptr 表達空指標;釋放後即使把一個指標設空,其他別名也不會自動更新。指標生命週期見 P4
  • 像 ReadingLog 這種要控制操作的類別,以 private 隱藏儲存;像 DisplayPoint 這種單純傳遞座標的資料,也可以合理公開成員。依不變條件決定,對照見 P8
補充 · 主動拋出

throw 與自訂例外

當函式無法完成它承諾的事時就該拋例外,而不是以正常回傳值掩蓋失敗。

沒有草稿時,takeLast 應回報錯誤
#include <iostream> #include <vector> #include <stdexcept> using namespace std; class NoDraftError : public runtime_error { // 自訂例外,繼承標準的就好 public: NoDraftError() : runtime_error("草稿箱沒有待取出的草稿") { } }; template <typename T> class DraftBox { private: vector<T> drafts; public: void save(T item) { drafts.push_back(item); } T takeLast() { if (drafts.empty()) throw NoDraftError(); // 我做不到,交給呼叫端決定 T top = drafts.back(); drafts.pop_back(); return top; } }; int main() { DraftBox<int> s; try { s.takeLast(); } catch (const NoDraftError& e) { cout << "接住了: " << e.what() << endl; } return 0; }
預期輸出
接住了: 草稿箱沒有待取出的草稿

這個例子的自訂例外繼承 runtime_error,建構式把訊息交給基底類別,再以 e.what() 取回。獨立型別讓呼叫端能精準接住草稿箱的特定失敗;這也延續了 P7 先用標準例外建立的 throw/catch 基礎

💡 為什麼不回傳一個特殊值就好

因為沒有哪個值是安全的。空堆疊 takeLast 回傳 0?那真的存了 0 的時候怎麼辦?回傳 -1?那存負數呢?而且呼叫端很容易忘記檢查,錯誤就一路往下傳,最後在很遠的地方才顯現。例外的價值就在於它不會被無視

EX · 隨堂練習

三題確認觀念 答錯就回該節

EXERCISE 1 · 優先佇列排不動

你寫了一個 Job 類別,把一堆 Job 丟進 priority_queue,結果編譯錯誤說 no match for 'operator<'。最直接的解法是?

(A) 加上 operator==
(B) 加上 operator<
(C) 加上 operator<<
(D) 加上 operator!=
EXERCISE 2 · 多型沒發生

Base *p = &derivedObj; p->who(); 跑出來是父類別的版本。為什麼?

(A) who() 沒有標 virtual
(B) 不能用基底類別的指標指向子類別物件
(C) 子類別沒有真的覆寫
(D) 指標必須改成參考才有多型
EXERCISE 3 · 該繼承還是該持有

你要實作一個保留收件順序、只允許依序接收與取件的 Inbox。哪個設計比較好?

(A) class Inbox : public vector<int>
(B) 內部持有一個 vector,只提供收件與取件操作
(C) 兩者完全等價
(D) 把 vector 設為 public 資料成員
REFERENCE · 速查表

運算子多載全表:語法 ↔ 函式 ↔ 使用情境

你寫的要定義的函式用途獨立例子
cout << xfriend operator<<印出物件印出座標
a + boperator+加法位移相加
a == boperator==相等判斷相同格子位置
a < boperator<排序、優先取值、set工作優先順序
x[i]operator[]下標讀寫每小時溫度
hash<T>std::hash 特化unordered 容器的鍵座標當查詢鍵
for (auto x : c)begin() / end()範圍走訪逐一讀取記錄

五條記得住的規則

① 子類別的建構式用成員初始化列表呼叫父類別的版本。
② 要多型就要 virtual,而且要透過指標或參考呼叫。
③ 想被排序、想進 setpriority_queue,給 operator<
④ 想當 unordered_map 的鍵,給 operator==std::hash 特化。
⑤ 不確定該繼承還是組合時,選組合

延伸閱讀

資源看什麼
cppreference · Operator overloading所有可多載運算子的完整清單與慣例。
cppreference · Templatestemplate 的完整規則,包含為什麼實作要放標頭檔。
cppreference · Operators運算子多載的語法、限制與常見形式。
C++ Gossip · 運算子多載延伸比較器語法與自訂型別的介面設計。
QUIZ · 自我檢測

自我檢測:類別延伸與模板 隨堂自測 · 6 題

每個選項都有解說:選錯也點開看看為什麼錯。全對之後再往下翻詞彙卡。

Q1.以下程式印出什麼?
#include <iostream>
using namespace std;
class Base {
    public:
        void who() { cout << "Base"; }
};
class Derived : public Base {
    public:
        void who() { cout << "Derived"; }
};

int main() {
    Derived d;
    Base *p = &d;
    p->who();
}
Q2.以下完整程式為什麼無法編譯?
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Task {
    public:
        string name;
        int priority;
        Task(string n, int p) { name = n; priority = p; }
};

int main() {
vector<Task> v = { Task("a", 5), Task("b", 1) };
sort(v.begin(), v.end());
}
Q3.使用預設雜湊器與相等判定時,如何讓自訂型別成為 unordered_map 的鍵?
Q4.以下程式印出什麼?
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Box {
    private:
        T item;
    public:
        Box(T v) { item = v; }
        T get() const { return item; }
};

int main() {
    Box<int> a(42);
    Box<string> b("hi");
    cout << a.get() << " " << b.get();
}
Q5.Inbox 只允許依收件順序加入與取出。寫成 class Inbox : public vector<int> 的主要問題是什麼?
Q6.為什麼一般供其他來源檔使用、由使用端隱式實例化的類別樣板,通常把定義也放在標頭中?
CARDS · 關鍵詞彙卡

關鍵詞彙卡:點卡片翻面

先看正面術語,心中默想定義再翻面對答案;洗牌後再過一輪,直到每張都能不假思索說出來。