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

发布了76 篇原创文章 · 获赞 44 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_24739717/article/details/104504432
今日推荐