侧边栏壁纸
  • 累计撰写 47 篇文章
  • 累计创建 0 个标签
  • 累计收到 38 条评论

目 录CONTENT

文章目录

第 14 章

14.1

【出题思路】

理解重载运算符与内置运算符的区别。

【解答】

不同点:

  • 重载操作符必须具有至少有一个 class 或枚举类型的操作数;
  • 重载操作符不保证操作数的求值顺序。例如,对 &&|| 的重载版本不再具有 “短路求值” 的特性,两个操作数都要进行求值,而且不规定操作数的求值顺序。

不同点:

  • 对于优先级结合性操作数的数目都不变。

14.2

【出题思路】

本题练习重载运算符。

【解答】

程序如下所示:

Sales_data.h

#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H

#include <iostream>
#include <string>

// added overloaded input, output, addition, and compound-assignment operators
class Sales_data {
    friend std::istream &operator>>(std::istream&, Sales_data&);        // input
    friend std::ostream &operator<<(std::ostream&, const Sales_data&);  // output
    friend Sales_data operator+(const Sales_data&, const Sales_data&);  // addition

public:
    Sales_data(const std::string &s, unsigned n, double p)
        : bookNo(s), units_sold(n), revenue(n * p) { }
    Sales_data() : Sales_data("", 0, 0.0f) { }
    Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f) { }
    Sales_data(std::istream&);

    Sales_data &operator+=(const Sales_data&);          // compound-assignment
    std::string isbn() const { return bookNo; }

private:
    double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0;
};

std::istream &operator>>(std::istream&, Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
Sales_data operator+(const Sales_data&, const Sales_data&);

inline
double Sales_data::avg_price() const {
    return units_sold ? revenue / units_sold : 0;
}

#endif //TEST_SALES_DATA_H

Sales_data.cpp

#include "Sales_data.h"

Sales_data::Sales_data(std::istream &is) : Sales_data() {
    is >> *this;
}

Sales_data& Sales_data::operator+=(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

std::istream &operator>>(std::istream &is, Sales_data &item) {
    double price = 0.0;
    is >> item.bookNo >> item.units_sold >> price;
    if (is)
        item.revenue = price * item.units_sold;
    else
        item = Sales_data();
    return is;
}

std::ostream &operator<<(std::ostream &os, const Sales_data &item) {
    os << item.isbn() << " " << item.units_sold << " " << item.revenue
       << " " << item.avg_price();
    return os;
}

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
    Sales_data sum = lhs;
    sum += rhs;
    return sum;
}

main.cpp

#include "Sales_data.h"

int main() {
    Sales_data cp5;
    std::cin >> cp5;
    std::cout << cp5 << std::endl;
}
// 第一行为从控制台输入的数据:ISBN 销售数量 单价
// 第二行为程序的输出:ISBN 销售数量 营收 单价
//运行结果
978-7-121-15535-2 20000 99.9
9787-121-15535-2 20000 1.998e+06 99.9

Process finished with exit code 0

14.3

【出题思路】

本题旨在理解编译器如何选择重载运算符的不同版本。

Why does the following not invoke the overloaded operator== (const String &, const String &)? “cobble” == “stone”

【解答】

(a)"cobble" == "stone" 应用了 C++ 语言内置版本的 == ,比较两个指针。

(b)svec1[0] == svec2[0] 应用了 string 版本的重载 ==

(c)svec1 == svec2 应用了 vector 版本的重载 ==

(d)svec1[0] == "stone" 应用了 string 版本的重载 ==,字符串字面常量被转换为 string。

14.4

【出题思路】

理解编译器如何选择重载运算符的不同版本。

【解答】

(a)% 通常定义为非成员。

(b)%= 通常定义为类成员,因为它会改变对象的状态。

(c)++ 通常定义为类成员,因为它会改变对象的状态。

(d)-> 必须定义为类成员,否则编译会报错。

(e)<< 通常定义为非成员。

(f)&& 通常定义为非成员。

(g)== 通常定义为非成员。

(h)() 必须定义为类成员,否则编译会报错。

注:复合赋值运算符(+=-=*=/=%=)会改变对象的状态,一般定义为类成员。如练习 14.20 中的 +=

14.5

【出题思路】

学会判断是否需要为类定义重载运算符。

【解答】

我们以(a)Book 为例,为其定义重载运算符,就可以让我们像内置类型使用运算符那样,使 Book 对象使用重载运算符,在易用性和代码的可读性上有明显的好处。

Book.h

#ifndef TEST_BOOK_H
#define TEST_BOOK_H

#include <iostream>
#include <string>

class Book {
    friend std::istream &operator>>(std::istream&, Book&);
    friend std::ostream &operator<<(std::ostream&, const Book&);
    friend bool operator==(const Book&, const Book&);
    friend bool operator!=(const Book&, const Book&);

public:
    Book() = default;
    Book(std::string no, std::string name, std::string author, std::string pubdate)
        : no_(no), name_(name), author_(author), pubdate_(pubdate) { }
    Book(std::istream &in) { in >> *this; }

private:
    std::string no_;
    std::string name_;
    std::string author_;
    std::string pubdate_;
};

std::istream &operator>>(std::istream&, Book&);
std::ostream &operator<<(std::ostream&, const Book&);
bool operator==(const Book&, const Book&);
bool operator!=(const Book&, const Book&);

#endif //TEST_BOOK_H

Book.cpp

#include "Book.h"

std::istream &operator>>(std::istream &in, Book &book) {
    in >> book.no_ >> book.name_ >> book.author_ >> book.pubdate_;
    if (!in)
        book = Book();
    return in;
}

std::ostream &operator<<(std::ostream &out, const Book &book) {
    out << book.no_ << " " << book.name_ << " " << book.author_
        << " " << book.pubdate_;
    return out;
}

bool operator==(const Book &lhs, const Book &rhs) {
    return lhs.no_ == rhs.no_;
}

bool operator!=(const Book &lhs, const Book &rhs) {
    return !(lhs == rhs);
}

main.cpp

#include "Book.h"

int main() {
    Book book1("978-7-121-15535-2", "CP5", "Lippman", "2012");
    Book book2("978-7-121-15535-2", "CP5", "Lippman", "2012");

    if (book1 == book2)
        std::cout << book1 << std::endl;

    return 0;
}
// 运行结果
978-7-121-15535-2 CP5 Lippman 2012

Process finished with exit code 0

14.6

【出题思路】

本题练习实现输出运算符。

【解答】

见练习 14.2

14.7

【出题思路】

本题练习实现输出运算符。

【解答】

练习 13.44

14.8

【出题思路】

本题练习实现输出运算符。

【解答】

见练习 14.5

14.9

【出题思路】

本题练习实现输入运算符。

【解答】

见练习 14.2

14.10

【出题思路】

理解重载重载运算符的工作过程。

【解答】

(a)参数中传入的 Sales_data 对象将会得到输入的值,其中 bookNo、units_sold、price 的值分别是:0-201-99999-9 10 24.95,同时 revenue 的值是 249.5。

(b)输入错误,参数中传入的 Sales_data 对象将会得到默认值。

14.11

【出题思路】

理解输入运算符通常要判断输入数据的正确性。

【解答】

这个实现没有判断输入数据的正确性,是错误的。

(a)如果输入的是 0-201-99999-9 10 24.95,程序将会正常执行,Sales_data 对象得到正确的值。

(b)如果输入的是 10 24.95 0-201-99999-9,bookNo、units_sold、price 将会得到错误的值,分别是:的值分别是:10 24 0.95,而 revenue 的值是:24 * 0.95 = 22.8。这显然跟我们的预期结果是不一样的。

14.12

【出题思路】

本题练习判断输入数据的正确性。

【解答】

见练习 14.5

14.13

【出题思路】

本题练习重载运算符的实现。

【解答】

对于 Sales_data 类,其实我们并不需要再为它添加其他算术运算符。

14.14

【出题思路】

理解重载运算符的不同实现方式。

【解答】

显然,从头实现 operator+ 的方式与借助 operator+= 实现的方式相比,在性能上没有优势,而可读性上后者显然更好。

14.15

【出题思路】

本题练习实现重载运算符。

【解答】

对于练习 14.5 的 Book 类,其实我们并不需要再为它添加其他算术运算符。因为 Book 类的 4 个私有数据成员(ISBN、书名、作者、出版日期)并不需要其它算数操作。根据你自己的类想要实现的功能,可以个性化定制运算符重载。

string no_;
string name_;
string author_;
string pubdate_;

14.16

【出题思路】

本题练习实现相等运算符。

【解答】

  • StrBlob & StrBlobPtr (适当修改 练习 12.19 即可)

    代码如下所示:

    StrBlob.h

    #ifndef TEST_STRBLOB_H
    #define TEST_STRBLOB_H
    
    #include <vector>
    #include <string>
    #include <initializer_list>
    #include <memory>
    #include <stdexcept>
    
    using std::string;
    using std::vector;
    using std::initializer_list;
    using std::shared_ptr;
    using std::weak_ptr;
    using std::make_shared;
    using std::out_of_range;
    using std::runtime_error;
    
    // 对于 StrBlob 中的友元声明来说,此前置声明是必要的
    class StrBlobPtr;
    class StrBlob {
        friend class StrBlobPtr;
        // 练习 14.16
        friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
        friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
    
    public:
        typedef vector<string>::size_type size_type;
        StrBlob();
        StrBlob(initializer_list<string> il);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        // 添加和删除元素
        void push_back(const string &t) { data->push_back(t); }
        void pop_back();
        // 元素访问
        string& front();
        const string& front() const;
        string& back();
        const string& back() const;
    
        // 提供给 StrBlobPtr 的接口
        // 返回指向首元素和尾后元素的 StrBlobPtr
        StrBlobPtr begin();     // 定义 StrBlobPtr 后才能定义这两个函数
        StrBlobPtr end();
    
    private:
        shared_ptr<vector<string>> data;
        // 如果 data[i] 不合法,抛出一个异常
        void check(size_type i, const string &msg) const;
    };
    
    StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
    StrBlob::StrBlob(initializer_list <string> il) :
            data(make_shared<vector<string>>(il)) { }
    
    void StrBlob::check(vector<string>::size_type i, const string &msg) const {
        if (i >= data->size())
            throw out_of_range(msg);
    }
    
    string& StrBlob::front() {
        // 如果 vector 为空,check 会抛出一个异常
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    // const 版本 front
    const string& StrBlob::front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    string& StrBlob::back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    // const 版本 back
    const string& StrBlob::back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    void StrBlob::pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    bool operator==(const StrBlob &lhs, const StrBlob&rhs) {
        return lhs.data == rhs.data;            // 所指向的 vector 相等
    }
    
    bool operator!=(const StrBlob &lhs, const StrBlob&rhs) {
        return !(lhs == rhs);
    }
    
    class StrBlobPtr {
        // 练习 14.16
        friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
        friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    
    public:
        StrBlobPtr() : curr(0) {}
        StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    
        string& deref() const;
        StrBlobPtr& incr();     // 前缀递增
        StrBlobPtr& decr();     // 后缀递减
    private:
        // 若检查成功,check 返回一个指向 vector 的 shared_ptr
        shared_ptr<vector<string>> check(size_t, const string&) const;
        // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
        weak_ptr<vector<string>> wptr;
        size_t curr;            // 在数组中的当前位置
    };
    
    shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const {
        auto ret = wptr.lock(); // vector 还存在吗?
        if (!ret)
            throw runtime_error("unbound StrBlobPtr");
        if (i >= ret->size())
            throw out_of_range(msg);
        return ret;             // 否则,返回指向 vector 的 shared_ptr
    }
    
    string& StrBlobPtr::deref() const {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];      // (*P) 是对象所指向的 vector
    }
    
    // 前缀递增:返回递增后的对象的引用
    StrBlobPtr& StrBlobPtr::incr() {
        // 如果 curr 已经指向容器的尾后位置,就不能递增它
        check(curr, "increment past end of StrBlobPtr");
        ++curr;                 // 推进当前位置
        return *this;
    }
    
    // 前缀递减:返回递减后的对象的引用
    StrBlobPtr& StrBlobPtr::decr() {
        // 如果 curr 已经为 0,递减它会产生一个非法下标
        --curr;                 // 递减当前位置
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    // StrBlob 的 begin 和 end 成员的定义
    StrBlobPtr StrBlob::begin() {
        return StrBlobPtr(*this);
    }
    StrBlobPtr StrBlob::end() {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // StrBlobPtr 的比较操作
    bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        // 若底层的 vector 是同一个
        if (l == r)
            // 则两个指针都是空,或者指向相同元素时,它们相等
            return (!r || lhs.curr == rhs.curr);
        else
            return false;       // 若指向不同 vector,则不可能相等
    }
    
    bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
        return !(lhs == rhs);
    }
    
    #endif //TEST_STRBLOB_H
    

    main.cpp

    #include <iostream>
    #include "StrBlob.h"
    
    using namespace std;
    
    int main() {
        StrBlob b1;
        {
            StrBlob b2 = {"a", "an", "the"};
            b1 = b2;
            b2.push_back("about");
            cout << b2.size() << endl;
        }
        // b2 在花括号外失效,作用域仅限于花括号内
        // cout << b2.size() << endl;
        cout << b1.size() << endl;
        cout << b1.front() << " " << b1.back() << endl;
    
        const StrBlob b3 = b1;
        cout << b3.front() << " " << b3.back() << endl;
    
        // 针对练习 14.16 重载运算符 != 的调用
        for (auto iter = b1.begin(); iter != b1.end(); iter.incr())
            cout << iter.deref() << " ";
        cout << endl;
    
        return 0;
    }
    
    // 运行结果
    4
    4
    a about
    a about
    a an the about 
    
    Process finished with exit code 0
    
  • StrVec (适当修改 练习 13.39 即可)

    代码如下所示:

    StrVec.h

    #ifndef TEST_STRVEC_H
    #define TEST_STRVEC_H
    
    #include <string>
    using std::string;
    
    #include <memory>
    using std::allocator;
    
    #include <utility>
    using std::pair;
    
    #include <initializer_list>
    using std::initializer_list;
    
    // 类 vector 类内存分配策略的简单实现
    class StrVec {
        // 练习 14.16
        friend bool operator==(const StrVec &lhs, const StrVec &rhs);
        friend bool operator!=(const StrVec &lhs, const StrVec &rhs);
    
    public:
        StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
        StrVec(const StrVec&);                  // 拷贝构造函数
        StrVec &operator=(const StrVec&);       // 拷贝赋值运算符
        ~StrVec();                              // 析构函数
        void push_back(const string&);          // 拷贝元素
        size_t size() const { return first_free - elements; }
        size_t capacity() const { return cap - elements; }
        string *begin() const { return elements; }
        string *end() const { return first_free; }
    
        // 新加的代码
        // reserve 预留一部分空间,需要一个辅助函数 void reallocate(size_t)
        void reserve(size_t n) { if (n > capacity()) reallocate(n); }
        void resize(size_t);
        void resize(size_t, const string &);
        // 练习 13.40 —— 新添加的一个构造函数
        StrVec(initializer_list<string>);
    
    private:
        // 由于静态成员需要类外初始化,所以我没有把 alloc 定义为 static
        // 因为我不熟悉 allocator<string> 初始化方法
        allocator<string> alloc;                // 分配元素
        // 被添加元素的函数所使用
        void chk_n_alloc() {
            if (size() == capacity())
                reallocate();
        }
        // 工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
        pair<string*, string*> alloc_n_copy(const string*, const string*);
        void free();                            // 销毁元素并释放内存
        void reallocate();                      // 获得更多内存并拷贝已有元素
        string *elements;                       // 指向数组首元素的指针
        string *first_free;                     // 指向数组第一个空闲元素的指针
        string *cap;                            // 指向数组尾后位置的指针
    
        // 新加的代码
        void reallocate(size_t);
    };
    
    #endif //TEST_STRVEC_H
    

    StrVec.cpp

    #include "StrVec.h"
    
    void StrVec::push_back(const string &s) {
        chk_n_alloc();          // 确保有空间容纳新元素
        // 在 first_free 指向的元素中构造 s 的副本
        alloc.construct(first_free++, s);
    }
    
    pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e) {
        // 分配空间保存给定范围中的元素
        auto data = alloc.allocate(e - b);
        // 初始化并返回一个 pair,该 pair 由 data 和 uninitialized_copy 的返回值构成
        return {data, uninitialized_copy(b, e, data)};
    }
    
    void StrVec::free() {
        // 不能传递给 deallocate 一个空指针,如果 elements 为 0,函数什么也不做
        if (elements) {
            // 逆序销毁旧元素
            for (auto p = first_free; p != elements; /* 空 */)
                alloc.destroy(--p);
            alloc.deallocate(elements, cap - elements);
        }
    }
    
    StrVec::StrVec(const StrVec &s) {
        // 调用 alloc_n_copy 分配空间以容纳与 s 中一样多的元素
        auto newdata = alloc_n_copy(s.begin(), s.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    
    StrVec::~StrVec() {
        free();
    }
    
    StrVec& StrVec::operator=(const StrVec &rhs) {
        // 调用 alloc_n_copy 分配内存,大小与 rhs 中元素占用空间一样多
        auto data = alloc_n_copy(rhs.begin(), rhs.end());
        free();
        elements = data.first;
        first_free = cap = data.second;
        return *this;
    }
    
    void StrVec::reallocate() {
        // 我们将分配当前大小两倍的内存空间
        auto newcapacity = size() ? 2 * size() : 1;
        // 分配新内存
        auto newdata = alloc.allocate(newcapacity);
        // 将数据从旧内存移动到新内存
        auto dest = newdata;            // 指向新数组中下一个空闲位置
        auto elem = elements;           // 指向旧数组中下一个元素
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();                         // 一旦我们移动完元素就释放旧内存空间
        // 更新我们的数据结构,执行新元素
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }
    
    // 新加的代码,StrVec::reallocate() 函数的重载形式
    void StrVec::reallocate(size_t newcapacity) {
        // 分配新内存
        auto newdata = alloc.allocate(newcapacity);
    
        // 将数据从旧空间移动到新空间
        auto dest = newdata;            // dest 指向新空间中第一个空闲位置
        auto elem = elements;           // 指向旧空间中下一个元素
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();                         // 数据移动完毕,释放旧空间
    
        // 更新指针,指向新空间开始、第一个空闲位置及末尾位置
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }
    
    void StrVec::resize(size_t count) {
        resize(count, string());
    }
    
    void StrVec::resize(size_t count, const string &s) {
        if (count > size()) {
            if (count > capacity())
                reserve(count * 2);
            for (size_t i = size(); i != count; ++i)
                alloc.construct(first_free++, s);
        }
        else if (count < size()) {
            while (first_free != elements + count)
                alloc.destroy(--first_free);
        }
    }
    
    StrVec::StrVec(initializer_list<string> il) {
        // 调用 alloc_n_copy 分配与列表 il 中元素数目一样多的空间
        auto newdata = alloc_n_copy(il.begin(), il.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    
    bool operator==(const StrVec &lhs, const StrVec &rhs) {
        if (lhs.size() != rhs.size())
            return false;
    
        for (auto iter1 = lhs.begin(), iter2 = rhs.begin();
            iter1 != lhs.end() && iter2 != rhs.end(); ++iter1, ++iter2) {
            if (*iter1 != *iter2)
                return false;
        }
    
        return true;
    }
    
    bool operator!=(const StrVec &lhs, const StrVec &rhs) {
        return !(lhs == rhs);
    }
    

    main.cpp

    #include "StrVec.h"
    #include <iostream>
    
    int main() {
        StrVec vec_list{"c++", "primer", "5th"};
        const StrVec con_vec_list{"c++", "primer", "5th"};
        if (vec_list == con_vec_list)
            for (const auto &str : con_vec_list)
                std::cout << str << " ";
        std::cout << std::endl;
    
        return 0;
    }
    
    // 运行结果
    c++ primer 5th 
    
    Process finished with exit code 0
    
  • String (适当修改 练习 13.44 即可)equal

    代码如下所示:

    String.h

    #ifndef TEST_STRING_H
    #define TEST_STRING_H
    
    #include <iostream>
    using std::ostream;
    
    #include <memory>
    using std::allocator;
    using std::uninitialized_copy;
    using std::uninitialized_fill_n;
    
    class String {
        friend String operator+(const String&, const String&);
        friend String add(const String&, const String&);
        friend ostream &operator<<(ostream&, const String&);
        friend ostream &print(ostream&, const String&);
        // 练习 14.16
        friend bool operator==(const String &lhs, const String &rhs);
        friend bool operator!=(const String &lhs, const String &rhs);
    
    public:
        String() : sz(0), p(0) { }
        // cp points to null terminated array, allocate new
        // memory & copy the array
        String(const char *cp) : sz(strlen(cp)), p(a.allocate(sz)) {
            uninitialized_copy(cp, cp + sz, p);
        }
        String(size_t n, char c) : sz(n), p(a.allocate(n)) {
            uninitialized_fill_n(p, sz, c);
        }
    
        // copy constructor: allocate a new copy of the characters in s
        String(const String &s) : sz(s.sz), p(a.allocate(s.sz)) {
            uninitialized_copy(s.p, s.p + sz, p);
        }
    
        // allocate a new copy of the data in the right-hand operand;
        // deletes the memory used by the left-hand operand
        String &operator=(const String&);
    
        // unconditionally delete the memory because each String has
        // its own memory
        ~String() { free(); }
    
    public:
        // additional assignment operators
        String &operator=(const char*);         // car = "Studebaker"
        String &operator=(char);                // model = 'T'
    
        const char *begin() { return p; }
        const char *begin() const { return p; }
        const char *end() { return p + sz; }
        const char *end() const { return p + sz; }
    
        size_t size() const { return sz; }
        void swap(String&);
    
    private:
        size_t sz;
        char *p;
        static allocator<char> a;
    
        void free();
    };
    
    String make_plural(size_t ctr, const String&, const String&);
    
    inline
    void swap(String &s1, String &s2) {
        s1.swap(s2);
    }
    
    #endif //TEST_STRING_H
    

    String.cpp

    #include "String.h"
    
    #include <algorithm>
    using std::for_each; using std::equal;
    
    // define the static allocator member
    allocator<char> String::a;
    
    void String::free() {
        if (p) {
            for_each(p, p + sz, [this] (char &c) { a.destroy(&c); });
            a.deallocate(p, sz);
        }
    }
    
    void String::swap(String &s) {
        char *tmp = p;
        p = s.p;
        s.p = tmp;
    
        size_t cnt = sz;
        sz = s.sz;
        s.sz = cnt;
    }
    
    // copy-assignment operator
    String& String::operator=(const String &rhs) {
        // copying the right-hand operand before deleting
        // the left handles self-assignment
        char *newp = a.allocate(rhs.sz);    // copy the underlying string from rhs
        uninitialized_copy(rhs.p, rhs.p + rhs.sz, newp);
        free();         // free the memory used by the left-hand operand
        p = newp;       // p now points to the newly allocated string
        sz = rhs.sz;    // update the size
        return *this;
    }
    
    String& String::operator=(const char *cp) {
        free();         // free the memory used by the left-hand operand
        p = a.allocate(sz = strlen(cp));
        uninitialized_copy(cp, cp + sz, p);
        return *this;
    }
    
    String& String::operator=(char c) {
        free();         // free the memory used by the left-hand operand
        p = a.allocate(sz = 1);
        *p = c;
        return *this;
    }
    
    // named functions for operators
    ostream &print(ostream &os, const String &s) {
        const char *p = s.begin();
        while (p != s.end())
            os << *p++;
        return os;
    }
    
    String add(const String &lhs, const String &rhs) {
        String ret;
        ret.sz = rhs.size() + lhs.size();       // size of the combined String
        ret.p = String::a.allocate(ret.sz);     // allocate new space
        uninitialized_copy(lhs.begin(), lhs.end(), ret.p);  // copy the operands
        uninitialized_copy(rhs.begin(), rhs.end(), ret.p + lhs.sz);
        return ret;
    }
    
    // return plural version of word if ctr isn't 1
    String make_plural(size_t ctr, const String &word, const String &ending) {
        return (ctr != 1) ? add(word, ending) : word;
    }
    
    // chapter 14 will explain overloaded operators
    ostream &operator<<(ostream &os, const String &s) {
        return print(os, s);
    }
    
    String operator+(const String &lhs, const String &rhs) {
        return add(lhs, rhs);
    }
    
    bool operator==(const String &lhs, const String &rhs) {
        return (lhs.size() == rhs.size() &&
            equal(lhs.begin(), lhs.end(), rhs.begin()));
    }
    
    bool operator!=(const String &lhs, const String &rhs) {
        return !(lhs == rhs);
    }
    

    main.cpp

    #include "String.h"
    
    using std::cout;
    using std::endl;
    
    int main() {
        String s1("One"), s2("Two");
        String s3(s2);
        if (s1 == s2)
            cout << "s1 == s2" << endl;
        else
            cout << "s1 != s2 " << endl;
        if (s3 == s2)
            cout << "s3 == s2" << endl;
        else
            cout << "s3 != s2 " << endl;
    
        return 0;
    }
    
    // 运行结果
    s1 != s2 
    s3 == s2
    
    Process finished with exit code 0
    

14.17

【出题思路】

本题练习判断类是否需要相等运算符及实现。

【解答】

见练习 14.5

14.18

【出题思路】

本题练习实现关系运算符。

在练习 14.16 增加关系运算符的声明和定义即可。

【解答】

本题的关键是明确关系运算符的语义。

  • StrBlob & StrBlobPtr 两个 StrBlob 的比较就是比较两个字符串 vector;两个 StrBlobPtr 的比较,本质上是比较两个指向 vector 内元素的指针(迭代器),因此,首先要求两个 StrBlobPtr 指向相同的 vector,否则没有可比性,然后比较指向的位置即可。

    代码如下所示:

    StrBlob.h

    #ifndef TEST_STRBLOB_H
    #define TEST_STRBLOB_H
    
    #include <vector>
    #include <string>
    #include <initializer_list>
    #include <memory>
    #include <stdexcept>
    
    using std::string;
    using std::vector;
    using std::initializer_list;
    using std::shared_ptr;
    using std::weak_ptr;
    using std::make_shared;
    using std::out_of_range;
    using std::runtime_error;
    
    // 对于 StrBlob 中的友元声明来说,此前置声明是必要的
    class StrBlobPtr;
    class StrBlob {
        friend class StrBlobPtr;
        // 练习 14.16
        friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
        friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
        // 练习 14.18
        friend bool operator<(const StrBlob &s1, const StrBlob &s2);
        friend bool operator<=(const StrBlob &s1, const StrBlob &s2);
        friend bool operator>(const StrBlob &s1, const StrBlob &s2);
        friend bool operator>=(const StrBlob &s1, const StrBlob &s2);
    
    public:
        typedef vector<string>::size_type size_type;
        StrBlob();
        StrBlob(initializer_list<string> il);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        // 添加和删除元素
        void push_back(const string &t) { data->push_back(t); }
        void pop_back();
        // 元素访问
        string& front();
        const string& front() const;
        string& back();
        const string& back() const;
    
        // 提供给 StrBlobPtr 的接口
        // 返回指向首元素和尾后元素的 StrBlobPtr
        StrBlobPtr begin();     // 定义 StrBlobPtr 后才能定义这两个函数
        StrBlobPtr end();
    
    private:
        shared_ptr<vector<string>> data;
        // 如果 data[i] 不合法,抛出一个异常
        void check(size_type i, const string &msg) const;
    };
    
    StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
    StrBlob::StrBlob(initializer_list <string> il) :
            data(make_shared<vector<string>>(il)) { }
    
    void StrBlob::check(vector<string>::size_type i, const string &msg) const {
        if (i >= data->size())
            throw out_of_range(msg);
    }
    
    string& StrBlob::front() {
        // 如果 vector 为空,check 会抛出一个异常
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    // const 版本 front
    const string& StrBlob::front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    string& StrBlob::back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    // const 版本 back
    const string& StrBlob::back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    void StrBlob::pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    bool operator==(const StrBlob &lhs, const StrBlob&rhs) {
        return lhs.data == rhs.data;            // 所指向的 vector 相等
    }
    
    bool operator!=(const StrBlob &lhs, const StrBlob&rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data < *s2.data;
    }
    
    bool operator<=(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data <= *s2.data;
    }
    
    bool operator>(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data > *s2.data;
    }
    
    bool operator>=(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data >= *s2.data;
    }
    
    class StrBlobPtr {
        // 练习 14.16
        friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
        friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
        // 练习 14.18
        friend bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
        friend bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
        friend bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
        friend bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);
    
    public:
        StrBlobPtr() : curr(0) {}
        StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    
        string& deref() const;
        StrBlobPtr& incr();     // 前缀递增
        StrBlobPtr& decr();     // 后缀递减
    private:
        // 若检查成功,check 返回一个指向 vector 的 shared_ptr
        shared_ptr<vector<string>> check(size_t, const string&) const;
        // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
        weak_ptr<vector<string>> wptr;
        size_t curr;            // 在数组中的当前位置
    };
    
    shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const {
        auto ret = wptr.lock(); // vector 还存在吗?
        if (!ret)
            throw runtime_error("unbound StrBlobPtr");
        if (i >= ret->size())
            throw out_of_range(msg);
        return ret;             // 否则,返回指向 vector 的 shared_ptr
    }
    
    string& StrBlobPtr::deref() const {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];      // (*P) 是对象所指向的 vector
    }
    
    // 前缀递增:返回递增后的对象的引用
    StrBlobPtr& StrBlobPtr::incr() {
        // 如果 curr 已经指向容器的尾后位置,就不能递增它
        check(curr, "increment past end of StrBlobPtr");
        ++curr;                 // 推进当前位置
        return *this;
    }
    
    // 前缀递减:返回递减后的对象的引用
    StrBlobPtr& StrBlobPtr::decr() {
        // 如果 curr 已经为 0,递减它会产生一个非法下标
        --curr;                 // 递减当前位置
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    // StrBlob 的 begin 和 end 成员的定义
    StrBlobPtr StrBlob::begin() {
        return StrBlobPtr(*this);
    }
    StrBlobPtr StrBlob::end() {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // StrBlobPtr 的比较操作
    bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        // 若底层的 vector 是同一个
        if (l == r)
            // 则两个指针都是空,或者指向相同元素时,它们相等
            return (!r || lhs.curr == rhs.curr);
        else
            return false;       // 若指向不同 vector,则不可能相等
    }
    
    bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        auto l = s1.wptr.lock(), r = s2.wptr.lock();
        if (l == r) {
            if (!r)
                return false;               // 两个指针都为空,认为是相等
            return (s1.curr < s2.curr);     // 指向相同 vector,比较指针位置
        } else {
            return false;                   // 指向不同 vector 时,不能比较
        }
    }
    
    bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        return s2 < s1;
    }
    
    bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        return !(s2 < s1);
    }
    
    bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        return !(s1 < s2);
    }
    
    #endif //TEST_STRBLOB_H
    

    main.cpp

    #include <iostream>
    #include "StrBlob.h"
    
    using namespace std;
    
    int main() {
        StrBlob b1;
        {
            StrBlob b2 = {"a", "an", "the"};
            b1 = b2;
            b2.push_back("about");
            cout << b2.size() << endl;
        }
        // b2 在花括号外失效,作用域仅限于花括号内
        // cout << b2.size() << endl;
        cout << b1.size() << endl;
        cout << b1.front() << " " << b1.back() << endl;
    
        const StrBlob b3 = b1;
        cout << b3.front() << " " << b3.back() << endl;
    
        // 针对练习 14.18 重载运算符 < 的调用
        for (auto iter = b1.begin(); iter < b1.end(); iter.incr())
            cout << iter.deref() << " ";
        cout << endl;
    
        return 0;
    }
    
    // 运行结果
    4
    4
    a about
    a about
    a an the about 
    
    Process finished with exit code 0
    
  • StrVec

    StrVec.h

    #ifndef TEST_STRVEC_H
    #define TEST_STRVEC_H
    
    #include <string>
    using std::string;
    
    #include <memory>
    using std::allocator;
    
    #include <utility>
    using std::pair;
    
    #include <initializer_list>
    using std::initializer_list;
    
    // 类 vector 类内存分配策略的简单实现
    class StrVec {
        // 练习 14.16
        friend bool operator==(const StrVec &lhs, const StrVec &rhs);
        friend bool operator!=(const StrVec &lhs, const StrVec &rhs);
        // 练习 14.18
        friend bool operator<(const StrVec &s1, const StrVec &s2);
        friend bool operator<=(const StrVec &s1, const StrVec &s2);
        friend bool operator>(const StrVec &s1, const StrVec &s2);
        friend bool operator>=(const StrVec &s1, const StrVec &s2);
    
    public:
        StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
        StrVec(const StrVec&);                  // 拷贝构造函数
        StrVec &operator=(const StrVec&);       // 拷贝赋值运算符
        ~StrVec();                              // 析构函数
        void push_back(const string&);          // 拷贝元素
        size_t size() const { return first_free - elements; }
        size_t capacity() const { return cap - elements; }
        string *begin() const { return elements; }
        string *end() const { return first_free; }
    
        // 新加的代码
        // reserve 预留一部分空间,需要一个辅助函数 void reallocate(size_t)
        void reserve(size_t n) { if (n > capacity()) reallocate(n); }
        void resize(size_t);
        void resize(size_t, const string &);
        // 练习 13.40 —— 新添加的一个构造函数
        StrVec(initializer_list<string>);
    
    private:
        // 由于静态成员需要类外初始化,所以我没有把 alloc 定义为 static
        // 因为我不熟悉 allocator<string> 初始化方法
        allocator<string> alloc;                // 分配元素
        // 被添加元素的函数所使用
        void chk_n_alloc() {
            if (size() == capacity())
                reallocate();
        }
        // 工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
        pair<string*, string*> alloc_n_copy(const string*, const string*);
        void free();                            // 销毁元素并释放内存
        void reallocate();                      // 获得更多内存并拷贝已有元素
        string *elements;                       // 指向数组首元素的指针
        string *first_free;                     // 指向数组第一个空闲元素的指针
        string *cap;                            // 指向数组尾后位置的指针
    
        // 新加的代码
        void reallocate(size_t);
    };
    
    #endif //TEST_STRVEC_H
    

    StrVec.cpp

    #include "StrVec.h"
    
    void StrVec::push_back(const string &s) {
        chk_n_alloc();          // 确保有空间容纳新元素
        // 在 first_free 指向的元素中构造 s 的副本
        alloc.construct(first_free++, s);
    }
    
    pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e) {
        // 分配空间保存给定范围中的元素
        auto data = alloc.allocate(e - b);
        // 初始化并返回一个 pair,该 pair 由 data 和 uninitialized_copy 的返回值构成
        return {data, uninitialized_copy(b, e, data)};
    }
    
    void StrVec::free() {
        // 不能传递给 deallocate 一个空指针,如果 elements 为 0,函数什么也不做
        if (elements) {
            // 逆序销毁旧元素
            for (auto p = first_free; p != elements; /* 空 */)
                alloc.destroy(--p);
            alloc.deallocate(elements, cap - elements);
        }
    }
    
    StrVec::StrVec(const StrVec &s) {
        // 调用 alloc_n_copy 分配空间以容纳与 s 中一样多的元素
        auto newdata = alloc_n_copy(s.begin(), s.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    
    StrVec::~StrVec() {
        free();
    }
    
    StrVec& StrVec::operator=(const StrVec &rhs) {
        // 调用 alloc_n_copy 分配内存,大小与 rhs 中元素占用空间一样多
        auto data = alloc_n_copy(rhs.begin(), rhs.end());
        free();
        elements = data.first;
        first_free = cap = data.second;
        return *this;
    }
    
    void StrVec::reallocate() {
        // 我们将分配当前大小两倍的内存空间
        auto newcapacity = size() ? 2 * size() : 1;
        // 分配新内存
        auto newdata = alloc.allocate(newcapacity);
        // 将数据从旧内存移动到新内存
        auto dest = newdata;            // 指向新数组中下一个空闲位置
        auto elem = elements;           // 指向旧数组中下一个元素
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();                         // 一旦我们移动完元素就释放旧内存空间
        // 更新我们的数据结构,执行新元素
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }
    
    // 新加的代码,StrVec::reallocate() 函数的重载形式
    void StrVec::reallocate(size_t newcapacity) {
        // 分配新内存
        auto newdata = alloc.allocate(newcapacity);
    
        // 将数据从旧空间移动到新空间
        auto dest = newdata;            // dest 指向新空间中第一个空闲位置
        auto elem = elements;           // 指向旧空间中下一个元素
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();                         // 数据移动完毕,释放旧空间
    
        // 更新指针,指向新空间开始、第一个空闲位置及末尾位置
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }
    
    void StrVec::resize(size_t count) {
        resize(count, string());
    }
    
    void StrVec::resize(size_t count, const string &s) {
        if (count > size()) {
            if (count > capacity())
                reserve(count * 2);
            for (size_t i = size(); i != count; ++i)
                alloc.construct(first_free++, s);
        }
        else if (count < size()) {
            while (first_free != elements + count)
                alloc.destroy(--first_free);
        }
    }
    
    StrVec::StrVec(initializer_list<string> il) {
        // 调用 alloc_n_copy 分配与列表 il 中元素数目一样多的空间
        auto newdata = alloc_n_copy(il.begin(), il.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    
    bool operator==(const StrVec &lhs, const StrVec &rhs) {
        if (lhs.size() != rhs.size())
            return false;
    
        for (auto iter1 = lhs.begin(), iter2 = rhs.begin();
            iter1 != lhs.end() && iter2 != rhs.end(); ++iter1, ++iter2) {
            if (*iter1 != *iter2)
                return false;
        }
    
        return true;
    }
    
    bool operator!=(const StrVec &lhs, const StrVec &rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const StrVec &s1, const StrVec &s2) {
        auto p1 = s1.begin(), p2 = s2.begin();
        for ( ; p1 != s1.end() && p2 != s2.end(); ++p1, ++p2) {
            if (*p1 < *p2)          // 之前的 string 都相等,当前 string 更小
                return true;
            else if (*p1 > *p2)     // 之前的 string 都相等,当前 string 更大
                return false;
        }
        // s1 中的所有 string 都与 s2 中的 string 相等,且 s1 更短
        if (p1 == s1.end() && p2 != s2.end())
            return true;
        return false;
    }
    
    bool operator>(const StrVec &s1, const StrVec &s2) {
        return s2 < s1;
    }
    
    bool operator<=(const StrVec &s1, const StrVec &s2) {
        return !(s2 < s1);
    }
    
    bool operator>=(const StrVec &s1, const StrVec &s2) {
        return !(s1 < s2);
    }
    

    main.cpp

    #include "StrVec.h"
    #include <iostream>
    
    int main() {
        StrVec vec_list{"c++", "primer", "5th", "lippman"};
        const StrVec con_vec_list_1{"c++", "primer", "5th"};
        const StrVec con_vec_list_2{"c++", "primer", "5th"};
        std::cout << (vec_list > con_vec_list_1) << std::endl;
        std::cout << (vec_list < con_vec_list_1) << std::endl;
        std::cout << (con_vec_list_2 < vec_list) << std::endl;
        std::cout << (con_vec_list_2 >= con_vec_list_1) << std::endl;
    
        return 0;
    }
    
    // 运行结果
    1
    0
    1
    1
    
    Process finished with exit code 0
    
  • String String 类的关系运算符就是比较两个字符串字典序的先后 std::lexicographical_compare

    代码如下所示:

    String.h

    #ifndef TEST_STRING_H
    #define TEST_STRING_H
    
    #include <iostream>
    using std::ostream;
    
    #include <memory>
    using std::allocator;
    using std::uninitialized_copy;
    using std::uninitialized_fill_n;
    
    class String {
        friend String operator+(const String&, const String&);
        friend String add(const String&, const String&);
        friend ostream &operator<<(ostream&, const String&);
        friend ostream &print(ostream&, const String&);
        // 练习 14.16
        friend bool operator==(const String &lhs, const String &rhs);
        friend bool operator!=(const String &lhs, const String &rhs);
        // 练习 14.18
        friend bool operator<(const String &s1, const String &s2);
        friend bool operator<=(const String &s1, const String &s2);
        friend bool operator>(const String &s1, const String &s2);
        friend bool operator>=(const String &s1, const String &s2);
    
    public:
        String() : sz(0), p(0) { }
        // cp points to null terminated array, allocate new
        // memory & copy the array
        String(const char *cp) : sz(strlen(cp)), p(a.allocate(sz)) {
            uninitialized_copy(cp, cp + sz, p);
        }
        String(size_t n, char c) : sz(n), p(a.allocate(n)) {
            uninitialized_fill_n(p, sz, c);
        }
    
        // copy constructor: allocate a new copy of the characters in s
        String(const String &s) : sz(s.sz), p(a.allocate(s.sz)) {
            uninitialized_copy(s.p, s.p + sz, p);
        }
    
        // allocate a new copy of the data in the right-hand operand;
        // deletes the memory used by the left-hand operand
        String &operator=(const String&);
    
        // unconditionally delete the memory because each String has
        // its own memory
        ~String() { free(); }
    
    public:
        // additional assignment operators
        String &operator=(const char*);         // car = "Studebaker"
        String &operator=(char);                // model = 'T'
    
        const char *begin() { return p; }
        const char *begin() const { return p; }
        const char *end() { return p + sz; }
        const char *end() const { return p + sz; }
    
        size_t size() const { return sz; }
        void swap(String&);
    
    private:
        size_t sz;
        char *p;
        static allocator<char> a;
    
        void free();
    };
    
    String make_plural(size_t ctr, const String&, const String&);
    
    inline
    void swap(String &s1, String &s2) {
        s1.swap(s2);
    }
    
    #endif //TEST_STRING_H
    

    String.cpp

    #include "String.h"
    
    #include <algorithm>
    using std::for_each; using std::equal;
    using std::lexicographical_compare;
    
    // define the static allocator member
    allocator<char> String::a;
    
    void String::free() {
        if (p) {
            for_each(p, p + sz, [this] (char &c) { a.destroy(&c); });
            a.deallocate(p, sz);
        }
    }
    
    void String::swap(String &s) {
        char *tmp = p;
        p = s.p;
        s.p = tmp;
    
        size_t cnt = sz;
        sz = s.sz;
        s.sz = cnt;
    }
    
    // copy-assignment operator
    String& String::operator=(const String &rhs) {
        // copying the right-hand operand before deleting
        // the left handles self-assignment
        char *newp = a.allocate(rhs.sz);    // copy the underlying string from rhs
        uninitialized_copy(rhs.p, rhs.p + rhs.sz, newp);
        free();         // free the memory used by the left-hand operand
        p = newp;       // p now points to the newly allocated string
        sz = rhs.sz;    // update the size
        return *this;
    }
    
    String& String::operator=(const char *cp) {
        free();         // free the memory used by the left-hand operand
        p = a.allocate(sz = strlen(cp));
        uninitialized_copy(cp, cp + sz, p);
        return *this;
    }
    
    String& String::operator=(char c) {
        free();         // free the memory used by the left-hand operand
        p = a.allocate(sz = 1);
        *p = c;
        return *this;
    }
    
    // named functions for operators
    ostream &print(ostream &os, const String &s) {
        const char *p = s.begin();
        while (p != s.end())
            os << *p++;
        return os;
    }
    
    String add(const String &lhs, const String &rhs) {
        String ret;
        ret.sz = rhs.size() + lhs.size();       // size of the combined String
        ret.p = String::a.allocate(ret.sz);     // allocate new space
        uninitialized_copy(lhs.begin(), lhs.end(), ret.p);  // copy the operands
        uninitialized_copy(rhs.begin(), rhs.end(), ret.p + lhs.sz);
        return ret;
    }
    
    // return plural version of word if ctr isn't 1
    String make_plural(size_t ctr, const String &word, const String &ending) {
        return (ctr != 1) ? add(word, ending) : word;
    }
    
    // chapter 14 will explain overloaded operators
    ostream &operator<<(ostream &os, const String &s) {
        return print(os, s);
    }
    
    String operator+(const String &lhs, const String &rhs) {
        return add(lhs, rhs);
    }
    
    bool operator==(const String &lhs, const String &rhs) {
        return (lhs.size() == rhs.size() &&
            equal(lhs.begin(), lhs.end(), rhs.begin()));
    }
    
    bool operator!=(const String &lhs, const String &rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const String &s1, const String &s2) {
        return lexicographical_compare(s1.begin(), s1.end(),
                s2.begin(), s2.end());
    }
    
    bool operator>(const String &s1, const String &s2) {
        return s2 < s1;
    }
    
    bool operator<=(const String &s1, const String &s2) {
        return !(s2 < s1);
    }
    
    bool operator>=(const String &s1, const String &s2) {
        return !(s1 < s2);
    }
    

    main.cpp

    #include "String.h"
    
    using std::cout;
    using std::endl;
    
    int main() {
        String s1("One"), s2("Oneone");
        String s3(s2);
        if (s1 < s2)
            cout << "s1 < s2" << endl;
        else
            cout << "s1 >= s2 " << endl;
        if (s3 >= s2)
            cout << "s3 >= s2" << endl;
        else
            cout << "s3 < s2 " << endl;
    
        return 0;
    }
    
    // 运行结果
    s1 < s2
    s3 >= s2
    
    Process finished with exit code 0
    

14.19

【出题思路】

本题练习实现关系运算符。

基于练习 14.15 增加关系运算符。

【解答】

我们可以为我们的 Book 类增加比较 ISBN(即 no_)的重载运算符 <>。因为 no_ 的类型为 string,string 标准库中已经实现了重载运算符 <>。我们正好可以利用,否则需要自己手动编写具体实现过程。

代码如下所示:

Book.h

#ifndef TEST_BOOK_H
#define TEST_BOOK_H

#include <iostream>
#include <string>

class Book {
    friend std::istream &operator>>(std::istream&, Book&);
    friend std::ostream &operator<<(std::ostream&, const Book&);
    friend bool operator==(const Book&, const Book&);
    friend bool operator!=(const Book&, const Book&);
    // 练习 14.19
    friend bool operator<(const Book&, const Book&);
    friend bool operator>(const Book&, const Book&);

public:
    Book() = default;
    Book(std::string no, std::string name, std::string author, std::string pubdate)
            : no_(no), name_(name), author_(author), pubdate_(pubdate) { }
    Book(std::istream &in) { in >> *this; }

private:
    std::string no_;
    std::string name_;
    std::string author_;
    std::string pubdate_;
};

std::istream &operator>>(std::istream&, Book&);
std::ostream &operator<<(std::ostream&, const Book&);
bool operator==(const Book&, const Book&);
bool operator!=(const Book&, const Book&);

#endif //TEST_BOOK_H

Book.cpp

#include "Book.h"

std::istream &operator>>(std::istream &in, Book &book) {
    in >> book.no_ >> book.name_ >> book.author_ >> book.pubdate_;
    if (!in)
        book = Book();
    return in;
}

std::ostream &operator<<(std::ostream &out, const Book &book) {
    out << book.no_ << " " << book.name_ << " " << book.author_
        << " " << book.pubdate_;
    return out;
}

bool operator==(const Book &lhs, const Book &rhs) {
    return lhs.no_ == rhs.no_;
}

bool operator!=(const Book &lhs, const Book &rhs) {
    return !(lhs == rhs);
}

bool operator<(const Book &lhs, const Book &rhs) {
    return lhs.no_ < rhs.no_;
}

bool operator>(const Book &lhs, const Book &rhs) {
    return rhs.no_ < lhs.no_;
}

main.cpp

#include "Book.h"

int main() {
    Book book1("978-7-121-15535-2", "CP5", "Lippman", "2012");
    Book book2("978-7-121-15535-2", "CP5", "Lippman", "2012");
    Book book3("978-7-121-15535-3", "CP5", "Lippman", "2012");

    std::cout << (book1 < book2) << std::endl;
    std::cout << (book3 > book2) << std::endl;

    return 0;
}
// 运行结果
0
1

Process finished with exit code 0

14.20

【出题思路】

本题练习实现重载运算符。

【解答】

Sales_data.h 和 Sales_data.cpp 同练习 14.2。主函数测试代码如下所示:

main.cpp

#include "Sales_data.h"

int main() {
    Sales_data b1("978-7-121-15535-2", 1, 97.9);
    Sales_data b2("978-7-121-15535-2", 1, 99.9);

    std::cout << b1 + b2 << std::endl;
    b1 += b2;
    std::cout << b1 << std::endl;
    std::cout << b1 + b2 << std::endl;

    return 0;
}
// 输入格式:isbn 数量 单价
// 输出格式:isbn 数量 总价 平均价格
// 运行结果
978-7-121-15535-2 2 197.8 98.9
978-7-121-15535-2 2 197.8 98.9
978-7-121-15535-2 3 297.7 99.2333

Process finished with exit code 0

14.21

【出题思路】

理解重载运算符的不同版本。

【解答】

基于练习 14.2 修改。代码如下所示:

Sales_data.h

#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H

#include <iostream>
#include <string>

// added overloaded input, output, addition, and compound-assignment operators
class Sales_data {
    friend std::istream &operator>>(std::istream&, Sales_data&);        // input
    friend std::ostream &operator<<(std::ostream&, const Sales_data&);  // output
    friend Sales_data operator+(const Sales_data&, const Sales_data&);  // addition

public:
    Sales_data(const std::string &s, unsigned n, double p)
            : bookNo(s), units_sold(n), revenue(n * p) { }
    Sales_data() : Sales_data("", 0, 0.0f) { }
    Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f) { }
    Sales_data(std::istream&);

    Sales_data &operator+=(const Sales_data&);          // compound-assignment
    std::string isbn() const { return bookNo; }

private:
    double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0;
};

std::istream &operator>>(std::istream&, Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
Sales_data operator+(const Sales_data&, const Sales_data&);

inline
double Sales_data::avg_price() const {
    return units_sold ? revenue / units_sold : 0;
}

#endif //TEST_SALES_DATA_H

Sales_data.cpp

#include "Sales_data.h"

Sales_data::Sales_data(std::istream &is) : Sales_data() {
    is >> *this;
}

Sales_data& Sales_data::operator+=(const Sales_data &rhs) {
    *this = (*this) + rhs;
    return *this;
}

std::istream &operator>>(std::istream &is, Sales_data &item) {
    double price = 0.0;
    is >> item.bookNo >> item.units_sold >> price;
    if (is)
        item.revenue = price * item.units_sold;
    else
        item = Sales_data();
    return is;
}

std::ostream &operator<<(std::ostream &os, const Sales_data &item) {
    os << item.isbn() << " " << item.units_sold << " " << item.revenue
       << " " << item.avg_price();
    return os;
}

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
    Sales_data sum = lhs;
    sum.units_sold += rhs.units_sold;       // 调用内置类型的符合赋值运算符
    sum.revenue += rhs.revenue;             // 调用内置类型的符合赋值运算符
    return sum;
}

main.cpp

#include "Sales_data.h"

int main() {
    Sales_data b1("978-7-121-15535-2", 1, 97.9);
    Sales_data b2("978-7-121-15535-2", 1, 99.9);

    b1 += b2;
    std::cout << b1 << std::endl;
    std::cout << b1 + b2 << std::endl;

    return 0;
}
// 输入格式:isbn 数量 单价
// 输出格式:isbn 数量 总价 平均价格
// 运行结果
978-7-121-15535-2 2 197.8 98.9
978-7-121-15535-2 2 197.8 98.9
978-7-121-15535-2 3 297.7 99.2333

Process finished with exit code 0

如练习 14.14 解答所述,本题的方式在性能上没有优势,可读性上也不好。

14.22

【出题思路】

本题练习实现赋值运算符。

【解答】

代码如下所示:

Sales_data.h

#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H

#include <iostream>
#include <string>

// added overloaded input, output, addition, and compound-assignment operators
class Sales_data {
    friend std::istream &operator>>(std::istream&, Sales_data&);        // input
    friend std::ostream &operator<<(std::ostream&, const Sales_data&);  // output
    friend Sales_data operator+(const Sales_data&, const Sales_data&);  // addition

public:
    Sales_data(const std::string &s, unsigned n, double p)
            : bookNo(s), units_sold(n), revenue(n * p) { }
    Sales_data() : Sales_data("", 0, 0.0f) { }
    Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f) { }
    Sales_data(std::istream&);

    Sales_data &operator+=(const Sales_data&);          // compound-assignment
    std::string isbn() const { return bookNo; }

    // exercise 14.22, assign string to Sales_data
    Sales_data &operator=(const std::string&);

private:
    double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0;
};

std::istream &operator>>(std::istream&, Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
Sales_data operator+(const Sales_data&, const Sales_data&);

inline
double Sales_data::avg_price() const {
    return units_sold ? revenue / units_sold : 0;
}

#endif //TEST_SALES_DATA_H

Sales_data.cpp

#include "Sales_data.h"

Sales_data::Sales_data(std::istream &is) : Sales_data() {
    is >> *this;
}

Sales_data& Sales_data::operator+=(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

std::istream &operator>>(std::istream &is, Sales_data &item) {
    double price = 0.0;
    is >> item.bookNo >> item.units_sold >> price;
    if (is)
        item.revenue = price * item.units_sold;
    else
        item = Sales_data();
    return is;
}

std::ostream &operator<<(std::ostream &os, const Sales_data &item) {
    os << item.isbn() << " " << item.units_sold << " " << item.revenue
       << " " << item.avg_price();
    return os;
}

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
    Sales_data sum = lhs;
    sum += rhs;
    return sum;
}

Sales_data& Sales_data::operator=(const std::string &isbn) {
    bookNo = isbn;
    return *this;
}

main.cpp

#include "Sales_data.h"

int main() {
    std::string s = "C++ Primer 5th";
    Sales_data b1("c++ primer", 10, 97.9);

    b1 = s;             // b1.operator=(s);
    std::cout << b1 << std::endl;

    return 0;
}
// 运行结果
C++ Primer 5th 10 979 97.9

Process finished with exit code 0

14.23

【出题思路】

本题练习实现赋值运算符。

基于练习 14.18 增加本题要求的运算符。

【解答】

StrVec.h

#ifndef TEST_STRVEC_H
#define TEST_STRVEC_H

#include <string>
using std::string;

#include <memory>
using std::allocator;

#include <utility>
using std::pair;

#include <initializer_list>
using std::initializer_list;

// 类 vector 类内存分配策略的简单实现
class StrVec {
    // 练习 14.16
    friend bool operator==(const StrVec &lhs, const StrVec &rhs);
    friend bool operator!=(const StrVec &lhs, const StrVec &rhs);
    // 练习 14.18
    friend bool operator<(const StrVec &s1, const StrVec &s2);
    friend bool operator<=(const StrVec &s1, const StrVec &s2);
    friend bool operator>(const StrVec &s1, const StrVec &s2);
    friend bool operator>=(const StrVec &s1, const StrVec &s2);

public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
    StrVec(const StrVec&);                  // 拷贝构造函数
    StrVec &operator=(const StrVec&);       // 拷贝赋值运算符
    ~StrVec();                              // 析构函数
    void push_back(const string&);          // 拷贝元素
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    string *begin() const { return elements; }
    string *end() const { return first_free; }

    // 新加的代码
    // reserve 预留一部分空间,需要一个辅助函数 void reallocate(size_t)
    void reserve(size_t n) { if (n > capacity()) reallocate(n); }
    void resize(size_t);
    void resize(size_t, const string &);
    // 练习 13.40 —— 新添加的一个构造函数
    StrVec(initializer_list<string>);
    // 练习 14.23
    StrVec &operator=(initializer_list<string>);

private:
    // 由于静态成员需要类外初始化,所以我没有把 alloc 定义为 static
    // 因为我不熟悉 allocator<string> 初始化方法
    allocator<string> alloc;                // 分配元素
    // 被添加元素的函数所使用
    void chk_n_alloc() {
        if (size() == capacity())
            reallocate();
    }
    // 工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
    pair<string*, string*> alloc_n_copy(const string*, const string*);
    void free();                            // 销毁元素并释放内存
    void reallocate();                      // 获得更多内存并拷贝已有元素
    string *elements;                       // 指向数组首元素的指针
    string *first_free;                     // 指向数组第一个空闲元素的指针
    string *cap;                            // 指向数组尾后位置的指针

    // 新加的代码
    void reallocate(size_t);
};

#endif //TEST_STRVEC_H

StrVec.cpp

#include "StrVec.h"

void StrVec::push_back(const string &s) {
    chk_n_alloc();          // 确保有空间容纳新元素
    // 在 first_free 指向的元素中构造 s 的副本
    alloc.construct(first_free++, s);
}

pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e) {
    // 分配空间保存给定范围中的元素
    auto data = alloc.allocate(e - b);
    // 初始化并返回一个 pair,该 pair 由 data 和 uninitialized_copy 的返回值构成
    return {data, uninitialized_copy(b, e, data)};
}

void StrVec::free() {
    // 不能传递给 deallocate 一个空指针,如果 elements 为 0,函数什么也不做
    if (elements) {
        // 逆序销毁旧元素
        for (auto p = first_free; p != elements; /* 空 */)
            alloc.destroy(--p);
        alloc.deallocate(elements, cap - elements);
    }
}

StrVec::StrVec(const StrVec &s) {
    // 调用 alloc_n_copy 分配空间以容纳与 s 中一样多的元素
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::~StrVec() {
    free();
}

StrVec& StrVec::operator=(const StrVec &rhs) {
    // 调用 alloc_n_copy 分配内存,大小与 rhs 中元素占用空间一样多
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

void StrVec::reallocate() {
    // 我们将分配当前大小两倍的内存空间
    auto newcapacity = size() ? 2 * size() : 1;
    // 分配新内存
    auto newdata = alloc.allocate(newcapacity);
    // 将数据从旧内存移动到新内存
    auto dest = newdata;            // 指向新数组中下一个空闲位置
    auto elem = elements;           // 指向旧数组中下一个元素
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
    free();                         // 一旦我们移动完元素就释放旧内存空间
    // 更新我们的数据结构,执行新元素
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

// 新加的代码,StrVec::reallocate() 函数的重载形式
void StrVec::reallocate(size_t newcapacity) {
    // 分配新内存
    auto newdata = alloc.allocate(newcapacity);

    // 将数据从旧空间移动到新空间
    auto dest = newdata;            // dest 指向新空间中第一个空闲位置
    auto elem = elements;           // 指向旧空间中下一个元素
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
    free();                         // 数据移动完毕,释放旧空间

    // 更新指针,指向新空间开始、第一个空闲位置及末尾位置
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

void StrVec::resize(size_t count) {
    resize(count, string());
}

void StrVec::resize(size_t count, const string &s) {
    if (count > size()) {
        if (count > capacity())
            reserve(count * 2);
        for (size_t i = size(); i != count; ++i)
            alloc.construct(first_free++, s);
    }
    else if (count < size()) {
        while (first_free != elements + count)
            alloc.destroy(--first_free);
    }
}

StrVec::StrVec(initializer_list<string> il) {
    // 调用 alloc_n_copy 分配与列表 il 中元素数目一样多的空间
    auto newdata = alloc_n_copy(il.begin(), il.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

bool operator==(const StrVec &lhs, const StrVec &rhs) {
    if (lhs.size() != rhs.size())
        return false;

    for (auto iter1 = lhs.begin(), iter2 = rhs.begin();
        iter1 != lhs.end() && iter2 != rhs.end(); ++iter1, ++iter2) {
        if (*iter1 != *iter2)
            return false;
    }

    return true;
}

bool operator!=(const StrVec &lhs, const StrVec &rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrVec &s1, const StrVec &s2) {
    auto p1 = s1.begin(), p2 = s2.begin();
    for ( ; p1 != s1.end() && p2 != s2.end(); ++p1, ++p2) {
        if (*p1 < *p2)          // 之前的 string 都相等,当前 string 更小
            return true;
        else if (*p1 > *p2)     // 之前的 string 都相等,当前 string 更大
            return false;
    }
    // s1 中的所有 string 都与 s2 中的 string 相等,且 s1 更短
    if (p1 == s1.end() && p2 != s2.end())
        return true;
    return false;
}

bool operator>(const StrVec &s1, const StrVec &s2) {
    return s2 < s1;
}

bool operator<=(const StrVec &s1, const StrVec &s2) {
    return !(s2 < s1);
}

bool operator>=(const StrVec &s1, const StrVec &s2) {
    return !(s1 < s2);
}

StrVec& StrVec::operator=(initializer_list<string> il) {
    // 调用 alloc_n_copy 分配内存,并从给定范围内拷贝元素
    auto data = alloc_n_copy(il.begin(), il.end());
    free();                     // 销毁对象中的元素并释放内存空间
    elements = data.first;      // 更新数据成员使其指向新空间
    first_free = cap = data.second;
    return *this;
}

用于测试的主函数:

版本一

#include "StrVec.h"
#include <iostream>

#include <vector>
using std::vector;

int main() {
    initializer_list<string> v;
    v = {"a", "an", "the"};
    // 创建 StrVec 对象 vec_list,默认初始化
    StrVec vec_list;	// 初始化语句,调用构造函数 StrVec()
    vec_list = v;			// 赋值语句,调用重载的赋值运算符函数 StrVec &operator=(initializer_list<string>)
    for (auto iter = vec_list.begin(); iter != vec_list.end(); ++iter)
        std::cout << *iter << " ";
    std::cout << std::endl;

    return 0;
}

版本二

#include "StrVec.h"
#include <iostream>

#include <vector>
using std::vector;

int main() {
    initializer_list<string> v;
    v = {"a", "an", "the"};
    // 创建 StrVec 对象 vec_list,并用 v 初始化
    StrVec vec_list = v;	// 初始化语句,调用构造函数 StrVec(initializer_list<string>)
    for (auto iter = vec_list.begin(); iter != vec_list.end(); ++iter)
        std::cout << *iter << " ";
    std::cout << std::endl;

    return 0;
}

版本一和版本二的输出结果相同:

// 运行结果
a an the 

Process finished with exit code 0

注:用于测试的主函数版本一符合该题的出题意图。

14.24

【出题思路】

理解拷贝和移动赋值运算符的作用。

关于重载运算符定义为类的成员,还是定义为类的非成员,可参考练习 14.4

【解答】

Date 类实现参考1

Date 类实现参考2

Date 类实现参考3

程序如下所示:

Date.h

#ifndef TEST_DATE_H
#define TEST_DATE_H

#include <iostream>

class Date {
private:
    int year_ = 1;          // 年
    int month_ = 1;         // 月
    int day_ = 1;           // 日
    int totalDays_;         // 改日期是从公元元年1月1日开始的第几天

public:
    Date() = default;
    // 用年、月、日构造日期,默认为公元元年1月1日
    Date(int year, int month, int day);
    ~Date() { std::cout << "destructor called" << std::endl; }
    int getYear() const { return year_; }
    int getMonth() const { return month_; }
    int getDay() const { return day_; }
    int getMaxDay() const;          // 获得当月有多少天
    bool isLeapYear() const {
        return year_ % 4 == 0 && year_ % 100 != 0 || year_ % 400 == 0;
    }
    // 计算两个日期之间差多少天
    int operator-(const Date &date) const {
        return totalDays_ - date.totalDays_;
    }
    // 判断两个日期的前后顺序
    bool operator<(const Date &date) const {
        return totalDays_ < date.totalDays_;
    }
    // 拷贝构造函数
    Date(const Date &date);
    // 拷贝赋值运算符
    Date &operator=(const Date &date);
    // 移动构造函数
    Date(Date &&date) noexcept;
    // 移动赋值运算符
    Date &operator=(Date &&rhs) noexcept;
};

std::istream &operator>>(std::istream &in, Date &date);
std::ostream &operator<<(std::ostream &out, const Date &date);

#endif //TEST_DATE_H

Date.cpp

#include "Date.h"

#include <stdexcept>
#include <sstream>

namespace  {
    // 存储平年中某个月1日之前有多少天,为便于 getMaxDay 函数的实现,该数组多出一项
    const int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181, 212,
                                     243, 273, 304, 334, 365};
}

// 用年、月、日构造日期,默认为公元元年1月1日
Date::Date(int year, int month, int day) : year_(year), month_(month), day_(day) {
    if (day_ <= 0 || day_ > getMaxDay())
        throw std::runtime_error("Invalid date");
    int years = year_ - 1;
    totalDays_ = years * 365 + years / 4 - years / 100 + years / 400
            + DAYS_BEFORE_MONTH[month_ - 1] + day_;
    if (isLeapYear() && month_ > 2)
        ++totalDays_;
}

int Date::getMaxDay() const {
    if (isLeapYear() && month_ == 2)
        return 29;
    else
        return DAYS_BEFORE_MONTH[month_] - DAYS_BEFORE_MONTH[month_ - 1];
}

std::istream &operator>>(std::istream &in, Date &date) {
    int year, month, day;
    char c1, c2;
    in >> year >> c1 >> month >> c2 >> day;
    if (c1 != '-' || c2 != '-')
        throw std::runtime_error("Bad time format");
    date = Date(year, month, day);
    return in;
}

std::ostream &operator<<(std::ostream &out, const Date &date) {
    out << date.getYear() << "-" << date.getMonth() << "-" << date.getDay();
    return out;
}

Date::Date(const Date &date)
    : year_(date.year_), month_(date.month_), day_(date.day_) {
    std::cout << "copy constructor" << std::endl;
}

Date& Date::operator=(const Date &date) {
    std::cout << "copy-assignment operator -- " << date << std::endl;


    day_ = date.day_;
    month_ = date.month_;
    year_ = date.year_;
    return *this;
}

Date::Date(Date &&date) noexcept
    : year_(date.year_), month_(date.month_), day_(date.day_) {
    std::cout << "move constructor" << std::endl;
}

Date& Date::operator=(Date &&rhs) noexcept {
    std::cout << "move-assignment operator -- " << rhs << std::endl;

    if (this != &rhs) {
        day_ = rhs.day_;
        month_ = rhs.month_;
        year_ = rhs.year_;
    }
    return *this;
}

main.cpp

#include "Date.h"

int main() {
    Date date1(2012, 12, 12);
    Date date2(2012, 12, 02);
    std::cout << (date1 - date2) << std::endl;      // 计算两个日期之间差多少天
    std::cout << (date1 < date2) << std::endl;      // 判断两个日期的前后顺序

    Date date3;
    date3 = date1;                  // 调用拷贝赋值运算符

    Date date4;
    date4 = std::move(date2);       // 调用移动赋值运算符


    return 0;
}
// 运行结果
10
0
copy-assignment operator -- 2012-12-12
move-assignment operator -- 2012-12-2
destructor called
destructor called
destructor called
destructor called

Process finished with exit code 0

14.25

【出题思路】

理解赋值运算符的作用。

【解答】

这完全取决于实际的需求,例如,如果你希望能用 string 形式的日期来初始化 Date,就需要定义一个接受 string 的赋值运算符。

代码如下所示:

Date.h

#ifndef TEST_DATE_H
#define TEST_DATE_H

#include <iostream>

class Date {
private:
    int year_ = 1;          // 年
    int month_ = 1;         // 月
    int day_ = 1;           // 日
    int totalDays_;         // 改日期是从公元元年1月1日开始的第几天

public:
    Date() = default;
    // 用年、月、日构造日期,默认为公元元年1月1日
    Date(int year, int month, int day);
    ~Date() { std::cout << "destructor called" << std::endl; }
    int getYear() const { return year_; }
    int getMonth() const { return month_; }
    int getDay() const { return day_; }
    int getMaxDay() const;          // 获得当月有多少天
    bool isLeapYear() const {
        return year_ % 4 == 0 && year_ % 100 != 0 || year_ % 400 == 0;
    }
    // 计算两个日期之间差多少天
    int operator-(const Date &date) const {
        return totalDays_ - date.totalDays_;
    }
    // 判断两个日期的前后顺序
    bool operator<(const Date &date) const {
        return totalDays_ < date.totalDays_;
    }
    // 拷贝构造函数
    Date(const Date &date);
    // 拷贝赋值运算符
    Date &operator=(const Date &date);
    // 移动构造函数
    Date(Date &&date) noexcept;
    // 移动赋值运算符
    Date &operator=(Date &&rhs) noexcept;
    // 练习 14.25,接受一个 string 的赋值运算符
    Date &operator=(const std::string &date);
};

std::istream &operator>>(std::istream &in, Date &date);
std::ostream &operator<<(std::ostream &out, const Date &date);

#endif //TEST_DATE_H

Date.cpp

#include "Date.h"

#include <stdexcept>
#include <sstream>

namespace  {
    // 存储平年中某个月1日之前有多少天,为便于 getMaxDay 函数的实现,该数组多出一项
    const int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181, 212,
                                     243, 273, 304, 334, 365};
}

// 用年、月、日构造日期,默认为公元元年1月1日
Date::Date(int year, int month, int day) : year_(year), month_(month), day_(day) {
    if (day_ <= 0 || day_ > getMaxDay())
        throw std::runtime_error("Invalid date");
    int years = year_ - 1;
    totalDays_ = years * 365 + years / 4 - years / 100 + years / 400
            + DAYS_BEFORE_MONTH[month_ - 1] + day_;
    if (isLeapYear() && month_ > 2)
        ++totalDays_;
}

int Date::getMaxDay() const {
    if (isLeapYear() && month_ == 2)
        return 29;
    else
        return DAYS_BEFORE_MONTH[month_] - DAYS_BEFORE_MONTH[month_ - 1];
}

std::istream &operator>>(std::istream &in, Date &date) {
    int year, month, day;
    char c1, c2;
    in >> year >> c1 >> month >> c2 >> day;
    if (c1 != '-' || c2 != '-')
        throw std::runtime_error("Bad time format");
    date = Date(year, month, day);
    return in;
}

std::ostream &operator<<(std::ostream &out, const Date &date) {
    out << date.getYear() << "-" << date.getMonth() << "-" << date.getDay();
    return out;
}

Date::Date(const Date &date)
    : year_(date.year_), month_(date.month_), day_(date.day_) {
    std::cout << "copy constructor" << std::endl;
}

Date& Date::operator=(const Date &date) {
    std::cout << "copy-assignment operator -- " << date << std::endl;


    day_ = date.day_;
    month_ = date.month_;
    year_ = date.year_;
    return *this;
}

Date::Date(Date &&date) noexcept
    : year_(date.year_), month_(date.month_), day_(date.day_) {
    std::cout << "move constructor" << std::endl;
}

Date& Date::operator=(Date &&rhs) noexcept {
    std::cout << "move-assignment operator -- " << rhs << std::endl;

    if (this != &rhs) {
        day_ = rhs.day_;
        month_ = rhs.month_;
        year_ = rhs.year_;
    }
    return *this;
}

Date& Date::operator=(const std::string &date) {
    std::cout <<"Date::operator=" << std::endl;
    // 接受 "1949-5-1" 格式的日期字符串
    std::istringstream in(date);
    char c1, c2;
    in >> year_ >> c1 >> month_ >> c2 >> day_;
    if (!in || c1 != '-' || c2 != '-')
        throw std::runtime_error("Bad time format");
    return *this;
}

main.cpp

#include "Date.h"

int main() {
    Date date1(2012, 12, 12);
    Date date2(2012, 12, 02);
    std::cout << (date1 - date2) << std::endl;      // 计算两个日期之间差多少天
    std::cout << (date1 < date2) << std::endl;      // 判断两个日期的前后顺序

    Date date3;
    date3 = date1;                  // 调用拷贝赋值运算符

    Date date4;
    date4 = std::move(date2);       // 调用移动赋值运算符

    Date date5;
    date5 = "2019-09-01";


    return 0;
}
// 运行结果
10
0
copy-assignment operator -- 2012-12-12
move-assignment operator -- 2012-12-2
Date::operator=
destructor called
destructor called
destructor called
destructor called
destructor called

Process finished with exit code 0

14.26

【出题思路】

本题练习实现下标运算符。

在练习 14.18 增加关系运算符的声明和定义即可。

void const f() vs void f() const

【解答】

本题的关键是明确关系运算符的语义。

  • StrBlob & StrBlobPtr

    代码如下所示:

    StrBlob.h

    #ifndef TEST_STRBLOB_H
    #define TEST_STRBLOB_H
    
    #include <vector>
    #include <string>
    #include <initializer_list>
    #include <memory>
    #include <stdexcept>
    
    using std::string;
    using std::vector;
    using std::initializer_list;
    using std::shared_ptr;
    using std::weak_ptr;
    using std::make_shared;
    using std::out_of_range;
    using std::runtime_error;
    
    // 对于 StrBlob 中的友元声明来说,此前置声明是必要的
    class StrBlobPtr;
    class StrBlob {
        friend class StrBlobPtr;
        // 练习 14.16
        friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
        friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
        // 练习 14.18
        friend bool operator<(const StrBlob &s1, const StrBlob &s2);
        friend bool operator<=(const StrBlob &s1, const StrBlob &s2);
        friend bool operator>(const StrBlob &s1, const StrBlob &s2);
        friend bool operator>=(const StrBlob &s1, const StrBlob &s2);
    
    public:
        typedef vector<string>::size_type size_type;
        StrBlob();
        StrBlob(initializer_list<string> il);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        // 添加和删除元素
        void push_back(const string &t) { data->push_back(t); }
        void pop_back();
        // 元素访问
        string& front();
        const string& front() const;
        string& back();
        const string& back() const;
    
        // 提供给 StrBlobPtr 的接口
        // 返回指向首元素和尾后元素的 StrBlobPtr
        StrBlobPtr begin();     // 定义 StrBlobPtr 后才能定义这两个函数
        StrBlobPtr end();
        // 练习 14.26
        string &operator[](size_t);
        const string &operator[](size_t) const;
    
    
    private:
        shared_ptr<vector<string>> data;
        // 如果 data[i] 不合法,抛出一个异常
        void check(size_type i, const string &msg) const;
    };
    
    StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
    StrBlob::StrBlob(initializer_list <string> il) :
            data(make_shared<vector<string>>(il)) { }
    
    void StrBlob::check(vector<string>::size_type i, const string &msg) const {
        if (i >= data->size())
            throw out_of_range(msg);
    }
    
    string& StrBlob::front() {
        // 如果 vector 为空,check 会抛出一个异常
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    // const 版本 front
    const string& StrBlob::front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    string& StrBlob::back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    // const 版本 back
    const string& StrBlob::back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    void StrBlob::pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    bool operator==(const StrBlob &lhs, const StrBlob&rhs) {
        return lhs.data == rhs.data;            // 所指向的 vector 相等
    }
    
    bool operator!=(const StrBlob &lhs, const StrBlob&rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data < *s2.data;
    }
    
    bool operator<=(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data <= *s2.data;
    }
    
    bool operator>(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data > *s2.data;
    }
    
    bool operator>=(const StrBlob &s1, const StrBlob &s2) {
        return *s1.data >= *s2.data;
    }
    
    string& StrBlob::operator[](size_t n) {
        check(n, "out of range");
        return data->at(n);
    }
    
    const string& StrBlob::operator[](size_t n) const {
        check(n, "out of range");
        return data->at(n);
    }
    
    class StrBlobPtr {
        // 练习 14.16
        friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
        friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
        // 练习 14.18
        friend bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
        friend bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
        friend bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
        friend bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);
    
    public:
        StrBlobPtr() : curr(0) {}
        StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    
        string& deref() const;
        StrBlobPtr& incr();     // 前缀递增
        StrBlobPtr& decr();     // 后缀递减
        // 练习 14.26
        const string &operator[](size_t) const;
    
    private:
        // 若检查成功,check 返回一个指向 vector 的 shared_ptr
        shared_ptr<vector<string>> check(size_t, const string&) const;
        // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
        weak_ptr<vector<string>> wptr;
        size_t curr;            // 在数组中的当前位置
    };
    
    shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const {
        auto ret = wptr.lock(); // vector 还存在吗?
        if (!ret)
            throw runtime_error("unbound StrBlobPtr");
        if (i >= ret->size())
            throw out_of_range(msg);
        return ret;             // 否则,返回指向 vector 的 shared_ptr
    }
    
    string& StrBlobPtr::deref() const {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];      // (*P) 是对象所指向的 vector
    }
    
    // 前缀递增:返回递增后的对象的引用
    StrBlobPtr& StrBlobPtr::incr() {
        // 如果 curr 已经指向容器的尾后位置,就不能递增它
        check(curr, "increment past end of StrBlobPtr");
        ++curr;                 // 推进当前位置
        return *this;
    }
    
    // 前缀递减:返回递减后的对象的引用
    StrBlobPtr& StrBlobPtr::decr() {
        // 如果 curr 已经为 0,递减它会产生一个非法下标
        --curr;                 // 递减当前位置
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    // StrBlob 的 begin 和 end 成员的定义
    StrBlobPtr StrBlob::begin() {
        return StrBlobPtr(*this);
    }
    StrBlobPtr StrBlob::end() {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    // StrBlobPtr 的比较操作
    bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        // 若底层的 vector 是同一个
        if (l == r)
            // 则两个指针都是空,或者指向相同元素时,它们相等
            return (!r || lhs.curr == rhs.curr);
        else
            return false;       // 若指向不同 vector,则不可能相等
    }
    
    bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        auto l = s1.wptr.lock(), r = s2.wptr.lock();
        if (l == r) {
            if (!r)
                return false;               // 两个指针都为空,认为是相等
            return (s1.curr < s2.curr);     // 指向相同 vector,比较指针位置
        } else {
            return false;                   // 指向不同 vector 时,不能比较
        }
    }
    
    bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        return s2 < s1;
    }
    
    bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        return !(s2 < s1);
    }
    
    bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
        return !(s1 < s2);
    }
    
    const string& StrBlobPtr::operator[](size_t n) const {
        auto p = check(n, "dereference out of range");
        return (*p)[n];
    }
    
    #endif //TEST_STRBLOB_H
    

    main.cpp

    #include <iostream>
    #include "StrBlob.h"
    
    using namespace std;
    
    int main() {
        StrBlob b1;
        {
            StrBlob b2 = {"a", "an", "the"};
            b1 = b2;
            b2.push_back("about");
            cout << b2.size() << endl;
        }
        // b2 在花括号外失效,作用域仅限于花括号内
        // cout << b2.size() << endl;
        cout << b1.size() << endl;
        cout << b1.front() << " " << b1.back() << endl;
    
        const StrBlob b3 = b1;
        cout << b3.front() << " " << b3.back() << endl;
    
        // 针对练习 14.18 重载运算符 < 的调用
        for (auto iter = b1.begin(); iter < b1.end(); iter.incr())
            cout << iter.deref() << " ";
        cout << endl;
    
        // 针对练习 14.26 重载下标运算符的调用
        StrBlob iter(b1);
        iter[2] = "an";
        for (auto iter = b1.begin(); iter != b1.end(); iter.incr())
            cout << iter.deref() << " ";
        cout << endl;
    
        return 0;
    }
    
    // 运行结果
    4
    4
    a about
    a about
    a an the about 
    a an an about 
    
    Process finished with exit code 0
    
  • StrVec

    StrVec.h

    #ifndef TEST_STRVEC_H
    #define TEST_STRVEC_H
    
    #include <string>
    using std::string;
    
    #include <memory>
    using std::allocator;
    
    #include <utility>
    using std::pair;
    
    #include <initializer_list>
    using std::initializer_list;
    
    // 类 vector 类内存分配策略的简单实现
    class StrVec {
        // 练习 14.16
        friend bool operator==(const StrVec &lhs, const StrVec &rhs);
        friend bool operator!=(const StrVec &lhs, const StrVec &rhs);
        // 练习 14.18
        friend bool operator<(const StrVec &s1, const StrVec &s2);
        friend bool operator<=(const StrVec &s1, const StrVec &s2);
        friend bool operator>(const StrVec &s1, const StrVec &s2);
        friend bool operator>=(const StrVec &s1, const StrVec &s2);
    
    public:
        StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
        StrVec(const StrVec&);                  // 拷贝构造函数
        StrVec &operator=(const StrVec&);       // 拷贝赋值运算符
        ~StrVec();                              // 析构函数
        void push_back(const string&);          // 拷贝元素
        size_t size() const { return first_free - elements; }
        size_t capacity() const { return cap - elements; }
        string *begin() const { return elements; }
        string *end() const { return first_free; }
    
        // 新加的代码
        // reserve 预留一部分空间,需要一个辅助函数 void reallocate(size_t)
        void reserve(size_t n) { if (n > capacity()) reallocate(n); }
        void resize(size_t);
        void resize(size_t, const string &);
        // 练习 13.40 —— 新添加的一个构造函数
        StrVec(initializer_list<string>);
        // 练习 14.26
        string &operator[](size_t n) { return elements[n]; }
        const string &operator[](size_t n) const { return elements[n]; }
    
    private:
        // 由于静态成员需要类外初始化,所以我没有把 alloc 定义为 static
        // 因为我不熟悉 allocator<string> 初始化方法
        allocator<string> alloc;                // 分配元素
        // 被添加元素的函数所使用
        void chk_n_alloc() {
            if (size() == capacity())
                reallocate();
        }
        // 工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
        pair<string*, string*> alloc_n_copy(const string*, const string*);
        void free();                            // 销毁元素并释放内存
        void reallocate();                      // 获得更多内存并拷贝已有元素
        string *elements;                       // 指向数组首元素的指针
        string *first_free;                     // 指向数组第一个空闲元素的指针
        string *cap;                            // 指向数组尾后位置的指针
    
        // 新加的代码
        void reallocate(size_t);
    };
    
    #endif //TEST_STRVEC_H
    

    StrVec.cpp

    #include "StrVec.h"
    
    void StrVec::push_back(const string &s) {
        chk_n_alloc();          // 确保有空间容纳新元素
        // 在 first_free 指向的元素中构造 s 的副本
        alloc.construct(first_free++, s);
    }
    
    pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e) {
        // 分配空间保存给定范围中的元素
        auto data = alloc.allocate(e - b);
        // 初始化并返回一个 pair,该 pair 由 data 和 uninitialized_copy 的返回值构成
        return {data, uninitialized_copy(b, e, data)};
    }
    
    void StrVec::free() {
        // 不能传递给 deallocate 一个空指针,如果 elements 为 0,函数什么也不做
        if (elements) {
            // 逆序销毁旧元素
            for (auto p = first_free; p != elements; /* 空 */)
                alloc.destroy(--p);
            alloc.deallocate(elements, cap - elements);
        }
    }
    
    StrVec::StrVec(const StrVec &s) {
        // 调用 alloc_n_copy 分配空间以容纳与 s 中一样多的元素
        auto newdata = alloc_n_copy(s.begin(), s.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    
    StrVec::~StrVec() {
        free();
    }
    
    StrVec& StrVec::operator=(const StrVec &rhs) {
        // 调用 alloc_n_copy 分配内存,大小与 rhs 中元素占用空间一样多
        auto data = alloc_n_copy(rhs.begin(), rhs.end());
        free();
        elements = data.first;
        first_free = cap = data.second;
        return *this;
    }
    
    void StrVec::reallocate() {
        // 我们将分配当前大小两倍的内存空间
        auto newcapacity = size() ? 2 * size() : 1;
        // 分配新内存
        auto newdata = alloc.allocate(newcapacity);
        // 将数据从旧内存移动到新内存
        auto dest = newdata;            // 指向新数组中下一个空闲位置
        auto elem = elements;           // 指向旧数组中下一个元素
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();                         // 一旦我们移动完元素就释放旧内存空间
        // 更新我们的数据结构,执行新元素
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }
    
    // 新加的代码,StrVec::reallocate() 函数的重载形式
    void StrVec::reallocate(size_t newcapacity) {
        // 分配新内存
        auto newdata = alloc.allocate(newcapacity);
    
        // 将数据从旧空间移动到新空间
        auto dest = newdata;            // dest 指向新空间中第一个空闲位置
        auto elem = elements;           // 指向旧空间中下一个元素
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();                         // 数据移动完毕,释放旧空间
    
        // 更新指针,指向新空间开始、第一个空闲位置及末尾位置
        elements = newdata;
        first_free = dest;
        cap = elements + newcapacity;
    }
    
    void StrVec::resize(size_t count) {
        resize(count, string());
    }
    
    void StrVec::resize(size_t count, const string &s) {
        if (count > size()) {
            if (count > capacity())
                reserve(count * 2);
            for (size_t i = size(); i != count; ++i)
                alloc.construct(first_free++, s);
        }
        else if (count < size()) {
            while (first_free != elements + count)
                alloc.destroy(--first_free);
        }
    }
    
    StrVec::StrVec(initializer_list<string> il) {
        // 调用 alloc_n_copy 分配与列表 il 中元素数目一样多的空间
        auto newdata = alloc_n_copy(il.begin(), il.end());
        elements = newdata.first;
        first_free = cap = newdata.second;
    }
    
    bool operator==(const StrVec &lhs, const StrVec &rhs) {
        if (lhs.size() != rhs.size())
            return false;
    
        for (auto iter1 = lhs.begin(), iter2 = rhs.begin();
             iter1 != lhs.end() && iter2 != rhs.end(); ++iter1, ++iter2) {
            if (*iter1 != *iter2)
                return false;
        }
    
        return true;
    }
    
    bool operator!=(const StrVec &lhs, const StrVec &rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const StrVec &s1, const StrVec &s2) {
        auto p1 = s1.begin(), p2 = s2.begin();
        for ( ; p1 != s1.end() && p2 != s2.end(); ++p1, ++p2) {
            if (*p1 < *p2)          // 之前的 string 都相等,当前 string 更小
                return true;
            else if (*p1 > *p2)     // 之前的 string 都相等,当前 string 更大
                return false;
        }
        // s1 中的所有 string 都与 s2 中的 string 相等,且 s1 更短
        if (p1 == s1.end() && p2 != s2.end())
            return true;
        return false;
    }
    
    bool operator>(const StrVec &s1, const StrVec &s2) {
        return s2 < s1;
    }
    
    bool operator<=(const StrVec &s1, const StrVec &s2) {
        return !(s2 < s1);
    }
    
    bool operator>=(const StrVec &s1, const StrVec &s2) {
        return !(s1 < s2);
    }
    

    main.cpp

    #include "StrVec.h"
    #include <iostream>
    
    int main() {
        StrVec vec_list{"c++", "primer", "5th", "lippman"};
        const StrVec con_vec_list_1{"c++", "primer", "5th"};
        const StrVec con_vec_list_2{"c++", "primer", "5th"};
        std::cout << (vec_list > con_vec_list_1) << std::endl;
        std::cout << (vec_list < con_vec_list_1) << std::endl;
        std::cout << (con_vec_list_2 < vec_list) << std::endl;
        std::cout << (con_vec_list_2 >= con_vec_list_1) << std::endl;
    
        // test []
        std::cout << con_vec_list_1[0] << std::endl;
    
        return 0;
    }
    
    // 运行结果
    1
    0
    1
    1
    c++
    
    Process finished with exit code 0
    
  • String

    代码如下所示:

    String.h

    #ifndef TEST_STRING_H
    #define TEST_STRING_H
    
    #include <iostream>
    using std::ostream;
    
    #include <memory>
    using std::allocator;
    using std::uninitialized_copy;
    using std::uninitialized_fill_n;
    
    class String {
        friend String operator+(const String&, const String&);
        friend String add(const String&, const String&);
        friend ostream &operator<<(ostream&, const String&);
        friend ostream &print(ostream&, const String&);
        // 练习 14.16
        friend bool operator==(const String &lhs, const String &rhs);
        friend bool operator!=(const String &lhs, const String &rhs);
        // 练习 14.18
        friend bool operator<(const String &s1, const String &s2);
        friend bool operator<=(const String &s1, const String &s2);
        friend bool operator>(const String &s1, const String &s2);
        friend bool operator>=(const String &s1, const String &s2);
    
    public:
        String() : sz(0), p(0) { }
        // cp points to null terminated array, allocate new
        // memory & copy the array
        String(const char *cp) : sz(strlen(cp)), p(a.allocate(sz)) {
            uninitialized_copy(cp, cp + sz, p);
        }
        String(size_t n, char c) : sz(n), p(a.allocate(n)) {
            uninitialized_fill_n(p, sz, c);
        }
    
        // copy constructor: allocate a new copy of the characters in s
        String(const String &s) : sz(s.sz), p(a.allocate(s.sz)) {
            uninitialized_copy(s.p, s.p + sz, p);
        }
    
        // allocate a new copy of the data in the right-hand operand;
        // deletes the memory used by the left-hand operand
        String &operator=(const String&);
    
        // unconditionally delete the memory because each String has
        // its own memory
        ~String() { free(); }
    
    public:
        // additional assignment operators
        String &operator=(const char*);         // car = "Studebaker"
        String &operator=(char);                // model = 'T'
    
        const char *begin() { return p; }
        const char *begin() const { return p; }
        const char *end() { return p + sz; }
        const char *end() const { return p + sz; }
    
        size_t size() const { return sz; }
        void swap(String&);
    
        // 练习 14.26
        char &operator[](size_t n) { return p[n]; }
        const char &operator[](size_t n) const { return p[n]; }
    
    private:
        size_t sz;
        char *p;
        static allocator<char> a;
    
        void free();
    };
    
    String make_plural(size_t ctr, const String&, const String&);
    
    inline
    void swap(String &s1, String &s2) {
        s1.swap(s2);
    }
    #endif //TEST_STRING_H
    
    

    String.cpp

    #include "String.h"
    
    #include <algorithm>
    using std::for_each; using std::equal;
    using std::lexicographical_compare;
    
    // define the static allocator member
    allocator<char> String::a;
    
    void String::free() {
        if (p) {
            for_each(p, p + sz, [this] (char &c) { a.destroy(&c); });
            a.deallocate(p, sz);
        }
    }
    
    void String::swap(String &s) {
        char *tmp = p;
        p = s.p;
        s.p = tmp;
    
        size_t cnt = sz;
        sz = s.sz;
        s.sz = cnt;
    }
    
    // copy-assignment operator
    String& String::operator=(const String &rhs) {
        // copying the right-hand operand before deleting
        // the left handles self-assignment
        char *newp = a.allocate(rhs.sz);    // copy the underlying string from rhs
        uninitialized_copy(rhs.p, rhs.p + rhs.sz, newp);
        free();         // free the memory used by the left-hand operand
        p = newp;       // p now points to the newly allocated string
        sz = rhs.sz;    // update the size
        return *this;
    }
    
    String& String::operator=(const char *cp) {
        free();         // free the memory used by the left-hand operand
        p = a.allocate(sz = strlen(cp));
        uninitialized_copy(cp, cp + sz, p);
        return *this;
    }
    
    String& String::operator=(char c) {
        free();         // free the memory used by the left-hand operand
        p = a.allocate(sz = 1);
        *p = c;
        return *this;
    }
    
    // named functions for operators
    ostream &print(ostream &os, const String &s) {
        const char *p = s.begin();
        while (p != s.end())
            os << *p++;
        return os;
    }
    
    String add(const String &lhs, const String &rhs) {
        String ret;
        ret.sz = rhs.size() + lhs.size();       // size of the combined String
        ret.p = String::a.allocate(ret.sz);     // allocate new space
        uninitialized_copy(lhs.begin(), lhs.end(), ret.p);  // copy the operands
        uninitialized_copy(rhs.begin(), rhs.end(), ret.p + lhs.sz);
        return ret;
    }
    
    // return plural version of word if ctr isn't 1
    String make_plural(size_t ctr, const String &word, const String &ending) {
        return (ctr != 1) ? add(word, ending) : word;
    }
    
    // chapter 14 will explain overloaded operators
    ostream &operator<<(ostream &os, const String &s) {
        return print(os, s);
    }
    
    String operator+(const String &lhs, const String &rhs) {
        return add(lhs, rhs);
    }
    
    bool operator==(const String &lhs, const String &rhs) {
        return (lhs.size() == rhs.size() &&
                equal(lhs.begin(), lhs.end(), rhs.begin()));
    }
    
    bool operator!=(const String &lhs, const String &rhs) {
        return !(lhs == rhs);
    }
    
    bool operator<(const String &s1, const String &s2) {
        return lexicographical_compare(s1.begin(), s1.end(),
                                       s2.begin(), s2.end());
    }
    
    bool operator>(const String &s1, const String &s2) {
        return s2 < s1;
    }
    
    bool operator<=(const String &s1, const String &s2) {
        return !(s2 < s1);
    }
    
    bool operator>=(const String &s1, const String &s2) {
        return !(s1 < s2);
    }
    
    

    main.cpp

    #include "String.h"
    
    using std::cout;
    using std::endl;
    
    int main() {
        String s1("One"), s2("Oneone");
        String s3(s2);
        if (s1 < s2)
            cout << "s1 < s2" << endl;
        else
            cout << "s1 >= s2 " << endl;
        if (s3 >= s2)
            cout << "s3 >= s2" << endl;
        else
            cout << "s3 < s2 " << endl;
    
        // test []
        cout << s2[3] << endl;
    
        return 0;
    }
    
    // 运行结果
    s1 < s2
    s3 >= s2
    o
    
    Process finished with exit code 0
    

14.27

【出题思路】

本题练习实现递增和递减运算符。

继续扩展练习 14.26

【解答】

代码如下所示:

StrBlob.h

#ifndef TEST_STRBLOB_H
#define TEST_STRBLOB_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using std::string;
using std::vector;
using std::initializer_list;
using std::shared_ptr;
using std::weak_ptr;
using std::make_shared;
using std::out_of_range;
using std::runtime_error;

// 对于 StrBlob 中的友元声明来说,此前置声明是必要的
class StrBlobPtr;
class StrBlob {
    friend class StrBlobPtr;
    // 练习 14.16
    friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
    friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
    // 练习 14.18
    friend bool operator<(const StrBlob &s1, const StrBlob &s2);
    friend bool operator<=(const StrBlob &s1, const StrBlob &s2);
    friend bool operator>(const StrBlob &s1, const StrBlob &s2);
    friend bool operator>=(const StrBlob &s1, const StrBlob &s2);

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    // 添加和删除元素
    void push_back(const string &t) { data->push_back(t); }
    void pop_back();
    // 元素访问
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;

    // 提供给 StrBlobPtr 的接口
    // 返回指向首元素和尾后元素的 StrBlobPtr
    StrBlobPtr begin();     // 定义 StrBlobPtr 后才能定义这两个函数
    StrBlobPtr end();
    // 练习 14.26
    string &operator[](size_t);
    const string &operator[](size_t) const;

private:
    shared_ptr<vector<string>> data;
    // 如果 data[i] 不合法,抛出一个异常
    void check(size_type i, const string &msg) const;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list <string> il) :
        data(make_shared<vector<string>>(il)) { }

void StrBlob::check(vector<string>::size_type i, const string &msg) const {
    if (i >= data->size())
        throw out_of_range(msg);
}

string& StrBlob::front() {
    // 如果 vector 为空,check 会抛出一个异常
    check(0, "front on empty StrBlob");
    return data->front();
}

// const 版本 front
const string& StrBlob::front() const {
    check(0, "front on empty StrBlob");
    return data->front();
}

string& StrBlob::back() {
    check(0, "back on empty StrBlob");
    return data->back();
}

// const 版本 back
const string& StrBlob::back() const {
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back() {
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

bool operator==(const StrBlob &lhs, const StrBlob&rhs) {
    return lhs.data == rhs.data;            // 所指向的 vector 相等
}

bool operator!=(const StrBlob &lhs, const StrBlob&rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data < *s2.data;
}

bool operator<=(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data <= *s2.data;
}

bool operator>(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data > *s2.data;
}

bool operator>=(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data >= *s2.data;
}

string& StrBlob::operator[](size_t n) {
    check(n, "out of range");
    return data->at(n);
}

const string& StrBlob::operator[](size_t n) const {
    check(n, "out of range");
    return data->at(n);
}

class StrBlobPtr {
    // 练习 14.16
    friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    // 练习 14.18
    friend bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);

public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    string& deref() const;
    StrBlobPtr& incr();     // 前缀递增
    StrBlobPtr& decr();     // 后缀递减
    // 练习 14.26
    const string &operator[](size_t) const;
    // 练习 14.27
    StrBlobPtr &operator++();       // 前置运算符
    StrBlobPtr &operator--();
    StrBlobPtr &operator++(int);    // 后置运算符
    StrBlobPtr &operator--(int);

private:
    // 若检查成功,check 返回一个指向 vector 的 shared_ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;
    // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;            // 在数组中的当前位置
};

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const {
    auto ret = wptr.lock(); // vector 还存在吗?
    if (!ret)
        throw runtime_error("unbound StrBlobPtr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;             // 否则,返回指向 vector 的 shared_ptr
}

string& StrBlobPtr::deref() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];      // (*P) 是对象所指向的 vector
}

// 前缀递增:返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr() {
    // 如果 curr 已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr;                 // 推进当前位置
    return *this;
}

// 前缀递减:返回递减后的对象的引用
StrBlobPtr& StrBlobPtr::decr() {
    // 如果 curr 已经为 0,递减它会产生一个非法下标
    --curr;                 // 递减当前位置
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

// StrBlob 的 begin 和 end 成员的定义
StrBlobPtr StrBlob::begin() {
    return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end() {
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

// StrBlobPtr 的比较操作
bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    // 若底层的 vector 是同一个
    if (l == r)
        // 则两个指针都是空,或者指向相同元素时,它们相等
        return (!r || lhs.curr == rhs.curr);
    else
        return false;       // 若指向不同 vector,则不可能相等
}

bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    auto l = s1.wptr.lock(), r = s2.wptr.lock();
    if (l == r) {
        if (!r)
            return false;               // 两个指针都为空,认为是相等
        return (s1.curr < s2.curr);     // 指向相同 vector,比较指针位置
    } else {
        return false;                   // 指向不同 vector 时,不能比较
    }
}

bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return s2 < s1;
}

bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return !(s2 < s1);
}

bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return !(s1 < s2);
}

const string& StrBlobPtr::operator[](size_t n) const {
    auto p = check(n, "dereference out of range");
    return (*p)[n];
}

// 前置版本:返回递增/递减对象的引用
StrBlobPtr& StrBlobPtr::operator++() {
    // 如果 curr 已经指向了容器的尾后位置,则无法递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr;         // 将 curr 在当前状态下向前移动一个元素
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--() {
    // 如果 curr 是 0,则继续递减它将产生一个无效下标
    --curr;         // 将 curr 在当前状态下后前移动一个元素
    check(curr, "decrement past begin of StrBlobPtr");
    return *this;
}

// 后置版本:递增/递减对象的值但是返回原值
StrBlobPtr& StrBlobPtr::operator++(int) {
    // 此处无需检查有效性,调用前置递增运算时才需要检查
    StrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向前移动一个元素,前置++需要检查递增的有效性
    return ret;     // 返回之前记录的状态
}
StrBlobPtr& StrBlobPtr::operator--(int) {
    // 此处无需检查有效性,调用前置递减运算时才需要检查
    StrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向后移动一个元素,前置--需要检查递减的有效性
    return ret;     // 返回之前记录的状态
}

#endif //TEST_STRBLOB_H

main.cpp

#include <iostream>
#include "StrBlob.h"

using namespace std;

int main() {
    StrBlob b1;
    {
        StrBlob b2 = {"a", "an", "the"};
        b1 = b2;
        b2.push_back("about");
        cout << b2.size() << endl;
    }
    // b2 在花括号外失效,作用域仅限于花括号内
    // cout << b2.size() << endl;
    cout << b1.size() << endl;
    cout << b1.front() << " " << b1.back() << endl;

    const StrBlob b3 = b1;
    cout << b3.front() << " " << b3.back() << endl;

    // 针对练习 14.18 重载运算符 < 的调用
    for (auto iter = b1.begin(); iter < b1.end(); iter.incr())
        cout << iter.deref() << " ";
    cout << endl;

    // 针对练习 14.26 重载下标运算符的调用
    StrBlob iter(b1);
    iter[2] = "an";
    for (auto iter = b1.begin(); iter != b1.end(); iter.incr())
        cout << iter.deref() << " ";
    cout << endl;

    // 练习 14.27
    for (StrBlobPtr iter = b1.begin(); iter != b1.end(); iter++)
        cout << iter.deref() << " ";
    cout << endl;

    return 0;
}
// 运行结果
4
4
a about
a about
a an the about 
a an an about 
a an an about 

Process finished with exit code 0

14.28

【出题思路】

本题练习实现加法和减法运算符。

继续扩展上一练习

【解答】

代码如下所示:

StrBlob.h

#ifndef TEST_STRBLOB_H
#define TEST_STRBLOB_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using std::string;
using std::vector;
using std::initializer_list;
using std::shared_ptr;
using std::weak_ptr;
using std::make_shared;
using std::out_of_range;
using std::runtime_error;

// 对于 StrBlob 中的友元声明来说,此前置声明是必要的
class StrBlobPtr;

//===========================================
//
//    StrBlob - custom vector<string>
//
//===========================================
class StrBlob {
    friend class StrBlobPtr;
    // 练习 14.16
    friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
    friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
    // 练习 14.18
    friend bool operator<(const StrBlob &s1, const StrBlob &s2);
    friend bool operator<=(const StrBlob &s1, const StrBlob &s2);
    friend bool operator>(const StrBlob &s1, const StrBlob &s2);
    friend bool operator>=(const StrBlob &s1, const StrBlob &s2);

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    // 添加和删除元素
    void push_back(const string &t) { data->push_back(t); }
    void pop_back();
    // 元素访问
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;

    // 提供给 StrBlobPtr 的接口
    // 返回指向首元素和尾后元素的 StrBlobPtr
    StrBlobPtr begin();     // 定义 StrBlobPtr 后才能定义这两个函数
    StrBlobPtr end();
    // 练习 14.26
    string &operator[](size_t);
    const string &operator[](size_t) const;

private:
    shared_ptr<vector<string>> data;
    // 如果 data[i] 不合法,抛出一个异常
    void check(size_type i, const string &msg) const;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list <string> il) :
        data(make_shared<vector<string>>(il)) { }

void StrBlob::check(vector<string>::size_type i, const string &msg) const {
    if (i >= data->size())
        throw out_of_range(msg);
}

string& StrBlob::front() {
    // 如果 vector 为空,check 会抛出一个异常
    check(0, "front on empty StrBlob");
    return data->front();
}

// const 版本 front
const string& StrBlob::front() const {
    check(0, "front on empty StrBlob");
    return data->front();
}

string& StrBlob::back() {
    check(0, "back on empty StrBlob");
    return data->back();
}

// const 版本 back
const string& StrBlob::back() const {
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back() {
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

bool operator==(const StrBlob &lhs, const StrBlob&rhs) {
    return lhs.data == rhs.data;            // 所指向的 vector 相等
}

bool operator!=(const StrBlob &lhs, const StrBlob&rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data < *s2.data;
}

bool operator<=(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data <= *s2.data;
}

bool operator>(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data > *s2.data;
}

bool operator>=(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data >= *s2.data;
}

string& StrBlob::operator[](size_t n) {
    check(n, "out of range");
    return data->at(n);
}

const string& StrBlob::operator[](size_t n) const {
    check(n, "out of range");
    return data->at(n);
}

//===================================================
//
//    StrBlobPtr - custom iterator of StrBlob
//
//===================================================
class StrBlobPtr {
    // 练习 14.16
    friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    // 练习 14.18
    friend bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);

public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    string& deref() const;
    StrBlobPtr& incr();     // 前缀递增
    StrBlobPtr& decr();     // 后缀递减
    // 练习 14.26
    const string &operator[](size_t) const;
    // 练习 14.27
    StrBlobPtr &operator++();       // 前置运算符
    StrBlobPtr &operator--();
    StrBlobPtr &operator++(int);    // 后置运算符
    StrBlobPtr &operator--(int);
    // 练习 14.28
    StrBlobPtr &operator+=(size_t);
    StrBlobPtr &operator-=(size_t);
    StrBlobPtr operator+(size_t) const;
    StrBlobPtr operator-(size_t) const;

private:
    // 若检查成功,check 返回一个指向 vector 的 shared_ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;
    // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;            // 在数组中的当前位置
};

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const {
    auto ret = wptr.lock(); // vector 还存在吗?
    if (!ret)
        throw runtime_error("unbound StrBlobPtr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;             // 否则,返回指向 vector 的 shared_ptr
}

string& StrBlobPtr::deref() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];      // (*P) 是对象所指向的 vector
}

// 前缀递增:返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr() {
    // 如果 curr 已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr;                 // 推进当前位置
    return *this;
}

// 前缀递减:返回递减后的对象的引用
StrBlobPtr& StrBlobPtr::decr() {
    // 如果 curr 已经为 0,递减它会产生一个非法下标
    --curr;                 // 递减当前位置
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

// StrBlob 的 begin 和 end 成员的定义
StrBlobPtr StrBlob::begin() {
    return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end() {
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

// StrBlobPtr 的比较操作
bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    // 若底层的 vector 是同一个
    if (l == r)
        // 则两个指针都是空,或者指向相同元素时,它们相等
        return (!r || lhs.curr == rhs.curr);
    else
        return false;       // 若指向不同 vector,则不可能相等
}

bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    auto l = s1.wptr.lock(), r = s2.wptr.lock();
    if (l == r) {
        if (!r)
            return false;               // 两个指针都为空,认为是相等
        return (s1.curr < s2.curr);     // 指向相同 vector,比较指针位置
    } else {
        return false;                   // 指向不同 vector 时,不能比较
    }
}

bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return s2 < s1;
}

bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return !(s2 < s1);
}

bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return !(s1 < s2);
}

const string& StrBlobPtr::operator[](size_t n) const {
    auto p = check(n, "dereference out of range");
    return (*p)[n];
}

// 前置版本:返回递增/递减对象的引用
StrBlobPtr& StrBlobPtr::operator++() {
    // 如果 curr 已经指向了容器的尾后位置,则无法递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr;         // 将 curr 在当前状态下向前移动一个元素
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--() {
    // 如果 curr 是 0,则继续递减它将产生一个无效下标
    --curr;         // 将 curr 在当前状态下后前移动一个元素
    check(curr, "decrement past begin of StrBlobPtr");
    return *this;
}

// 后置版本:递增/递减对象的值但是返回原值
StrBlobPtr& StrBlobPtr::operator++(int) {
    // 此处无需检查有效性,调用前置递增运算时才需要检查
    StrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向前移动一个元素,前置++需要检查递增的有效性
    return ret;     // 返回之前记录的状态
}
StrBlobPtr& StrBlobPtr::operator--(int) {
    // 此处无需检查有效性,调用前置递减运算时才需要检查
    StrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向后移动一个元素,前置--需要检查递减的有效性
    return ret;     // 返回之前记录的状态
}

StrBlobPtr& StrBlobPtr::operator+=(size_t n) {
    curr += n;
    check(curr, "increment past end of StrBlobPtr");
    return *this;
}

StrBlobPtr& StrBlobPtr::operator-=(size_t n) {
    curr -= n;
    check(curr, "decrement past begin of StrBlobPtr");
    return *this;
}

StrBlobPtr StrBlobPtr::operator+(size_t n) const {
    StrBlobPtr ret = *this;
    ret += n;
    return ret;
}

StrBlobPtr StrBlobPtr::operator-(size_t n) const {
    StrBlobPtr ret = *this;
    ret -= n;
    return ret;
}

#endif //TEST_STRBLOB_H

main.cpp

#include <iostream>
#include "StrBlob.h"

using namespace std;

int main() {
    StrBlob b1;
    {
        StrBlob b2 = {"a", "an", "the"};
        b1 = b2;
        b2.push_back("about");
        cout << b2.size() << endl;
    }
    // b2 在花括号外失效,作用域仅限于花括号内
    // cout << b2.size() << endl;
    cout << b1.size() << endl;
    cout << b1.front() << " " << b1.back() << endl;

    const StrBlob b3 = b1;
    cout << b3.front() << " " << b3.back() << endl;

    // 针对练习 14.18 重载运算符 < 的调用
    for (auto iter = b1.begin(); iter < b1.end(); iter.incr())
        cout << iter.deref() << " ";
    cout << endl;

    // 针对练习 14.26 重载下标运算符的调用
    StrBlob iter(b1);
    iter[2] = "an";
    for (auto iter = b1.begin(); iter != b1.end(); iter.incr())
        cout << iter.deref() << " ";
    cout << endl;

    // 练习 14.27
    for (StrBlobPtr iter = b1.begin(); iter != b1.end(); iter++)
        cout << iter.deref() << " ";
    cout << endl;

    // 练习 14.28
    StrBlobPtr itr(b1);
    cout << (itr + 3).deref() << endl;

    return 0;
}
// 运行结果
4
4
a about
a about
a an the about 
a an an about 
a an an about 
about

Process finished with exit code 0

14.29

【出题思路】

理解递增和递减运算符。

【解答】

对于 ++-- 运算符,无论它是前缀版本还是后缀版本,都会改变对象本身的值,因此,不能定义成 const 的。

14.30

【出题思路】

本题练习实现解引用和箭头运算符。

基于上一题的程序,增加本题的要求。另外,本题把类的声明(在 .h 文件)和定义(在 .cpp 文件)分开,使程序组织更加清晰。

【解答】

StrBlob.h

#ifndef TEST_STRBLOB_H
#define TEST_STRBLOB_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using std::string;
using std::vector;
using std::initializer_list;
using std::shared_ptr;
using std::weak_ptr;
using std::make_shared;
using std::out_of_range;
using std::runtime_error;

// 对于 StrBlob 中的友元声明来说,此前置声明是必要的
class StrBlobPtr;
class ConstStrBlobPtr;

//===========================================
//
//    StrBlob - custom vector<string>
//
//===========================================
class StrBlob {
    friend class StrBlobPtr;
    friend class ConstStrBlobPtr;
    // 练习 14.16
    friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
    friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
    // 练习 14.18
    friend bool operator<(const StrBlob &s1, const StrBlob &s2);
    friend bool operator<=(const StrBlob &s1, const StrBlob &s2);
    friend bool operator>(const StrBlob &s1, const StrBlob &s2);
    friend bool operator>=(const StrBlob &s1, const StrBlob &s2);

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    // 添加和删除元素
    void push_back(const string &t) { data->push_back(t); }
    void pop_back();
    // 元素访问
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;

    // 提供给 StrBlobPtr 的接口
    // 返回指向首元素和尾后元素的 StrBlobPtr
    StrBlobPtr begin();     // 定义 StrBlobPtr 后才能定义这两个函数
    StrBlobPtr end();
    // 提供给 ConstStrBlobPtr 的接口
    // 返回指向首元素和尾后元素的 ConstStrBlobPtr
    ConstStrBlobPtr cbegin() const;     // 定义 ConstStrBlobPtr 后才能定义这两个函数
    ConstStrBlobPtr cend() const;
    // 练习 14.26
    string &operator[](size_t);
    const string &operator[](size_t) const;

private:
    shared_ptr<vector<string>> data;
    // 如果 data[i] 不合法,抛出一个异常
    void check(size_type i, const string &msg) const;
};
bool operator==(const StrBlob &lhs, const StrBlob &rhs);
bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
bool operator<(const StrBlob &s1, const StrBlob &s2);
bool operator<=(const StrBlob &s1, const StrBlob &s2);
bool operator>(const StrBlob &s1, const StrBlob &s2);
bool operator>=(const StrBlob &s1, const StrBlob &s2);


//===================================================
//
//    StrBlobPtr - custom iterator of StrBlob
//
//===================================================
class StrBlobPtr {
    // 练习 14.16
    friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
    // 练习 14.18
    friend bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
    friend bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);

public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    string& deref() const;
    StrBlobPtr& incr();     // 前缀递增
    StrBlobPtr& decr();     // 后缀递减
    // 练习 14.26
    string &operator[](size_t);
    const string &operator[](size_t) const;
    // 练习 14.27
    StrBlobPtr &operator++();       // 前置运算符
    StrBlobPtr &operator--();
    StrBlobPtr &operator++(int);    // 后置运算符
    StrBlobPtr &operator--(int);
    // 练习 14.28
    StrBlobPtr &operator+=(size_t);
    StrBlobPtr &operator-=(size_t);
    StrBlobPtr operator+(size_t) const;
    StrBlobPtr operator-(size_t) const;
    // 练习 14.30
    string &operator*() const;
    string *operator->() const;

private:
    // 若检查成功,check 返回一个指向 vector 的 shared_ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;
    // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;            // 在数组中的当前位置
};
bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);


//=========================================================
//
//    ConstStrBlobPtr - custom const_iterator of StrBlob
//
//=========================================================
class ConstStrBlobPtr {
    // 练习 14.16
    friend bool operator==(const ConstStrBlobPtr &lhs, const ConstStrBlobPtr &rhs);
    friend bool operator!=(const ConstStrBlobPtr &lhs, const ConstStrBlobPtr &rhs);
    // 练习 14.18
    friend bool operator<(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);
    friend bool operator<=(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);
    friend bool operator>(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);
    friend bool operator>=(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);

public:
    ConstStrBlobPtr() : curr(0) {}
    ConstStrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    string& deref() const;
    ConstStrBlobPtr& incr();     // 前缀递增
    ConstStrBlobPtr& decr();     // 后缀递减
    // 练习 14.26
    const string &operator[](size_t) const;
    // 练习 14.27
    ConstStrBlobPtr &operator++();       // 前置运算符
    ConstStrBlobPtr &operator--();
    ConstStrBlobPtr &operator++(int);    // 后置运算符
    ConstStrBlobPtr &operator--(int);
    // 练习 14.28
    ConstStrBlobPtr &operator+=(size_t);
    ConstStrBlobPtr &operator-=(size_t);
    ConstStrBlobPtr operator+(size_t) const;
    ConstStrBlobPtr operator-(size_t) const;
    // 练习 14.30
    const string &operator*() const;
    const string *operator->() const;

private:
    // 若检查成功,check 返回一个指向 vector 的 shared_ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;
    // 保存一个 weak_ptr,意味着底层 vector 可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;            // 在数组中的当前位置
};
bool operator==(const ConstStrBlobPtr &lhs, const ConstStrBlobPtr &rhs);
bool operator!=(const ConstStrBlobPtr &lhs, const ConstStrBlobPtr &rhs);
bool operator<(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);
bool operator<=(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);
bool operator>(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);
bool operator>=(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2);

#endif //TEST_STRBLOB_H

StrBlob.cpp

#include "StrBlob.h"

/*
 * StrBlob
 */
StrBlob::StrBlob() : data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list <string> il) :
        data(make_shared<vector<string>>(il)) { }

void StrBlob::check(vector<string>::size_type i, const string &msg) const {
    if (i >= data->size())
        throw out_of_range(msg);
}

string& StrBlob::front() {
    // 如果 vector 为空,check 会抛出一个异常
    check(0, "front on empty StrBlob");
    return data->front();
}

// const 版本 front
const string& StrBlob::front() const {
    check(0, "front on empty StrBlob");
    return data->front();
}

string& StrBlob::back() {
    check(0, "back on empty StrBlob");
    return data->back();
}

// const 版本 back
const string& StrBlob::back() const {
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back() {
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

bool operator==(const StrBlob &lhs, const StrBlob&rhs) {
    return lhs.data == rhs.data;            // 所指向的 vector 相等
}

bool operator!=(const StrBlob &lhs, const StrBlob&rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data < *s2.data;
}

bool operator<=(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data <= *s2.data;
}

bool operator>(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data > *s2.data;
}

bool operator>=(const StrBlob &s1, const StrBlob &s2) {
    return *s1.data >= *s2.data;
}

string& StrBlob::operator[](size_t n) {
    check(n, "out of range");
    return data->at(n);
}

const string& StrBlob::operator[](size_t n) const {
    check(n, "out of range");
    return data->at(n);
}


/*
 * StrBlobPtr
 */
shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const {
    auto ret = wptr.lock(); // vector 还存在吗?
    if (!ret)
        throw runtime_error("unbound StrBlobPtr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;             // 否则,返回指向 vector 的 shared_ptr
}

string& StrBlobPtr::deref() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];      // (*P) 是对象所指向的 vector
}

// 前缀递增:返回递增后的对象的引用
StrBlobPtr& StrBlobPtr::incr() {
    // 如果 curr 已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr;                 // 推进当前位置
    return *this;
}

// 前缀递减:返回递减后的对象的引用
StrBlobPtr& StrBlobPtr::decr() {
    // 如果 curr 已经为 0,递减它会产生一个非法下标
    --curr;                 // 递减当前位置
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

// StrBlob 的 begin 和 end 成员的定义
StrBlobPtr StrBlob::begin() {
    return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end() {
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

// StrBlobPtr 的比较操作
bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    // 若底层的 vector 是同一个
    if (l == r)
        // 则两个指针都是空,或者指向相同元素时,它们相等
        return (!r || lhs.curr == rhs.curr);
    else
        return false;       // 若指向不同 vector,则不可能相等
}

bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {
    return !(lhs == rhs);
}

bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    auto l = s1.wptr.lock(), r = s2.wptr.lock();
    if (l == r) {
        if (!r)
            return false;               // 两个指针都为空,认为是相等
        return (s1.curr < s2.curr);     // 指向相同 vector,比较指针位置
    } else {
        return false;                   // 指向不同 vector 时,不能比较
    }
}

bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return s2 < s1;
}

bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return !(s2 < s1);
}

bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2) {
    return !(s1 < s2);
}

string& StrBlobPtr::operator[](size_t n) {
    auto p = check(n, "dereference out of range");
    return (*p)[n];
}

const string& StrBlobPtr::operator[](size_t n) const {
    auto p = check(n, "dereference out of range");
    return (*p)[n];
}

// 前置版本:返回递增/递减对象的引用
StrBlobPtr& StrBlobPtr::operator++() {
    // 如果 curr 已经指向了容器的尾后位置,则无法递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr;         // 将 curr 在当前状态下向前移动一个元素
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--() {
    // 如果 curr 是 0,则继续递减它将产生一个无效下标
    --curr;         // 将 curr 在当前状态下后前移动一个元素
    check(curr, "decrement past begin of StrBlobPtr");
    return *this;
}

// 后置版本:递增/递减对象的值但是返回原值
StrBlobPtr& StrBlobPtr::operator++(int) {
    // 此处无需检查有效性,调用前置递增运算时才需要检查
    StrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向前移动一个元素,前置++需要检查递增的有效性
    return ret;     // 返回之前记录的状态
}
StrBlobPtr& StrBlobPtr::operator--(int) {
    // 此处无需检查有效性,调用前置递减运算时才需要检查
    StrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向后移动一个元素,前置--需要检查递减的有效性
    return ret;     // 返回之前记录的状态
}

StrBlobPtr& StrBlobPtr::operator+=(size_t n) {
    curr += n;
    check(curr, "increment past end of StrBlobPtr");
    return *this;
}

StrBlobPtr& StrBlobPtr::operator-=(size_t n) {
    curr -= n;
    check(curr, "decrement past begin of StrBlobPtr");
    return *this;
}

StrBlobPtr StrBlobPtr::operator+(size_t n) const {
    StrBlobPtr ret = *this;
    ret += n;
    return ret;
}

StrBlobPtr StrBlobPtr::operator-(size_t n) const {
    StrBlobPtr ret = *this;
    ret -= n;
    return ret;
}

string& StrBlobPtr::operator*() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];      // (*p) 是对象所指的 vector
}

string* StrBlobPtr::operator->() const {
    // 将实际工作委托给解引用运算符
    return & this->operator*();
}


/*
 * ConstStrBlobPtr
 */
shared_ptr<vector<string>> ConstStrBlobPtr::check(size_t i, const string &msg) const {
    auto ret = wptr.lock(); // vector 还存在吗?
    if (!ret)
        throw runtime_error("unbound ConstStrBlobPtr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;             // 否则,返回指向 vector 的 shared_ptr
}

string& ConstStrBlobPtr::deref() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];      // (*P) 是对象所指向的 vector
}

// 前缀递增:返回递增后的对象的引用
ConstStrBlobPtr& ConstStrBlobPtr::incr() {
    // 如果 curr 已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of ConstStrBlobPtr");
    ++curr;                 // 推进当前位置
    return *this;
}

// 前缀递减:返回递减后的对象的引用
ConstStrBlobPtr& ConstStrBlobPtr::decr() {
    // 如果 curr 已经为 0,递减它会产生一个非法下标
    --curr;                 // 递减当前位置
    check(-1, "decrement past begin of ConstStrBlobPtr");
    return *this;
}

// StrBlob 的 begin 和 end 成员的定义
ConstStrBlobPtr StrBlob::cbegin() const {
    return ConstStrBlobPtr(*this);
}
ConstStrBlobPtr StrBlob::cend() const {
    auto ret = ConstStrBlobPtr(*this, data->size());
    return ret;
}

// ConstStrBlobPtr 的比较操作
bool operator==(const ConstStrBlobPtr &lhs, const ConstStrBlobPtr &rhs) {
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    // 若底层的 vector 是同一个
    if (l == r)
        // 则两个指针都是空,或者指向相同元素时,它们相等
        return (!r || lhs.curr == rhs.curr);
    else
        return false;       // 若指向不同 vector,则不可能相等
}

bool operator!=(const ConstStrBlobPtr &lhs, const ConstStrBlobPtr &rhs) {
    return !(lhs == rhs);
}

bool operator<(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2) {
    auto l = s1.wptr.lock(), r = s2.wptr.lock();
    if (l == r) {
        if (!r)
            return false;               // 两个指针都为空,认为是相等
        return (s1.curr < s2.curr);     // 指向相同 vector,比较指针位置
    } else {
        return false;                   // 指向不同 vector 时,不能比较
    }
}

bool operator>(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2) {
    return s2 < s1;
}

bool operator<=(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2) {
    return !(s2 < s1);
}

bool operator>=(const ConstStrBlobPtr &s1, const ConstStrBlobPtr &s2) {
    return !(s1 < s2);
}

const string& ConstStrBlobPtr::operator[](size_t n) const {
    auto p = check(n, "dereference out of range");
    return (*p)[n];
}

// 前置版本:返回递增/递减对象的引用
ConstStrBlobPtr& ConstStrBlobPtr::operator++() {
    // 如果 curr 已经指向了容器的尾后位置,则无法递增它
    check(curr, "increment past end of ConstStrBlobPtr");
    ++curr;         // 将 curr 在当前状态下向前移动一个元素
    return *this;
}
ConstStrBlobPtr& ConstStrBlobPtr::operator--() {
    // 如果 curr 是 0,则继续递减它将产生一个无效下标
    --curr;         // 将 curr 在当前状态下后前移动一个元素
    check(curr, "decrement past begin of ConstStrBlobPtr");
    return *this;
}

// 后置版本:递增/递减对象的值但是返回原值
ConstStrBlobPtr& ConstStrBlobPtr::operator++(int) {
    // 此处无需检查有效性,调用前置递增运算时才需要检查
    ConstStrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向前移动一个元素,前置++需要检查递增的有效性
    return ret;     // 返回之前记录的状态
}
ConstStrBlobPtr& ConstStrBlobPtr::operator--(int) {
    // 此处无需检查有效性,调用前置递减运算时才需要检查
    ConstStrBlobPtr ret = *this;         // 记录当前的值
    ++*this;        // 向后移动一个元素,前置--需要检查递减的有效性
    return ret;     // 返回之前记录的状态
}

ConstStrBlobPtr& ConstStrBlobPtr::operator+=(size_t n) {
    curr += n;
    check(curr, "increment past end of ConstStrBlobPtr");
    return *this;
}

ConstStrBlobPtr& ConstStrBlobPtr::operator-=(size_t n) {
    curr -= n;
    check(curr, "decrement past begin of ConstStrBlobPtr");
    return *this;
}

ConstStrBlobPtr ConstStrBlobPtr::operator+(size_t n) const {
    ConstStrBlobPtr ret = *this;
    ret += n;
    return ret;
}

ConstStrBlobPtr ConstStrBlobPtr::operator-(size_t n) const {
    ConstStrBlobPtr ret = *this;
    ret -= n;
    return ret;
}

const string& ConstStrBlobPtr::operator*() const {
    auto p = check(curr, "dereference past end");
    return (*p)[curr];      // (*p) 是对象所指的 vector
}

const string* ConstStrBlobPtr::operator->() const {
    // 将实际工作委托给解引用运算符
    return & this->operator*();
}

main.cpp

#include <iostream>
#include "StrBlob.h"

using namespace std;

int main() {
    StrBlob b1;
    {
        StrBlob b2 = {"a", "an", "the"};
        b1 = b2;
        b2.push_back("about");
        cout << b2.size() << endl;
    }
    // b2 在花括号外失效,作用域仅限于花括号内
    // cout << b2.size() << endl;
    cout << b1.size() << endl;
    cout << b1.front() << " " << b1.back() << endl;

    const StrBlob b3 = b1;
    cout << b3.front() << " " << b3.back() << endl;

    // 针对练习 14.18 重载运算符 < 的调用
    for (auto iter = b1.begin(); iter < b1.end(); iter.incr())
        cout << iter.deref() << " ";
    cout << endl;

    // 针对练习 14.26 重载下标运算符的调用
    StrBlob iter(b1);
    iter[2] = "an";
    for (auto iter = b1.begin(); iter != b1.end(); iter.incr())
        cout << iter.deref() << " ";
    cout << endl;

    // 练习 14.27
    for (StrBlobPtr iter = b1.begin(); iter != b1.end(); iter++)
        cout << iter.deref() << " ";
    cout << endl;

    // 练习 14.28
    StrBlobPtr itr(b1);
    cout << (itr + 3).deref() << endl;

    // 练习 14.30
    StrBlob a1 = {"hi", "bye", "now"};
    StrBlobPtr p1(a1);               // p1 指向 a1 中的 vector
    *p1 = "okay";                    // 给 a1 的首元素赋值
    cout << p1->size() << endl;      // 打印 4,这是 a1 首元素的大小
    cout << (*p1).size() << endl;    // 等价于 p1->size()

    // 练习 14.30
    StrBlob a2 = {"hi", "bye", "now"};
    ConstStrBlobPtr p2(a2);          // p2 指向 a2 中的 const vector
    cout << p2->size() << endl;      // 打印 2,这是 a2 首元素的大小
    cout << (*p2).size() << endl;    // 等价于 p2->size()


    return 0;
}
// 运行结果
4
4
a about
a about
a an the about 
a an an about 
a an an about 
about
4
4
2
2

Process finished with exit code 0

14.31

【出题思路】

理解拷贝控制成员。(三/五法则 P447P_{447}

【解答】

对于 StrBlobPtr 类,它的所有数据成员都能被合成的拷贝控制成员(synthesized copy-control members)恰当的拷贝、赋值和销毁。

14.32

【出题思路】

本题练习定义箭头运算符访问成员对象的数据。

【解答】

StrBlob.h 和 StrBlob.cpp 同练习 14.30

main.cpp:

#include <iostream>
#include "StrBlob.h"

using namespace std;

/*
 * MyClass 类定义了一个指向 StrBlobPtr 的指针
 */
class MyClass {
public:
    MyClass() = default;
    MyClass(StrBlobPtr *p) : pointer(p) {}

    StrBlobPtr &operator*() { return *this->pointer; };
    StrBlobPtr *operator->() { return &this->operator*(); };

private:
    StrBlobPtr *pointer = nullptr;
};

int main() {
    StrBlob a1 = {"hi", "bye", "now"};
    StrBlobPtr p1(a1);               // p1 指向 a1 中的 vector
    MyClass p(&p1);
    cout << p->operator->()->front();
    cout << p->operator->()->back() << endl;

    return 0;
}
// 运行结果
hi

Process finished with exit code 0

Preference:

std::vector::front

std::vector::back

C++ 运算符优先级

14.33

【出题思路】

理解调用运算符。

【解答】

0 个或多个。

An overloaded operator function has the same number of parameters as the operator has operands. Hence the maximum value should be around 256. (question on SO)

14.34

【出题思路】

本题练习定义调用运算符。

【解答】

struct Test {
    int operator()(bool b, int iA, int iB) {
        return b ? iA : iB;
    }
};

14.35

【出题思路】

本题练习定义调用运算符。

【解答】

程序如下所示:

#include <iostream>
#include <string>

class GetInput {
public:
    GetInput(std::istream &i = std::cin) : is(i) { }
    std::string operator()() const {
        std::string str;
        std::getline(is, str);
        return is ? str : std::string();
    }

private:
    std::istream &is;
};

int main() {
    GetInput getInput;
    std::cout << getInput() << std::endl;
    return 0;
}
// 第一行是从控制台的输入
// 第二行为程序运行结果
Welcome To CPlusPlus
Welcome To CPlusPlus

Process finished with exit code 0

14.36

【出题思路】

本题练习定义调用运算符。

【解答】

程序如下所示:

#include <iostream>
#include <string>
#include <vector>

class GetInput {
public:
    GetInput(std::istream &i = std::cin) : is(i) { }
    std::string operator()() const {
        std::string str;
        std::getline(is, str);
        return is ? str : std::string();
    }

private:
    std::istream &is;
};

int main() {
    GetInput getInput;
    std::vector<std::string> svec;
    for (std::string tmp; !(tmp = getInput()).empty(); )
        svec.push_back(tmp);
    for (const auto &str : svec)
        std::cout << str << ",";
    std::cout << std::endl;
    return 0;
}
// 前两行是从控制台的输入,然后按 command + d 组合键结束输入
// 程序运行结果为第四行
Hello World !
Welcome To CPlusPlus
^D
Hello World !,Welcome To CPlusPlus, 

Process finished with exit code 0

14.37

【出题思路】

本题练习定义和使用调用运算符。

【解答】

程序如下所示:

#include <iostream>
#include <algorithm>
#include <vector>

class IsEqual {
    int value;

public:
    IsEqual(int v) : value(v) { }
    bool operator()(int elem) { return elem == value; }
};

int main() {
    std::vector<int> ivec = {3, 2, 1, 4, 3, 7, 8, 6};
    std::replace_if(ivec.begin(), ivec.end(), IsEqual(3), 5);
    for (int i : ivec)
        std::cout << i << " ";
    std::cout << std::endl;
}
// 运行结果
5 2 1 4 5 7 8 6 

Process finished with exit code 0

14.38

【出题思路】

本题练习定义和使用调用运算符。

【解答】

程序如下所示:

#include <iostream>
#include <algorithm>        // std::count_if
#include <vector>
#include <string>
#include <fstream>          // std::ifstream
#include <sstream>          // std::istringstream
#include <stdexcept>        // std::cerr

class StrLenIs {
private:
    int len_;

public:
    StrLenIs(int len) : len_(len) { }
    bool operator()(const std::string &str) {
        return str.length() == len_;
    }
};

int main(int argc, char *argv[]) {
    if (argc != 2) {
        std::cerr << "请给出文件名" << std::endl;
        return -1;
    }
    std::ifstream in(argv[1]);
    if (!in) {
        std::cerr << "无法打开输入文件" << std::endl;
        return -1;
    }
    std::vector<std::string> svec;
    std::string line;
    std::string word;
    while (getline(in, line)) {
        std::istringstream l_in(line);          // 构造字符串流,读取单词
        while (l_in >> word)
            svec.push_back(word);
    }
    const int minLen = 1;
    const int maxLen = 10;
    for (int i = minLen; i <= maxLen; ++i) {
        StrLenIs strObj(i);
        std::cout << "len : " << i << ", cnt : "
                  << std::count_if(svec.begin(),svec.end(), strObj)
                  << std::endl;
    }
    return 0;
}

运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../data

注:../data 即为文件 data 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。

并在文件 data 中写入如下内容(The New Yorker):

I don’t understand the point of garden visits. Why do ordinary people, the
owners of mere balconies and tiny yards, torment themselves by touring 
other people’s grand estates? Nut trees, stables, ancestral compost 
heaps: I need no reminder of what I am missing. So, unlike virtually every
other gardener in Britain, I had no intention of spending my summer
wandering among aristocratic roses and marvelling at the fine tilth of
Lord Whatsit’s sandy carrot beds. All those rambling sweet peas make me 
furious; yes, Tristram, it is a handsome cardoon bed, but some of us are 
struggling to find space for a single extra lettuce. And then, wholly by 
accident, I found myself in the Lost Gardens of Heligan.

运行程序,程序执行结果如下所示:

// 运行结果
len : 1, cnt : 7
len : 2, cnt : 22
len : 3, cnt : 15
len : 4, cnt : 13
len : 5, cnt : 16
len : 6, cnt : 11
len : 7, cnt : 8
len : 8, cnt : 13
len : 9, cnt : 7
len : 10, cnt : 5

Process finished with exit code 0

14.39

【出题思路】

本题练习定义和使用调用运算符。

【解答】

程序如下所示:

#include <iostream>
#include <algorithm>        // std::count_if
#include <vector>
#include <string>
#include <fstream>          // std::ifstream
#include <sstream>          // std::istringstream
#include <stdexcept>        // std::cerr

class StrLenBetween {
private:
    int minLen_;
    int maxLen_;

public:
    StrLenBetween(int minLen, int maxLen)
        : minLen_(minLen), maxLen_(maxLen) { }
    bool operator()(const std::string &str) {
        return str.length() >= minLen_ && str.length() <= maxLen_;
    }
};

class StrNotShorterThan {
private:
    int minLen_;

public:
    StrNotShorterThan(int len) : minLen_(len) { }
    bool operator()(const std::string &str) {
        return str.length() >= minLen_;
    }
};

int main(int argc, char *argv[]) {
    if (argc != 2) {
        std::cerr << "请给出文件名" << std::endl;
        return -1;
    }
    std::ifstream in(argv[1]);
    if (!in) {
        std::cerr << "无法打开输入文件" << std::endl;
        return -1;
    }
    std::vector<std::string> svec;
    std::string line;
    std::string word;
    while (getline(in, line)) {
        std::istringstream l_in(line);          // 构造字符串流,读取单词
        while (l_in >> word)
            svec.push_back(word);
    }
    StrLenBetween sLenBetween(1, 9);
    StrNotShorterThan sNotShorterThan(10);
    std::cout << "len 1~9 : "
              << std::count_if(svec.begin(), svec.end(), sLenBetween)
              << std::endl;
    std::cout << "len >= 10 : "
              << std::count_if(svec.begin(), svec.end(), sNotShorterThan)
              << std::endl;
    return 0;
}

运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../data

注:../data 即为文件 data 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。

文件 data 中的测试数据同上一题。

运行程序,程序执行结果如下所示:

// 运行结果
len 1~9 : 112
len >= 10 : 7

Process finished with exit code 0

14.40

【出题思路】

本题练习定义和使用调用运算符。

【解答】

重新编写 练习 10.16

程序如下所示:

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using std::string;
using std::vector;
using std::cout;
using std::endl;

class ShorterString {
public:
    bool operator()(const string &s1, const string &s2) const {
        return s1.size() < s2.size();
    }
};

class NotShorterThan {
private:
    int minLen_;

public:
    NotShorterThan(int len) : minLen_(len) { }
    bool operator()(const string &s) {
        return s.size() >= minLen_;
    }
};

// 如果 ctr 的值大于 1,返回 word 的复数形式
string make_plural(size_t ctr, const string &word, const string &ending) {
    return (ctr > 1) ? word + ending : word;
}

void elimDups(std::vector<std::string> &words) {
    // 按字典序排序 words,以便查找重复单词
    sort(words.begin(), words.end());
    // unique 重排输入范围,使得每个单词只出现一次
    // 排列在范围的前部,返回指向不重复区域之后一个位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    // 删除重复单词
    words.erase(end_unique, words.end());
}

void biggies(vector<string> &words, vector<string>::size_type sz) {
    // 将 words 按字典序排序,删除重复单词
    elimDups(words);
    // 按长度排序,长度相同的单词维持字典序
    stable_sort(words.begin(), words.end(), ShorterString());
    // 获取一个迭代器,指向第一个满足 size() >= sz 的元素
    auto wc = find_if(words.begin(), words.end(), NotShorterThan(sz));
    // 计算满足 size() >= sz 的元素的数目
    auto count = words.end() - wc;
    cout << count << " " << make_plural(count, "word", "s")
         << " of length " << sz << " or longer" << endl;
    // 打印长度大于等于给定值的单词,每个单词后面接一个空格
    for_each(wc, words.end(),
             [] (const string &s) {
                 cout << s << " ";
             }
    );
    cout << endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    // 按字典序打印 svec 中长度不小于 4 的单词
    biggies(svec, 4);

    return 0;
}
// 运行结果
5 words of length 4 or longer
over slow jumps quick turtle 

Process finished with exit code 0

14.41

【出题思路】

本题旨在理解 lambda。

【解答】

在 C++11 中,lambda 是通过匿名的函数对象来实现的,因此我们可以把 lambda 看作是对函数对象在使用方式上进行的简化。

当代码需要一个简单的函数,并且这个函数并不会在其他地方被使用时,就可以使用 lambda 来实现,此时它所起的作用类似于匿名函数。

但如果这个函数需要多次使用,并且它需要保存某些状态的话,使用**函数对象(functor)**则更合适一些。

14.42

【出题思路】

本题练习使用函数对象。

std::transform

【解答】

程序如下所示:

#include <functional>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

int main() {
    using std::placeholders::_1;

    // 统计大于 1024 的值有多少个
    std::vector<int> ivec{1000, 2000, 3000, 4000, 5000};
    std::cout << std::count_if(ivec.begin(), ivec.end(),
            std::bind(std::greater<int>(), _1, 1024)) << std::endl;

    // 找到第一个不等于 pooth 的字符串
    std::vector<std::string> svec{"pooth", "pooth", "abc", "pooth"};
    std::cout << *std::find_if(svec.begin(), svec.end(),
            std::bind(std::not_equal_to<std::string>(), _1, "pooth"))
            << std::endl;

    // 将所有的值乘以 2
    std::transform(ivec.begin(), ivec.end(), ivec.begin(),
            std::bind(std::multiplies<int>(), _1, 2));
    for (const auto &i : ivec)
        std::cout << i << " ";
    std::cout << std::endl;

    return 0;
}
// 运行结果
4
abc
2000 4000 6000 8000 10000 

Process finished with exit code 0

14.43

【出题思路】

本题练习使用函数对象。

all_of, std::any_of, std::none_of

【解答】

#include <functional>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

int main() {
    int n;
    std::cin >> n;
    std::modulus<int> mod;
    std::vector<int> ivec{1, 2, 4, 5, 10};
    auto predicator = [&] (int i) { return 0 == mod(n, i); };
    auto is_divisible = std::all_of(ivec.begin(), ivec.end(), predicator);
    std::cout << (is_divisible ? "Yes" : "No");
    std::cout << std::endl;

    return 0;
}
// 运行结果
100
Yes

Process finished with exit code 0

14.44

【出题思路】

本题练习使用函数对象。

【解答】

程序如下所示:

#include <functional>
#include <string>
#include <map>
#include <iostream>

// 普通函数
int add(int i, int j) { return i + j; }

// lambda,其产生一个未命名的函数对象类
auto mod = [] (int i, int j) { return i % j; };

// 函数对象类
struct divide {
    int operator()(int denominator, int divisor) {
        return denominator / divisor;
    }
};

// 列举了可调用对象与二元运算符对应关系的表格
// 所有可调用对象都必须接受两个 int,返回一个 int
// 其中的元素可以是函数指针、函数对象或者 lambda
std::map<std::string, std::function<int(int, int)>> binops = {
        {"+", add},                                 // 函数指针
        {"-", std::minus<int>()},                   // 标准库函数对象
        {"*", [] (int i, int j) { return i * j; }}, // 未命名的 lambda
        {"/", divide()},                            // 用户定义的函数对象
        {"%", mod}                                  // 命名了的 lambda 对象
};

int main() {
    while (true) {
        std::cout << "请输入(输入格式:操作数1 运算符 操作数2):\n";
        int x, y;
        std::string s;
        std::cin >> x >> s >> y;
        std::cout << binops[s](x, y) << std::endl;
    }

    return 0;
}
// 运行结果
请输入(输入格式:操作数1 运算符 操作数2):
1 + 2
3
请输入(输入格式:操作数1 运算符 操作数2):
1 - 2
-1
请输入(输入格式:操作数1 运算符 操作数2):
1 * 2
2
请输入(输入格式:操作数1 运算符 操作数2):
1 / 2
0
请输入(输入格式:操作数1 运算符 操作数2):
1 % 2
1
请输入(输入格式:操作数1 运算符 操作数2):

14.45

【出题思路】

理解类型转换运算符。

【解答】

如果要转换成 string,那么返回值应该是 bookNo;如果要转换成 double,那么返回值应该是 revenue。

14.46

【出题思路】

理解类型转换运算符。

【解答】

Sales_data 类不应该定义这两种类型转换运算符,因为对于该类型来讲,它包含三个数据成员:bookNo,units_sold 和 revenue,只有三者在一起才是有效的数据。

但如果确实想要定义这两个类型转换运算符的话,应该把它们声明成 explicit 的,这样可以防止 Sales_data 在某些情况下被默认转换成 string 或 double 类型,这有可能导致意料之外的运算结果。

代码如下所示:

Sales_data.h

#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H

#include <iostream>
#include <string>

// added overloaded input, output, addition, and compound-assignment operators
class Sales_data {
    friend std::istream &operator>>(std::istream&, Sales_data&);        // input
    friend std::ostream &operator<<(std::ostream&, const Sales_data&);  // output
    friend Sales_data operator+(const Sales_data&, const Sales_data&);  // addition

public:
    Sales_data(const std::string &s, unsigned n, double p)
            : bookNo(s), units_sold(n), revenue(n * p) { }
    Sales_data() : Sales_data("", 0, 0.0f) { }
    Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f) { }
    Sales_data(std::istream&);

    Sales_data &operator+=(const Sales_data&);          // compound-assignment
    std::string isbn() const { return bookNo; }

    // exercise 14.22, assign string to Sales_data
    Sales_data &operator=(const std::string&);
    // exercise 14.46
    explicit operator std::string() const { return bookNo; }
    explicit operator double() const { return revenue; }

private:
    double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0;
};

std::istream &operator>>(std::istream&, Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
Sales_data operator+(const Sales_data&, const Sales_data&);

inline
double Sales_data::avg_price() const {
    return units_sold ? revenue / units_sold : 0;
}

#endif //TEST_SALES_DATA_H

Sales_data.cpp

#include "Sales_data.h"

Sales_data::Sales_data(std::istream &is) : Sales_data() {
    is >> *this;
}

Sales_data& Sales_data::operator+=(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

std::istream &operator>>(std::istream &is, Sales_data &item) {
    double price = 0.0;
    is >> item.bookNo >> item.units_sold >> price;
    if (is)
        item.revenue = price * item.units_sold;
    else
        item = Sales_data();
    return is;
}

std::ostream &operator<<(std::ostream &os, const Sales_data &item) {
    os << item.isbn() << " " << item.units_sold << " " << item.revenue
       << " " << item.avg_price();
    return os;
}

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
    Sales_data sum = lhs;
    sum += rhs;
    return sum;
}

Sales_data& Sales_data::operator=(const std::string &isbn) {
    bookNo = isbn;
    return *this;
}

main.cpp

#include "Sales_data.h"

/*
 * 测试数据输入格式:ISBN 数量 单价
 * 输出格式:ISBN 数量 总价 平均价格
 * ISBN: std::string bookNo
 * 数量: unsigned units_sold
 * 单价: double price
 * 总价: double revenue
 * 平均价格: double avg_price()
 */
int main() {
    Sales_data cp5("978-7-121-15535-2", 2, 99.9);
    std::cout << cp5 << std::endl;
    std::cout << static_cast<std::string>(cp5) << std::endl;
    std::cout << static_cast<double>(cp5) << std::endl;
    return 0;
}
// 运行结果
978-7-121-15535-2 2 199.8 99.9
978-7-121-15535-2
199.8

Process finished with exit code 0

14.47

【出题思路】

理解类型转换运算符。

【解答】

前者将对象转换成 const int ,在接受 const int 值的地方才能够使用。

后者则将对象转换成 int 值,且类型转换运算符不允许修改对象的内容。相对来说更加通用一些。

14.48

【出题思路】

理解类型转换运算符。

【解答】

A conversion to bool can be useful for the class Date. But it must be an explicit one to prevent any automatic conversion.

14.49

【出题思路】

本题练习类型转换运算符。

【解答】

代码如下所示:

Date.h

#ifndef TEST_DATE_H
#define TEST_DATE_H

#include <iostream>

class Date {
private:
    int year_ = 1;          // 年
    int month_ = 1;         // 月
    int day_ = 1;           // 日
    int totalDays_;         // 改日期是从公元元年1月1日开始的第几天

public:
    Date() = default;
    // 用年、月、日构造日期,默认为公元元年1月1日
    Date(int year, int month, int day);
    ~Date() { std::cout << "destructor called" << std::endl; }
    int getYear() const { return year_; }
    int getMonth() const { return month_; }
    int getDay() const { return day_; }
    int getMaxDay() const;          // 获得当月有多少天
    bool isLeapYear() const {
        return year_ % 4 == 0 && year_ % 100 != 0 || year_ % 400 == 0;
    }
    // 计算两个日期之间差多少天
    int operator-(const Date &date) const {
        return totalDays_ - date.totalDays_;
    }
    // 判断两个日期的前后顺序
    bool operator<(const Date &date) const {
        return totalDays_ < date.totalDays_;
    }
    // 拷贝构造函数
    Date(const Date &date);
    // 拷贝赋值运算符
    Date &operator=(const Date &date);
    // 移动构造函数
    Date(Date &&date) noexcept;
    // 移动赋值运算符
    Date &operator=(Date &&rhs) noexcept;
    // 练习 14.49
    explicit operator bool() { return (year_ < 4000) ? true : false; }
};

std::istream &operator>>(std::istream &in, Date &date);
std::ostream &operator<<(std::ostream &out, const Date &date);

#endif //TEST_DATE_H

Date.cpp

#include "Date.h"

#include <stdexcept>
#include <sstream>

namespace  {
    // 存储平年中某个月1日之前有多少天,为便于 getMaxDay 函数的实现,该数组多出一项
    const int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181, 212,
                                     243, 273, 304, 334, 365};
}

// 用年、月、日构造日期,默认为公元元年1月1日
Date::Date(int year, int month, int day) : year_(year), month_(month), day_(day) {
    if (day_ <= 0 || day_ > getMaxDay())
        throw std::runtime_error("Invalid date");
    int years = year_ - 1;
    totalDays_ = years * 365 + years / 4 - years / 100 + years / 400
            + DAYS_BEFORE_MONTH[month_ - 1] + day_;
    if (isLeapYear() && month_ > 2)
        ++totalDays_;
}

int Date::getMaxDay() const {
    if (isLeapYear() && month_ == 2)
        return 29;
    else
        return DAYS_BEFORE_MONTH[month_] - DAYS_BEFORE_MONTH[month_ - 1];
}

std::istream &operator>>(std::istream &in, Date &date) {
    int year, month, day;
    char c1, c2;
    in >> year >> c1 >> month >> c2 >> day;
    if (c1 != '-' || c2 != '-')
        throw std::runtime_error("Bad time format");
    date = Date(year, month, day);
    return in;
}

std::ostream &operator<<(std::ostream &out, const Date &date) {
    out << date.getYear() << "-" << date.getMonth() << "-" << date.getDay();
    return out;
}

Date::Date(const Date &date)
    : year_(date.year_), month_(date.month_), day_(date.day_) {
    std::cout << "copy constructor" << std::endl;
}

Date& Date::operator=(const Date &date) {
    std::cout << "copy-assignment operator -- " << date << std::endl;


    day_ = date.day_;
    month_ = date.month_;
    year_ = date.year_;
    return *this;
}

Date::Date(Date &&date) noexcept
    : year_(date.year_), month_(date.month_), day_(date.day_) {
    std::cout << "move constructor" << std::endl;
}

Date& Date::operator=(Date &&rhs) noexcept {
    std::cout << "move-assignment operator -- " << rhs << std::endl;

    if (this != &rhs) {
        day_ = rhs.day_;
        month_ = rhs.month_;
        year_ = rhs.year_;
    }
    return *this;
}

main.cpp

#include "Date.h"

int main() {
    Date date(2019, 8, 7);
    if (static_cast<bool>(date))
        std::cout << date << std::endl;
    return 0;
}
// 运行结果
2019-8-7
destructor called

Process finished with exit code 0

14.50

【出题思路】

理解类型转换运算符。

【解答】

对于 int ex1 = ldObj; ,它需要把 LongDouble 类型转换成 int 类型,但是 LongDouble 并没有定义对应的类型转换运算符。因此,它会尝试使用其它的来进行转换,其中 operator double () operator float() 都满足需求。但编译器无法确定哪一个更合适,因此会产生二义性错误:conversion from ‘LongDouble’ to ‘int’ is ambiguous。

对于 float ex2 = ldObj; ,它需要把 LongDouble 转换成 float 类型,而我们恰好定义了对应的类型转换运算符。因此,只需要直接调用 operator float() 即可。

14.51

【出题思路】

理解类型转换运算符。

【解答】

这里会优先调用 void calc(int) 函数。因为 double 转换为 int 是标准类型转换,而转换成 LongDouble 则是转换为用户自定义类型,实际上是调用了转换构造函数,因此前者优先。

类类型的转换优先级最低。

转换优先级排序:

  1. exact match
  2. const conversion
  3. promotion
  4. arithmetic or pointer conversion
  5. class-type conversion

14.52

【出题思路】

理解类型转换运算符。

【解答】

struct LongDouble {
    // member operator+ for illustration purposes; + is usually a nonmember LongDouble operator+(const SmallInt&); // 1
    // other members as in 14.9.2 (p. 587)
};
LongDouble operator+(LongDouble&, double); // 2
SmallInt si;
LongDouble ld;
ld = si + ld;
ld = ld + si;

ld = si + ld; is ambiguous.

ld = ld + si can use both 1 and 2, but 1 is more exactly. (in the 2, SmallInt need to convert to double)

该题答案来自

14.53

【出题思路】

理解类型转换运算符。

【解答】

内置的 operator+(int, double) 是可行的;

而 3.14 可以转换为 int,然后再转换为 SmallInt,所以 SmallInt 的成员 operator+ 也是可行的。

两者都需要进行类型转换,所以会产生二义性。改为 double d = s1 + SmallInt(3.14); 即可。

13

评论区