《C++ Primer》第 14 章涵蓋運算符重載、類型轉換運算符、輸入輸出運算符、關係/相等運算符、賦值運算符、函數調用運算符、自增自減、成員訪問運算符、標量布爾轉換等核心主題。本文整理關鍵知識點,並提供全章習題參考實現(含 Sales_data、String、StrBlob、StrVec、Date、函數對象、Lambda、標準庫算法綁定等完整代碼)。
| 主題 | 關鍵規則與最佳實踐 |
|---|---|
| 運算符重載基礎 | 至少一個運算元為類類型;不可改變優先級/結合性/操作數個數;避免重載 && || ,(失去短路求值/求值順序) |
| 成員 vs 非成員 | 賦值/下標/調用/箭頭**必須**為成員;對稱運算符(算術/關係/相等)推薦非成員友元 |
| 輸入輸出運算符 | operator<< 返回 ostream&;operator>> 返回 istream&;處理失敗時設置流狀態 |
| 關係/相等運算符 | 定義 == 和 <,其餘用模板/輔助函數合成;!= = !(==),> = < 交換參數 |
| 賦值運算符 | 返回 *this 引用;處理自賦值;拷貝賦值 vs 移動賦值(C++11) |
| 函數調用運算符 | 可重載為任意參數列表;實現函數對象,配合 STL 算法 |
| 自增自減 | 前置返回引用,後置返回舊值副本(int 佔位參數區分) |
| 成員訪問運算符 | operator->() 必須返回指針或類類型對象(遞歸調用) |
| 類型轉換運算符 | 單參數構造函數 + 轉換運算符 = 隱式轉換陷阱;關鍵字 explicit 抑制隱式轉換(C++11 可修飾轉換運算符) |
| 標量布爾轉換 | explicit operator bool() 用於流狀態檢查、智能指針判空 |
Sales_data —— 運算符重載標準范式class Sales_data {
friend Sales_data operator+(const Sales_data&, const Sales_data&);
friend istream& operator>>(istream&, Sales_data&);
friend ostream& operator<<(ostream&, const Sales_data&);
public:
Sales_data() = default;
Sales_data(const string& s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(n*p) {}
Sales_data(const string& s) : Sales_data(s, 0, 0) {}
Sales_data(istream& is) { is >> *this; }
Sales_data& operator+=(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
Sales_data& operator=(const string& isbn) { // 14.22
*this = Sales_data(isbn);
return *this;
}
string isbn() const { return bookNo; }
explicit operator string() const { return bookNo; } // 14.45
explicit operator double() const { return avg_price(); } // 14.45
private:
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
double avg_price() const { return units_sold ? revenue/units_sold : 0; }
};
Sales_data operator+(const Sales_data& lhs, const Sales_data& rhs) {
Sales_data sum = lhs; sum += rhs; return sum;
}
istream& operator>>(istream& is, Sales_data& item) {
double price; is >> item.bookNo >> item.units_sold >> price;
if (is) item.revenue = price * item.units_sold; else item = Sales_data();
return is;
}
ostream& operator<<(ostream& os, const Sales_data& item) {
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
String —— 動態字符串完整實現(含移動語義、迭代器、運算符組)class String {
friend ostream& operator<<(ostream&, const String&);
friend istream& operator>>(istream&, String&);
friend bool operator==(const String&, const String&);
friend bool operator!=(const String&, const String&);
friend bool operator<(const String&, const String&);
friend bool operator>(const String&, const String&);
friend bool operator<=(const String&, const String&);
friend bool operator>=(const String&, const String&);
public:
String() : String("") {}
String(const char* s) { range_initializer(s, s + strlen(s)); }
String(const String& rhs) { range_initializer(rhs.elements, rhs.first_free); }
String(String&& s) noexcept : elements(s.elements), first_free(s.first_free), cap(s.cap) {
s.elements = s.first_free = s.cap = nullptr;
}
String& operator=(const String& rhs) { // 拷貝賦值
auto newstr = alloc_n_copy(rhs.elements, rhs.first_free);
free();
elements = newstr.first; first_free = cap = newstr.second;
return *this;
}
String& operator=(String&& rhs) noexcept { // 移動賦值
if (this != &rhs) { free(); elements = rhs.elements; first_free = rhs.first_free; cap = rhs.cap;
rhs.elements = rhs.first_free = rhs.cap = nullptr; }
return *this;
}
~String() { free(); }
char& operator[](size_t n) { return elements[n]; }
const char& operator[](size_t n) const { return elements[n]; } // 14.26
void push_back(char c) { chk_n_alloc(); *last_elem++ = c; alloc.construct(first_free++, '\0'); }
size_t size() const { return last_elem - elements; }
size_t capacity() const { return cap - elements; }
const char* c_str() const { return elements; }
// 迭代器支持
char* begin() const { return elements; }
char* end() const { return last_elem; }
private:
static allocator alloc;
char *elements, *first_free, *cap, *last_elem; // last_elem 指向最後有效字符
// 內存管理輔助函數...
pair alloc_n_copy(const char*, const char*);
void range_initializer(const char*, const char*);
void free();
void reallocate();
void alloc_n_move(size_t new_cap);
void chk_n_alloc() { if (first_free == cap) reallocate(); }
};
StrBlob / StrBlobPtr / ConstStrBlobPtr —— 共享向量容器與指釖迭代器核心設計:
StrBlob 內含 shared_ptr> ,支持拷貝共享底層數據StrBlobPtr 保存 weak_ptr + 索引,解引用時 lock() 檢查有效性==, !=, <, >, <=, >=, ++, --, +, -, +=, -=, *, ->, []ConstStrBlobPtr 提供常量訪問語義StrVec —— 簡易 vector 實現包含擴容策略(2倍)、移動構造/賦值、初始化列表賦值(14.23)、下標運算符(14.26)、關係運算符組。
Date —— 日期類與算術運算符、關係運算符、流運算符、布爾轉換+ - 天數運算explicit operator bool() 判斷年份是否合法(< 4000)==, !=, <, <=, >, >=, +, -, +=, -=, <<, >>// 14.44 計算器 map
int add(int i, int j) { return i + j; }
auto mod = [](int i, int j) { return i % j; };
struct wy_div { int operator()(int d, int v) { return d / v; } };
map> binops = {
{"+", add}, {"-", minus()}, {"/", wy_div()}, {"*", [](int i,int j){return i*j;}}, {"%", mod}
};
bind 綁定參數/調整順序
// 14.42
count_if(vec.begin(), vec.end(), bind(greater(), _1, 1024));
find_if(svec.begin(), svec.end(), bind(not_equal_to(), _1, "pooh"));
transform(ivec.begin(), ivec.end(), ivec.begin(), bind(multiplies(), _1, 2));
auto data = {2, 3, 4, 5};
int input; cin >> input;
modulus mod;
auto pred = [&](int i) { return 0 == mod(input, i); };
bool divisible = any_of(data.begin(), data.end(), pred);
| 題號 | 核心考點 | 答案要點 |
|---|---|---|
| 14.1 | 重載運算符與內置差異 | 可直接調用函數形式;至少一個類類型運算元;不保證求值順序;優先級結合性相同 |
| 14.2 | Sales_data 輸入輸出/加法 | 見 2.1 完整實現 |
| 14.3 | 字面值與字符串比較 | (a) const char[] 比較地址 (b) string 版本 (c) vector 版本 (d) string 版本 |
| 14.5 | Book 類完整運算符組 | 定義 ==, !=, <, >, +, +=, <<, >> |
| 14.7 | String 完整實現 | 見 2.2,含移動語義、迭代器、下標、關係運算符 |
| 14.11 | 輸入運算符錯誤處理 | 失敗時重置對象為默認狀態 |
| 14.14 | 複合賦值實現加法 | operator+= 修改左運算元,operator+ 創建副本調用 += |
| 14.16 | 拷貝/移動構造輸出驗證 | 通過 foo(String) bar(const String&) baz() 觀察調用次序 |
| 14.22 | 字符串賦值運算符 | Sales_data& operator=(const string&) 委託構造函數 |
| 14.23 | 初始化列表賦值 | StrVec& operator=(initializer_list |
| 14.24 | Date 類完整實現 | 見 2.5,含天數運算、關係運算符、流運算符、bool 轉換 |
| 14.26 | 常量下標運算符 | 返回 const T&,配合非常量版本構成重載組 |
| 14.27 | 前置自增自減返回引用 | StrBlobPtr& operator++() 檢查邊界後 ++curr; return *this; |
| 14.28 | 算術運算符 + - | operator+(size_t) const 創建副本調用 += |
| 14.30 | 箭頭運算符 | const string* operator->() const { return &this->operator*(); } |
| 14.32 | 智能指針模擬 | StrBlobPtr_pointer 封裝原始指針,實現 * -> |
| 14.34 | 函數對象實現三元運算 | struct func_class { int operator()(bool a, int b, int c) { return a ? b : c; } }; |
| 14.35 | PrintString 函數對象 | 構造捕獲 istream&,調用 getline 返回行內容 |
| 14.37-38 | 謂詞函數對象 | IsEqual BoundTest 配合 replace_if count_if |
| 14.40 | 算法綜合應用 | biggies:去重、按長度穩定排序、find_if 分區、統計輸出 |
| 14.45 | 顯式類型轉換 | explicit operator string() explicit operator double() 防止隱式轉換 |
| 14.46 | 轉換運算符設計原則 | 應聲明 explicit,避免誤導性隱式轉換 |
| 14.49 | 標量布爾轉換 | explicit operator bool() 用於流/智能指針判空 |
| 14.52 | 多重轉換二義性 | LongDouble 同時提供 operator double() operator float() 導致 int 初始化二義性 |
explicit,除非極確定需要隱式轉換(如智能指針 operator bool)noexcept 標註利於容器優化std::function 實現類型擦除bind、lambda、function 組合替代手寫循環完整代碼已整合於 Chapter14.h 與 main.cc,通過宏 NUM14XX 選擇運行特定習題。建議讀者逐題編譯運行,對照輸出深化理解。