首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ Primer 第五版 第14章 重載運算與類型轉換:筆記與習題完整解析

     編輯:關於C++

《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() 用於流狀態檢查、智能指針判空

二、經典類實現精選

2.1 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;
}

2.2 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(); }
};

2.3 StrBlob / StrBlobPtr / ConstStrBlobPtr —— 共享向量容器與指釖迭代器

核心設計:

  • StrBlob 內含 shared_ptr>,支持拷貝共享底層數據
  • StrBlobPtr 保存 weak_ptr + 索引,解引用時 lock() 檢查有效性
  • 完整實現 ==, !=, <, >, <=, >=, ++, --, +, -, +=, -=, *, ->, []
  • ConstStrBlobPtr 提供常量訪問語義

2.4 StrVec —— 簡易 vector 實現

包含擴容策略(2倍)、移動構造/賦值、初始化列表賦值(14.23)、下標運算符(14.26)、關係運算符組。

2.5 Date —— 日期類與算術運算符、關係運算符、流運算符、布爾轉換

  • 內部以「距離紀元天數」存儲,支持 + - 天數運算
  • explicit operator bool() 判斷年份是否合法(< 4000)
  • 完整實現 ==, !=, <, <=, >, >=, +, -, +=, -=, <<, >>

三、函數對象、Lambda 與標準庫綁定

3.1 可調用對象統一調用包裝器

// 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}
};

3.2 Lambda vs 函數對象選擇指南

  • Lambda:一次性、簡單、無狀態/少量捕獲狀態、作為算法參數傳遞
  • 函數對象:需保持狀態、多次複用、需要自定義類型/構造邏輯、作為模板參數類型

3.3 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));

3.4 習題 14.43:任意除數檢查

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.2Sales_data 輸入輸出/加法見 2.1 完整實現
14.3字面值與字符串比較(a) const char[] 比較地址 (b) string 版本 (c) vector 版本 (d) string 版本
14.5Book 類完整運算符組定義 ==, !=, <, >, +, +=, <<, >>
14.7String 完整實現見 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.24Date 類完整實現見 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.35PrintString 函數對象構造捕獲 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 初始化二義性

五、總結與工程建議

  1. 運算符重載:遵循「最小驚訝原則」,語義與內置類型一致;對稱運算符寫非成員友元
  2. 類型轉換:單參數構造函數與轉換運算符均應加 explicit,除非極確定需要隱式轉換(如智能指針 operator bool)
  3. 移動語義:C++11 後實現移動構造/賦值,noexcept 標註利於容器優化
  4. 函數對象:優先使用 Lambda;需狀態/類型時定義結構體;配合 std::function 實現類型擦除
  5. 標準庫算法:善用 bind、lambda、function 組合替代手寫循環

完整代碼已整合於 Chapter14.h 與 main.cc,通過宏 NUM14XX 選擇運行特定習題。建議讀者逐題編譯運行,對照輸出深化理解。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved