C++ Primer(第五版)|練習題答案與解析(第十四章:重載運算與類型轉換)

C++ Primer(第五版)|練習題答案與解析(第十四章:重載運算與類型轉換)

本博客主要記錄C++ Primer(第五版)中的練習題答案與解析。
參考:C++ Primer
C++ Primer

練習題14.1

在什麼情況下重載的運算符與內置運算符有所區別?在什麼情況下重載的運算符又與內置運算符一樣?

區別:

  • P490,重載運算符必須是一個類的成員或者至少有一個參數是類類型。
  • P491,可以直接通過對象調用一個重載運算符,如data1.operator+(data2)。
  • P491,使用重載的運算符本質上是函數調用,所以這些運算符指定了運算對象的求值順序或短路求值屬性,這些規則無法應用到重載運算符上,比如邏輯運算符、關係運算符和逗號運算符,不建議使用。

相同

  • P490,重載運算符的優先級和結合律與對應的內置運算符保持一致。

練習題14.2

爲Sales_data編寫重載的輸入、輸出、加法和複合賦值運算符。

//.h文件
#include <string>
#include <iostream>

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 &is);

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

private:
    inline double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.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;
}
//.cpp文件
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;
}
//測試文件
int main()
{
    Sales_data test;
    std::cin >> test;
    std::cout << test << std::endl;
}

測試:

ISN1913 10 5
ISN1913 10 50 5

練習題14.3

string和vector都定義了重載的==以比較各自的對象,假設svec1和svec2是存放string的vector,確定在下面的表達式中分別使用了哪個版本的==?

(a ) "cobble" == "stone" string重載的“==”
(b ) svec1[0] == svec2[0] string重載的“==”
(c ) svec1 == svec2 vector重載的“==”
(d ) svec1[0] == "stone" string重載的“==”

練習題14.4

如何確定下列運算符是否應該是類的成員?

P493
(a ) % 一般定義爲非成員。
(b ) %= 一般定義爲類成員,能夠改變對象的狀態。
(c ) ++ 一般定義爲類成員,能夠改變對象的狀態。
(d ) -> 必須定義爲類成員
(e ) << 一般定義爲非成員
(f )&& 一般定義爲非成員。
(g ) == 一般定義爲非成員。
(h ) () 必須定義爲類成員

練習題14.5

在7.5.1節練習7.40中,編寫了下列類中某一個的框架,請問在這個類中應該定義重載的運算符嗎?如果是,請寫出來。
(a) Book (b) Date © Employee (d) Vehicle (e) Object (f) Tree

//.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(unsigned 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:
    unsigned 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&);

//.cpp文件
std::istream& operator>>(std::istream &in, Book &book)
{
    in >> book.no_ >> book.name_ >> book.author_ >> book.pubdate_;
    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);
}
//測試文件
int main()
{
    Book book1(001, "Test", "Andrew", "2020");
    Book book2(001, "Test", "Andrew", "2020");

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

測試:1 Test Andrew 2020

練習題14.6

爲你的Sales_data類定義輸出運算符。

同14.2。

練習題14.7

你在13.5節的練習(P470)中曾經編寫了一個String類,爲它定義一個輸出運算符。

//.h文件
#include <memory>
#include <iostream>
class String
{
    friend std::ostream& operator<<(std::ostream&, const String&);
public:
    String() : String("") { }
    String(const char *);
    String(const String&);
    String& operator=(const String&);
    ~String();

    const char *c_str() const { return elements; }
    size_t size() const { return end - elements; }
    size_t length() const { return end - elements - 1; }
private:
    std::pair<char*, char*> alloc_n_copy(const char*, const char*);
    void range_initializer(const char*, const char*);
    void free();
private:
    char *elements;
    char *end;
    std::allocator<char> alloc;
};
std::ostream& operator<<(std::ostream&, const String&);
//.cpp文件
#include <algorithm>
#include <iostream>
std::pair<char*, char*>
String::alloc_n_copy(const char *b, const char *e)
{
    auto str = alloc.allocate(e - b);
    return{ str, std::uninitialized_copy(b, e, str) };
}
void String::range_initializer(const char *first, const char *last)
{
    auto newstr = alloc_n_copy(first, last);
    elements = newstr.first;
    end = newstr.second;
}
String::String(const char *s)
{
    char *sl = const_cast<char*>(s);
    while (*sl)
        ++sl;
    range_initializer(s, ++sl);
}
String::String(const String& rhs)
{
    range_initializer(rhs.elements, rhs.end);
    std::cout << "拷貝構造函數" << std::endl;
}
void String::free()
{
    if (elements) {
        std::for_each(elements, end, [this](char &c){ alloc.destroy(&c); });
        alloc.deallocate(elements, end - elements);
    }
}
String::~String()
{
    free();
}
String& String::operator = (const String &rhs)
{
    auto newstr = alloc_n_copy(rhs.elements, rhs.end);
    free();
    elements = newstr.first;
    end = newstr.second;
    std::cout << "拷貝賦值運算符" << std::endl;
    return *this;
}
std::ostream& operator<<(std::ostream &os, const String &s)
{
    std::cout << "重載輸出運算符<<" << std::endl;
    char *c = const_cast<char*>(s.c_str());
    while (*c)
        os << *c++;
    return os;
}
//測試文件
int main()
{
    String str("test train");
    std::cout << str << std::endl;
}

測試:

重載輸出運算符<<
test train

練習題14.8

你在7.51節的練習7.40(P261)中曾經編寫了一個類,爲它定義一個輸出運算符。

同14.5。

練習題14.9

爲你的Sales_data類定義輸入運算符。

同14.2。

練習題14.10

對於Sales_data的輸入運算符來說如果給定了下面的輸入將發生什麼情況?

使用14.2程序測試。
(a)0-201-99999-9 10 24.95
輸出:0-201-99999-9 10 249.5 24.95
(b)10 24.95 0-210-99999-9
輸出:10 24 22.8 0.95

練習題14.11

下面的Sales_data輸入運算符存在錯誤嗎?如果有,請指出來。對於這個輸入運算符如果仍然給定上個練習的輸入將發生什麼情況?
istream& operator>>(istream& in, Sales_data& s)
{
double price;
in >> s.bookNo >> s.units_sold >> price;
s.revenue = s.units_sold * price;
return in;
}

該寫法沒有檢查輸入是否正確。
測試1:

0-201-99999-9 10 24.95
0-201-99999-9 10 249.5 24.95

測試2:

10 24.95 0-210-99999-9
10 24 22.8 0.95

練習題14.12

你在7.51節的練習7.40(P261)中曾經編寫了一個類,爲它定義一個輸入運算符並確保該運算符可以處理輸入錯誤。

std::istream& operator>> (std::istream& is, Date& d)
{
    is >> d.year >> d.month >> d.day;
    if (is) {
        return is;
    }
    else {
        d = Date();
        return is;
    }
}

練習題14.13

你認爲Sales_data類還應該支持哪些其他算術運算符?如果有的話,請給出它們的定義。

既然有了+,那就應該有-。

//.h文件
#include <string>
#include <iostream>

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
    friend Sales_data operator-(const Sales_data&, const Sales_data&); // substraction

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 &is);

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

private:
    inline double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.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&);
Sales_data operator-(const Sales_data&, const Sales_data&);
inline double Sales_data::avg_price() const
{
    return units_sold ? revenue/units_sold : 0;
}
//.cpp文件
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;
}
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 operator-(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs;
    sum -= rhs;
    return sum;
}
//測試文件
int main()
{
  Sales_data s1("ISN-1918", 98, 8);
  Sales_data s2("ISN-1918", 20, 20);
  std::cout << s1 << std::endl;
  // 賦值
  s1 = s1 + s2;
  std::cout << s1 << std::endl;

  // 複合賦值
  s1 += s2;
  std::cout << s1 << std::endl;

  // 複合減法
  s1 -= s2;
  std::cout << s1 << std::endl;

  // 減法
  s1 = s1 - s2;
  std::cout << s1 << std::endl;
}

測試:

ISN-1918 98 784 8
ISN-1918 118 1184 10.0339
ISN-1918 138 1584 11.4783
ISN-1918 118 1184 10.0339
ISN-1918 98 784 8

練習題14.14

你覺得爲什麼調用operator+=來定義operator+比其他方法更有效?

如P497所示,使用+還需要額外分配空間,申請臨時空間保存等號右邊的和,而+=不需要。

練習題14.16

爲你的StrBlob類、StrBlobPtr類、StrVec類和String類分別定義相等運算符和不相等運算符。

// StrBlob
bool operator== (const StrBlob& lhs, const StrBlob& rhs)
{
    return *lhs.data == *rhs.data;
}

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

// StrBlobPtr
bool operator== (const StrBlobPtr& lhs, const StrBlobPtr& rhs)
{
    return lhs.curr == rhs.curr;
}

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

// StrVec
bool operator== (const StrVec& lhs, const StrVec& rhs)
{
    if (lhs.size() != rhs.size()) {
        return false;
    }
    else {
        auto l_iter = lhs.begin();
        auto r_iter = rhs.begin();
        for (l_iter, r_iter; l_iter != lhs.end(); ++ l_iter, ++ r_iter) {
            if (*l_iter != *r_iter) {
                return false;
            }
        }
    }
    return true;
}
bool operator!= (const StrVec& lhs, const StrVec& rhs)
{
    return !(lhs == rhs);
}

// String
bool operator== (const String& lhs, const String& rhs)
{
    if (lhs.size() != rhs.size()) {
        return false;
    }
    else {
        auto l_iter = lhs.begin, r_iter = rhs.begin;
        for (l_iter, r_iter; l_iter!=lhs.end; ++l_iter, ++r_iter) {
            if (*l_iter != *r_iter) {
                return false;
            }
        }
    }
    return true;
}

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

練習題14.18

爲你的StrBlob類、StrBlobPtr類、StrVec類和String類分別定義關係運算符。

// StrBlob類
bool operator< (const StrBlob& lhs, const StrBlob& rhs)
{
    return std::lexicographical_compare(lhs.data->begin(), lhs.data->end(), rhs.data->begin(), rhs.data->end());
}

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

// StrBlobPtr類
bool operator> (const StrBlobPtr& lhs, const StrBlobPtr& rhs)
{
    return lhs.curr > rhs.curr;
}

bool operator< (const StrBlobPtr& lhs, const StrBlobPtr& rhs)
{
    return lhs.curr < rhs.curr;
}

// StrVec類
bool operator< (const StrVec& lhs, const StrVec& rhs)
{
    return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
bool operator> (const StrVec& lhs, const StrVec& rhs)
{
    return rhs < lhs;
}

// String類
bool operator< (const String& lhs, const String& rhs)
{
    return std::lexicographical_compare(lhs.begin, lhs.end, rhs.begin, rhs.end);
}
bool operator> (const String& lhs, const String& rhs)
{
    return rhs < lhs;
}

練習題14.20

爲你的Sales_data類定義加法和複合賦值運算符。

同14.13。

練習題14.21

編寫Sales_data類的+和+=運算符,使得+執行實際的加法操作而+=調用+。相比於14.3節和14.4節對這兩個運算符的定義,本題的定義有何缺點?試討論之。

見14.13

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

使用+申請了Sales_data 類型的臨時變量,而+=沒有。

練習題14.22

定義賦值運算符的一個新版本,使得我們能把一個表示ISBN的string賦給一個Sales_data對象。

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

練習題14.23

爲你的StrVec類定義一個initializer_list賦值運算符。

StrVec& StrVec::operator=(std::initializer_list<std::string> il)
{
    auto data = alloc_n_copy(il.begin(), il.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

練習題14.26

爲你的StrBlob類、StrBlbPtr類、StrVec類和String類定義下標運算符。

// String類
char String::operator[] (std::size_t n)
{
    if (n < 0 || n > size()) {
        cout << "out of range." << endl;
        return 0;
    }
    return begin[n];
}
const char String::operator[] (std::size_t n) const
{
    if (n < 0 || n > size()) {
        cout << "out of range." << endl;
        return 0;
    }
    return begin[n];
}
// StrVec類
std::string& operator[] (std::size_t n) 
{ 
    return elements[n]; 
}
const std::string& operator[] (std::size_t n) const 
{ 
    return elements[n]; 
}

練習題14.27

爲你的StrBlobPtr類添加遞增和遞減運算符。

// 前置遞增和遞減
StrBlobPtr& operator++ ()
{
    check(curr, "increment past end of StrBlobPtr");
    ++ curr;
    return *this;
}

StrBlobPtr& operator-- ()
{
    -- 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;
}

練習題14.28

爲你的StrBlobPtr類添加加法和減法運算符,使其可以實現指針的算術運算。

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

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

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

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

練習題14.29

爲什麼不定義const版本的遞增和遞減運算符?

因爲遞增和遞減運算會改變對象,所以不能用const。

練習題14.30

爲你的StrBlobPtr類和練習12.22(P423)中定義的ConstStrBlobPtr類分別添加解引用運算符和箭頭運算符。注意:因爲ConstStrBlobPtr的數據成員指向const vector,所以ConstStrBlobPtr中的運算符必須返回常量引用。

// StrBlobPtr
std::string& operator* () const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}
std::string* operator->() const
{
    return &(this->operator*());
}
// ConstStrBlobPtr
inline const string* ConstStrBlobPtr::operator->() const
{
    return &this->operator*();
}
inline ConstStrBlobPtr& ConstStrBlobPtr::operator++()
{
    check(curr, "increment past end of ConstStrBlobPtr");
    ++curr;
    return *this;
}

練習題14.31

我們的StrBlobPtr類沒有定義拷貝構造函數、賦值運算符及析構函數,爲什麼?

因爲在StrBlobPtr中不需要申請或管理內存,使用合成版本就滿足需求。

練習題14.32

定義一個類令其含有指向StrBlobPtr對象的指針,爲這個類定義重載的箭頭運算符。

class StrBlobPtr_pointer
{
public:
    StrBlobPtr_pointer() = default;
    StrBlobPtr_pointer(StrBlobPtr* p) : pointer(p) { }
    StrBlobPtr& operator *() const;
    StrBlobPtr* operator->() const;
private:
    StrBlobPtr* pointer = nullptr;
};
StrBlobPtr&
StrBlobPtr_pointer::operator *() const
{
    return *pointer;
}

StrBlobPtr*
StrBlobPtr_pointer::operator ->() const
{
    return pointer;
}

練習題14.33

一個重載的函數調用運算符應該接受幾個運算對象?

重載運算符函數的參數數量和該運算符作用的運算對象的數量一樣多。函數調用運算符()最多可以傳256個參數,因此在重載時,最多也能接受256個參數用作運算。

練習題14.34

定義一個函數對象類,令其執行if-then-else的操作:該類的調用運算符接受三個形參,它首先檢查第一個形參,如果成功返回第二個形參的值;如果不成功返回第三個形參的值。

#include <iostream>
class TestClass
{
public:
    TestClass (int n) : num(n) {}
    int operator() (int x, int y, int z);
private:
    int num;
};
int TestClass::operator() (int x, int y, int z)
{
   return (x == num) ? y : z;
}
int main()
{
    TestClass tc(10);
    int x = tc(2, 8, 4);
    std::cout << x << std::endl;
}

練習題14.35

編寫一個類似於PrintString的類,令其從istream中讀取一行輸入,然後返回一個表示我們所讀內容的string。如果讀取失敗,返回空string。

#include <iostream>
#include <string>
class PrintString {
public:
    PrintString(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()
{
    PrintString getInput;
    std::cout << getInput() << std::endl;
}

測試:

test
test

練習題14.36

使用前一個練習定義的類讀取標準輸入,將每一行保存爲vector的一個元素。

#include <iostream>
#include <string>
#include <vector>
class PrintString  {
public:
    PrintString (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()
{
    PrintString  getInput;
    std::vector<std::string> vec;
    for ( std::string tmp; !( tmp = getInput() ).empty(); )
        vec.push_back( tmp );
    for (const auto &str : vec)
        std::cout << str << " ";
    std::cout << std::endl;
}

測試:

test train CNN
^Z
test train CNN

練習題14.37

編寫一個類令其檢查兩個值是否相等。使用該對象及標準庫算法編寫程序,令其替換某個序列中具有給定值的所有實例。

#include <iostream>
#include <vector>
#include <algorithm>
class IsEqual {
    int value;
public:
    IsEqual(int v) : value(v) { }
    bool operator()(int elem) {
        return elem == value;
    }
};
int main()
{
    std::vector<int> vec = { 0, 4, 3, 2, 1,7, 2, 2};
    std::replace_if(vec.begin(), vec.end(), IsEqual(2), -1);
    for (int i : vec)
        std::cout << i << " ";
    std::cout << std::endl;
}

測試:0 4 3 -1 1 7 -1 -1

練習題14.38

編寫一個類令其檢查某個給定的string對象的長度是否與一個閾值相等。使用該對象編寫程序,統計並報告在輸入的文件中長度爲1的單詞有多少個、長度爲2的單詞有多少個、…、長度爲10的單詞有多少個。

練習題14.39

修改上一題的程序令其報告長度在1至9之間的單詞有多少個、長度在10以上的單詞有多少個。

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <map>
struct IsInRange
{
    IsInRange(std::size_t lower, std::size_t upper)
        :_lower(lower), _upper(upper)
    { }
    bool operator()(std::string const& str) const
    {
        return str.size() >= _lower && str.size() <= _upper;
    }
    std::size_t lower_limit() const
    {
        return _lower;
    }
    std::size_t upper_limit() const
    {
        return _upper;
    }
private:
    std::size_t _lower;
    std::size_t _upper;
};
int main()
{
    //創建具有各種上限的謂詞。
    std::size_t lower = 1;
    auto uppers = { 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u };
    std::vector<IsInRange> predicates;
    for (auto upper : uppers)
        predicates.push_back(IsInRange{ lower, upper });
    //創建count_table來存儲計數
    std::map<std::size_t, std::size_t> count_table;
    for (auto upper : uppers)
        count_table[upper] = 0;
    //讀取文件並計數
    std::ifstream fin("DataStory.txt");
    for (std::string word; fin >> word; /* */)
        for (auto is_size_in_range : predicates)
            if (is_size_in_range(word))
                ++count_table[is_size_in_range.upper_limit()];
    //打印
    for (auto pair : count_table)
        std::cout << "count in range [1, " << pair.first << "] : " << pair.second << std::endl;
    return 0;
}

測試:

count in range [1, 3] : 61
count in range [1, 4] : 84
count in range [1, 5] : 92
count in range [1, 6] : 101
count in range [1, 7] : 107
count in range [1, 8] : 109
count in range [1, 9] : 109
count in range [1, 10] : 109
count in range [1, 11] : 109
count in range [1, 12] : 109
count in range [1, 13] : 109
count in range [1, 14] : 109

練習題14.40

重新編寫10.3.2節的biggies函數,使用函數對象類替換其中的lambda表達式。

#include <vector>
using std::vector;
#include <string>
using std::string;
#include <iostream>
using std::cout; using std::endl;
#include <algorithm>
using std::sort; using std::stable_sort; using std::for_each;
class ShorterString {
public:
    bool operator()(string const& s1, string const& s2) const { return s1.size() < s2.size(); }
};
class BiggerEqual {
    size_t sz_;
public:
    BiggerEqual(size_t sz) : sz_(sz) { }
    bool operator()(string const& s) { return s.size() >= sz_; }
};
class Print {
public:
    void operator()(string const& s) { cout << s << " "; }
};
string make_plural(size_t ctr, string const& word, string const& ending)
{
    return (ctr > 1) ? word + ending : word;
}
void elimDups(vector<string> &words) {
    sort(words.begin(), words.end());
    auto end_unique = unique(words.begin(), words.end());
    words.erase(end_unique, words.end());
}
void biggies( vector<string> &words, vector<string>::size_type sz ) {
    elimDups(words);
    stable_sort(words.begin(), words.end(), ShorterString());
    auto wc = find_if(words.begin(), words.end(), BiggerEqual(sz));
    auto count = words.end() - wc;
    cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl;
    for_each(wc, words.end(), Print());
    cout << endl;
}
int main()
{
    vector<string> vec{ "fox", "jumps", "over", "quick", "red", "red", "slow", "the", "turtle" };
    biggies(vec, 4);
}

測試:

5 words of length 4 or longer
over slow jumps quick turtle

練習題14.41

你認爲c++11新標準爲什麼要增加lambda?對於你自己來說,什麼情況下會使用lambda,什麼情況下會使用類?

當一個函數被頻繁調用的時候,使用lambda就會更簡單。

練習題14.42

使用標準庫函數對象及適配器定義一條表達式,令其
​ (a)統計大於1024的值有多少個。
​ (b)找到第一個不等於pooh的字符串。
​ (c)將所有的值乘以2。

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>
int main()
{
    using std::placeholders::_1;

    std::vector<int> ivec { 1, 100, 1000, 10000 };
    int count = std::count_if (ivec.cbegin(), ivec.cend(), std::bind(std::greater<int>(), _1, 1024));
    std::cout << count << std::endl;

    std::vector<std::string> svec { "pooh", "pooh", "push", "pooh" };
    auto found = std::find_if (svec.cbegin(), svec.cend(), std::bind(std::not_equal_to<std::string>(), _1, "pooh"));
    std::cout << *found << std::endl;

    std::transform(ivec.begin(), ivec.end(), ivec.begin(), std::bind(std::multiplies<int>(), _1, 2));
    for (int i : ivec) std::cout << i << " ";
    std::cout << std::endl;
}

測試:

1
push
2 200 2000 20000

練習題14.43

使用標準庫函數對象判斷一個給定的int值是否能被int容器中的所有元素整除。

#include <iostream>
#include <string>
#include <functional>
#include <algorithm>
int main()
{
    std::vector<int> ivec = {14, 2, 12, 8};
    int num;
    std::cin>>num;
    std::modulus<int> mod;
    auto is_divisible = std::any_of(ivec.begin(), ivec.end(), [&mod, &num](int i)->bool{ return 0 == mod(num, i);});
    std::cout << (is_divisible ? "Yes!" : "No!") << std::endl;
    return 0;
}

測試:

2
Yes!

練習題14.44

編寫一個簡單的桌面計算器使其能處理二元運算。

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

int add(int i, int j){ return i + j; }
auto mod = [](int i, int j){ return i % j; };
struct Div{ int operator ()(int i, int j) const { return i / j; } };

auto binops = std::map<std::string, std::function<int(int, int)>>
{
    { "+", add },                               // 函數指針
    { "-", std::minus<int>() },                 // 標準庫函數對象
    { "/", Div() },                             // 用戶定義函數對象
    { "*", [](int i, int j) { return i*j; } },  // 未命名的 lambda
    { "%", mod }                                // 命名的 lambda 對象
};


int main()
{
    while ( std::cout << "請輸入爲: 數字 操作符 數字 :\n", true )
    {
        int lhs, rhs; std::string op;
        std::cin >> lhs >> op >> rhs;
        std::cout << binops[op](lhs, rhs) << std::endl;
    }
    return 0;
}

測試:

請輸入爲: 數字 操作符 數字 :
10 + 5
15
請輸入爲: 數字 操作符 數字 :
10 - 5
5
請輸入爲: 數字 操作符 數字 :
5 * 8
40
請輸入爲: 數字 操作符 數字 :
15 / 3
5
請輸入爲: 數字 操作符 數字 :
15 % 4
3

練習題14.45

編寫類型轉換運算符將一個Sales_data對象分別轉換成string和double,你認爲這些運算符的返回值應該是什麼?

//.h文件
#include <string>
#include <iostream>
class Sales_data {
    friend std::istream& operator>>(std::istream&, Sales_data&);
    friend std::ostream& operator<<(std::ostream&, const Sales_data&);
    friend Sales_data operator+(const Sales_data&, const Sales_data&);
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 &is);
    Sales_data& operator=(const std::string&);
    Sales_data& operator+=(const Sales_data&);
    explicit operator std::string() const { return bookNo; }
    explicit operator double() const { return avg_price(); }
    std::string isbn() const { return bookNo; }
private:
    inline double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.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;
}
//.c文件
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)
{
    *this = Sales_data(isbn);
    return *this;
}
//測試文件
int main()
{
    Sales_data cp5("DL modlel Good", 8, 12.5);
    std::cout << cp5 << std::endl;
    std::cout << static_cast<std::string>(cp5) << std::endl;
    std::cout << static_cast<double>(cp5) << std::endl;
}

測試

DL modlel Good 8 100 12.5
DL modlel Good
12.5

練習題14.46

你認爲應該爲Sales_data類定義上面兩種類型轉換運算符嗎?應該把它們聲明成explicit的嗎?爲什麼?

可以,這樣能夠提供使用者一種容易的途徑去獲取編號或價格。應該要聲明爲explicit,避免使用者無意中隱式轉換從而產生意外的結果。

練習題14.47

說明下面這兩個類型轉換運算符的區別。
struct Integal {
operator const int();
operator int() const;
};

轉化爲const int類型:operator const int();
爲在函數內不改變對象的值,將對象轉化爲int類型:operator int() const;

練習題14.48

你在7.40練習中曾經選擇並編寫了一個類,你認爲它應該含有向bool類型轉換運算符嗎?如果是,解釋原因並說明該運算符是否應該是explicit的;如果不是,也請解釋原因。

    bool isLeapYear() const { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }

    explicit operator bool() const
    {
        vector<vector<int>> days_per_month = {{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
        return 1 <= month && month <= 12 && 1 <= day && day <= days_per_month[isLeapYear()? 1 : 0][month - 1];
    }
// 應該包含bool類型轉換運算符,可以用來檢驗date的值是否合法。
// 應該是explict,避免使用者無意中隱式轉換從而產生意外的結果。

練習題14.50

在初始化ex1和ex2的過程中,可能用到哪些類類型的轉換序列呢?說明初始化是否正確並解釋原因。

struct LongDouble {
    LongDouble (double = 0.0);
    operator double();
    operator float();
};
LongDouble ldObj;
int ex1 = ldObj;       
 // 產生二義性:在將ldObj轉換爲int時,類定義的類型轉換都無法精準匹配,可能先執行 operator double(), 再進行double到int的轉換。
// 也可能調用operator float(),再進行float到int的轉換。
float ex2 = ldObj;      
// 調用operator float()

練習題14.51

在調用calc的過程中,可能用到哪些類型轉換序列呢?說明最佳可行函數是如何被選出來的。

void calc(int);
void calc(LongDouble);
double dval;
calc(dval);  // 哪個calc?

calc是會優先調用calc(int),doube轉int是標準類型轉換,而LongDouble轉int是用戶自定義轉換。

練習題14.52

在下面的加法表達式中分別選用了哪個operator+?列出候選函數、可行函數及爲每個可行函數的實參執行的類型轉換:

struct LongDouble {
    LongDouble operator+ (const SmallInt&); // 1
};
LongDouble operator+(LongDouble&, double);  // 2
SmallInt si;
LongDouble ld;
ld = si + ld;
ld = ld + si;
  • ld = si + ld;具有二義性,調用1需將si轉換爲LongDouble,ld轉換爲SmallInt。調用2需要將si轉換爲LongDouble,ld轉換爲double。
  • ld = ld + si;精確匹配LongDouble operator+ (const SmallInt&);若調用LongDouble operator+(LongDouble&, double);還需將si轉換爲double。

練習題14.53

假設我們已經定義瞭如第522頁所示的SmallInt,判斷下面的加法表達式是否合法。如果合法,使用了哪個加法運算符?如果不合法,應該怎樣修改代碼才能使其合法?
SmallInt s1;
double d = s1 + 3.14;

由於內置的operator+(int, double)是可行的,同時3.14可以轉換爲int,然後再轉換爲SmallInt,所以SmallInt的成員operator+也是可行的。因爲兩者都需要進行類型轉換,所以會產生二義性。改爲:double d = s1 +SmallInt(3.14);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章