《C++ Primer》第 15 章聚焦繼承、虛函數、多態、抽象基類、訪問控制、類型轉換、動態綁定、虛析構函數、文本查詢系統。本文提供全章習題詳細答案、核心概念解析、關鍵代碼實現(Quote 體系、Basket、Query 系統),並總結工程最佳實踐。
一、核心概念速查表
| 概念 | 定義/規則 |
| 虛函數 | 基類用 virtual 聲明,派生類可覆蓋(override),支持動態綁定。構造函數不能虛,析構函數**應為虛** |
| 純虛函數 | virtual Ret fun() = 0;,含純虛函數的類為抽象基類,不可實例化 |
| protected | 派生類成員/友元可訪問,外部不可訪問;比 private 寬鬆,比 public 嚴格 |
| 公有繼承 | 建立 is-a 關係,基類接口完整保留,支持向上轉型(多態基礎) |
| 私有/保護繼承 | 建立 has-a / implemented-in-terms-of 關係,基類 public/protected 成員在派生類變為 private/protected,外部不可轉型 |
| 虛析構函數 | 確保 delete base_ptr 正確調用派生類析構函數,防止資源洩漏 |
| 靜態類型 vs 動態類型 | 靜態:編譯時聲明類型;動態:運行時對象實際類型。指針/引用兩者可能不同 |
| 動態綁定條件 | 虛函數 + 基類指針/引用 + 派生類對象 |
| override / final | override 明確覆蓋意圖,編譯器檢查簽名匹配;final 禁止進一步覆蓋/繼承 |
| 繼承構造函數 | C++11 using Base::Base; 繼承基類構造函數(除默認/拷貝/移動) |
二、Quote 體系:面向對象設計範例
2.1 基類 Quote
class Quote {
public:
Quote() = default;
Quote(const string& book, double price) : bookNo(book), price(price) {}
string isbn() const { return bookNo; }
virtual double net_price(size_t n) const { return n * price; }
virtual void debug() const { cout << "bookNo=" << bookNo << " price=" << price << endl; }
virtual ~Quote() = default; // 虛析構函數!
private:
string bookNo;
protected:
double price = 0.0;
};
2.2 折扣基類 Disc_quote(抽象)
class Disc_quote : public Quote {
public:
Disc_quote() = default;
Disc_quote(const string& book, double price, size_t qty, double disc)
: Quote(book, price), quantity(qty), discount(disc) {}
double net_price(size_t) const = 0; // 純虛函數
protected:
size_t quantity = 0;
double discount = 0.0;
};
2.3 批量折扣 Bulk_quote
class Bulk_quote : public Disc_quote {
public:
using Disc_quote::Disc_quote; // 繼承構造函數
double net_price(size_t cnt) const override {
if (cnt >= quantity) return cnt * (1 - discount) * price;
else return cnt * price;
}
void debug() const override {
Quote::debug();
cout << "quantity=" << quantity << " discount=" << discount << endl;
}
};
2.4 限量折扣 Limited_quote(習題 15.7/16)
class Limited_quote : public Disc_quote {
public:
using Disc_quote::Disc_quote;
double net_price(size_t cnt) const override {
if (cnt <= quantity) return cnt * (1 - discount) * price;
else return quantity * (1 - discount) * price + (cnt - quantity) * price;
}
};
三、動態綁定與多態實戰
3.1 print_total 函數
double print_total(ostream& os, const Quote& item, size_t n) {
double ret = item.net_price(n); // 動態綁定:實際調用派生類版本
os << "ISBN: " << item.isbn() << " #sold: " << n << " total due: " << ret << endl;
return ret;
}
3.2 容器存儲策略對比
| 策略 | 代碼 | 行為 | 適用場景 |
| 對象切片 | vector vec; vec.push_back(bulk); | 僅保留基類部分,**靜態綁定**調用 Quote::net_price | 錯誤用法,切片丟失派生類狀態 |
| 裸指針 | vector vec; vec.push_back(&bulk); | 動態綁定,但**手動管理生命周期**,易洩漏/懸空 | 避免使用 |
| 智能指針 | vector> vec; vec.push_back(make_shared(...)); | 動態綁定 + 自動生命周期管理,推薦標準做法 | 生產代碼首選 |
四、Basket 購物籃系統(習題 15.30)
class Basket {
public:
void add_item(const shared_ptr& sale) { items.insert(sale); }
double total_receipt(ostream& os) const {
double sum = 0.0;
for (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) {
sum += print_total(os, **iter, items.count(*iter));
}
os << "Total Sale: " << sum << endl;
return sum;
}
private:
static bool compare(const shared_ptr& lhs, const shared_ptr& rhs) {
return lhs->isbn() < rhs->isbn();
}
multiset, decltype(compare)*> items{compare};
};
五、文本查詢系統:Query 類層次(習題 15.31-42)
5.1 核心類圖
Query_base (抽象)
├─ WordQuery // 單詞查詢
├─ NotQuery // ~Query
├─ BinaryQuery (抽象) // 兩操作數
│ ├─ AndQuery // Query & Query
│ └─ OrQuery // Query | Query
Query (句柄類) // 封裝 shared_ptr,提供運算符重載、eval、rep
5.2 關鍵實現片段
// Query 句柄類
class Query {
friend Query operator~(const Query&);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const string& s) : q(new WordQuery(s)) {}
QueryResult eval(const TextQuery& t) const { return q->eval(t); }
string rep() const { return q->rep(); }
private:
Query(shared_ptr query) : q(query) {}
shared_ptr q;
};
// 二元查詢基類
class BinaryQuery : public Query_base {
protected:
BinaryQuery(const Query& l, const Query& r, string op)
: lhs(l), rhs(r), oper(op) {}
string rep() const override { return "(" + lhs.rep() + " " + oper + " " + rhs.rep() + ")"; }
Query lhs, rhs;
string oper;
};
// AndQuery / OrQuery 繼承 BinaryQuery,實現 eval() 進行 set_intersection / set_union
// NotQuery 實現 eval() 進行 set_difference (全行集 - 子查詢結果)
5.3 表達式構建對象數量分析(習題 15.31)
| 表達式 | 創建對象數 | 構成 |
Query(s1) | Query(s2) & ~Query(s3) | 12 個 | 3 WordQuery + 1 NotQuery + 1 AndQuery + 1 OrQuery + 6 Query 句柄 |
Query(s1) | (Query(s2) & ~Query(s3)) | 12 個 | 同上,結合性不影響對象數 |
(Query(s1) & Query(s2)) | (Query(s3) & Query(s4)) | 14 個 | 4 WordQuery + 2 AndQuery + 1 OrQuery + 7 Query 句柄 |
六、習題精選答案摘要
| 題號 | 考點 | 答案要點 |
| 15.1 | 虛成員定義 | 基類希望派生類重定義的非 static 成員(除構造函數外) |
| 15.2 | protected vs private | protected 允許派生類訪問,private 不允許 |
| 15.3 | Quote/print_total 定義 | 見 2.1、3.1 |
| 15.4 | 派生列表合法性 | (a) 錯誤:類不能繼承自己 (b) 合法 (c) 錯誤:前向聲明不可含派生列表 |
| 15.5 | Bulk_quote 定義 | 見 2.3 |
| 15.7 | 限量折扣策略 | 見 2.4 |
| 15.8 | 靜態/動態類型定義 | 靜態:編譯時類型;動態:運行時對象類型 |
| 15.9 | 類型不一致場景 | 基類指針/引用指向派生類對象:Quote* p = new Bulk_quote; Quote& r = bulk; |
| 15.11 | 虛 debug 函數 | 基類定義 virtual debug(),派生類 override 並調用基類版本 |
| 15.12 | override + final 必要性 | 必要:override 防止簽名錯誤,final 防止進一步覆蓋,意圖明確 |
| 15.14 | 虛函數調用解析 | 對象調用→靜態綁定;指針/引用調用→動態綁定(運行時類型決定) |
| 15.15 | Disc_quote 抽象基類 | 見 2.2 |
| 15.18 | 繼承方式與向上轉型 | 僅公有繼承允許用戶代碼隱式向上轉型 |
| 15.19 | 派生類成員函數轉型合法性 | 私有繼承體系中,派生類成員函數**不能**將 *this 轉為基類引用(對用戶代碼不可見,但對成員可見) |
| 15.22 | 幾何圖元繼承體系設計 | Figure → Figure_2D(area/perimeter) → Rectangle/Circle; Figure_3D(cubage) → Sphere/Cone |
| 15.24 | 虛析構函數必要性 | 基類指針刪除派生類對象時正確調用派生類析構函數 |
| 15.25 | Disc_quote 默認構造函數作用 | 初始化基類 Quote 部分,缺失將導致 Bulk_quote 默認構造失敗 |
| 15.26 | 拷貝控制成員實現 | 合成版本行為:拷貝 shared_ptr 計數+1,移動轉移所有權 |
| 15.27 | 繼承構造函數 | using Disc_quote::Disc_quote; |
| 15.28-29 | 容器存儲策略差異 | 對象切片調用基類版本;shared_ptr 調用派生類版本(動態綁定) |
| 15.32-33 | Query/Query_base 拷貝語義 | Query 合成拷貝控制(shared_ptr 語義);Query_base 無數據成員,合成版本無操作 |
| 15.35 | Query/Query_base 核心定義 | 見 5.2 |
| 15.38 | 聲明合法性 | BinaryQuery 抽象不可實例化;Query 不能隱式轉為 AndQuery/OrQuery |
| 15.40 | OrQuery 空集處理 | set 構造/插入天然支持空範圍,結果正確 |
| 15.41 | 原始指針實現 Query | 需手動引用計數(uc),實現拷貝/賦值/析構,極易出錯,**不推薦** |
| 15.42 | 擴展設計 | (a) 按句子索引 (b) 歷史系統 (c) 結果範圍限制 |
七、工程最佳實踐總結
- 公有繼承表達 is-a,組合優於繼承(has-a)
- 基類析構函數必須虛,防止資源洩漏
- 虛函數盡量加
override,葉子類加 final
- 多態容器統一用
shared_ptr,避免切片與手動內存管理
- 抽象基類定義接口,具體類實現細節,依賴倒置原則
- 類型轉換運算符與單參數構造函數加
explicit,避免隱式轉換陷阱
- 文本查詢系統展示了「句柄/主體」模式:
Query 輕量句柄,Query_base 體系承擔邏輯,運算符重載構建領域語言
第 15 章是 C++ 面向對象精髓所在,徹底掌握繼承體系設計、動態綁定機制、資源管理規範,是編寫大型可維護 C++ 系統的基石。