Skip to content

Latest commit

 

History

History
2105 lines (1600 loc) · 43.6 KB

ch14.md

File metadata and controls

2105 lines (1600 loc) · 43.6 KB

第十四章 重载运算与类型转换

练习14.1

在什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样?

解:

我们可以直接调用重载运算符函数。重置运算符与内置运算符有一样的优先级与结合性。

练习14.2

Sales_data 编写重载的输入、输出、加法和复合赋值运算符。

解:

头文件:

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

主函数:

#include "ex_14_02.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;
}

练习14.3

stringvector 都定义了重载的==以比较各自的对象,假设 svec1svec2 是存放 stringvector,确定在下面的表达式中分别使用了哪个版本的==

(a) "cobble" == "stone"
(b) svec1[0] == svec2[0]
(c) svec1 == svec2
(d) svec1[0] == "stone"

解:

  • (a) 都不是。
  • (b) string
  • (c) vector
  • (d) string

练习14.4

如何确定下列运算符是否应该是类的成员?

(a) %
(b) %=
(c) ++
(d) ->
(e) <<
(f) &&
(g) ==
(h) ()

解:

  • (a) 不需要是成员。
  • (b) 是成员。
  • (c) 是成员。
  • (d) 必须是成员。
  • (e) 不需要是成员。
  • (f) 不需要是成员。
  • (g) 不需要是成员。
  • (h) 必须是成员。

练习14.5

在7.5.1节中的练习7.40中,编写了下列类中某一个的框架,请问在这个类中应该定义重载的运算符吗?如果是,请写出来。

(a) Book
(b) Date
(c) Employee
(d) Vehicle
(e) Object
(f) Tree

解:

Book,应该重载。

头文件:

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

实现:

#include "ex_14_5.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);
}

测试:

#include "ex_14_5.h"

int main()
{
	Book book1(123, "CP5", "Lippman", "2012");
	Book book2(123, "CP5", "Lippman", "2012");

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

练习14.6

为你的 Sales_data 类定义输出运算符。

解:

参考14.2。

练习14.7

你在13.5节的练习中曾经编写了一个String类,为它定义一个输出运算符。

解:

头文件:

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

实现:

#include "ex_14_7.h"
#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 << "copy constructor" << 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 << "copy-assignment" << std::endl;
	return *this;
}

std::ostream& operator<<(std::ostream &os, const String &s)
{
	char *c = const_cast<char*>(s.c_str());
	while (*c)
		os << *c++;
	return os;
}

测试:

#include "ex_14_7.h"

int main()
{
	String str("Hello World");
	std::cout << str << std::endl;
}

练习14.8

你在7.5.1节中的练习中曾经选择并编写了一个类,为它定义一个输出运算符。

解:

�参考14.5。

练习14.9

为你的 Sales_data 类定义输入运算符。

解:

参考14.2。

练习14.10

对于 Sales_data 的输入运算符来说如果给定了下面的输入将发生什么情况?

(a) 0-201-99999-9 10 24.95
(b) 10 24.95 0-210-99999-9

解:

  • (a) 格式正确。
  • (b) 不合法的输入。因为程序试图将 0-210-99999-9 转换为 float

练习14.11

下面的 Sales_data 输入运算符存在错误吗?如果有,请指出来。对于这个输入运算符如果仍然给定上个练习的输入将会发生什么情况?

istream& operator>>(istream& in, Sales_data& s)
{
	double price;
	in >> s.bookNo >> s.units_sold >> price;
	s.revence = s.units_sold >> price;
	return in;
}

解:

没有输入检查,什么也不会发生。

练习14.12

你在7.5.1节的练习中曾经选择并编写了一个类,为它定义一个输入运算符并确保该运算符可以处理输入错误。

解:

�参考14.5。

练习14.13

你认为 Sales_data 类还应该支持哪些其他算术运算符?如果有的话,请给出它们的定义。

解:

没有其他了。

练习14.14

你觉得为什么调用 operator+= 来定义operator+ 比其他方法更有效?

解:

因为用 operator+= 会避免使用一个临时对象,而使得更有效。

练习14.15

你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有其他算术运算符吗?如果是,请实现它们;如果不是,解释原因。

解:

头文件:

#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&);
	friend bool operator<(const Book&, const Book&);
	friend bool operator>(const Book&, const Book&);
	friend Book operator+(const Book&, const Book&);

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

	Book& operator+=(const Book&);

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

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&);
bool operator<(const Book&, const Book&);
bool operator>(const Book&, const Book&);
Book operator+(const Book&, const Book&);

实现:

#include "ex_14_15.h"

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

std::ostream& operator<<(std::ostream &out, const Book &book)
{
	out << book.no_ << " " << book.name_ << " " << book.author_ << " " << book.pubdate_ << " " << book.number_ << std::endl;
	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 < lhs;
}

Book& Book::operator+=(const Book &rhs)
{
	if (rhs == *this)
		this->number_ += rhs.number_;

	return *this;
}

Book operator+(const Book &lhs, const Book &rhs)
{
	Book book = lhs;
	book += rhs;
	return book;
}

测试:

#include "ex_14_15.h"

int main()
{
	Book cp5_1(12345, "CP5", "Lippmen", "2012", 2);
	Book cp5_2(12345, "CP5", "Lippmen", "2012", 4);

	std::cout << cp5_1 + cp5_2 << std::endl;
}

练习14.16

为你的 StrBlob 类、StrBlobPtr 类、StrVec 类和 String 类分别定义相等运算符和不相等运算符。

解:

练习14.17

你在7.5.1节中的练习7.40中曾经选择并编写了一个类,你认为它应该含有相等运算符吗?如果是,请实现它;如果不是,解释原因。

解:

参考14.15。

练习14.18

为你的 StrBlob 类、StrBlobPtr 类、StrVec 类和 String 类分别定义关系运算符。

解:

练习14.19

你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有关系运算符吗?如果是,请实现它;如果不是,解释原因。

解:

参考14.15。

练习14.20

为你的 Sales_data 类定义加法和复合赋值运算符。

解:

参考14.2。

练习14.21

编写 Sales_data 类的++= 运算符,使得 + 执行实际的加法操作而 += 调用+。相比14.3节和14.4节对这两个运算符的定义,本题的定义有何缺点?试讨论之。

解:

缺点:使用了一个 Sales_data 的临时对象,但它并不是必须的。

练习14.22

定义赋值运算符的一个新版本,使得我们能把一个表示 ISBNstring 赋给一个 Sales_data 对象。

解:

头文件:

#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&);
	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;
}

实现:

#include "ex_14_22.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)
{
	*this = Sales_data(isbn);
	return *this;
}

测试:

#include "ex_4_22.h"

int main()
{
	std::string strCp5("C++ Primer 5th");
	Sales_data cp5 = strCp5;
	std::cout << cp5 << std::endl;
}

练习14.23

为你的StrVec 类定义一个 initializer_list 赋值运算符。

解:

头文件:

#include <memory>
#include <string>
#include <initializer_list>

#ifndef _MSC_VER
#define NOEXCEPT noexcept
#else
#define NOEXCEPT
#endif

class StrVec
{
	friend bool operator==(const StrVec&, const StrVec&);
	friend bool operator!=(const StrVec&, const StrVec&);
	friend bool operator< (const StrVec&, const StrVec&);
	friend bool operator> (const StrVec&, const StrVec&);
	friend bool operator<=(const StrVec&, const StrVec&);
	friend bool operator>=(const StrVec&, const StrVec&);

public:
	StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
	StrVec(std::initializer_list<std::string>);
	StrVec(const StrVec&);
	StrVec& operator=(const StrVec&);
	StrVec(StrVec&&) NOEXCEPT;
	StrVec& operator=(StrVec&&)NOEXCEPT;
	~StrVec();

	StrVec& operator=(std::initializer_list<std::string>);

	void push_back(const std::string&);
	size_t size() const { return first_free - elements; }
	size_t capacity() const { return cap - elements; }
	std::string *begin() const { return elements; }
	std::string *end() const { return first_free; }

	std::string& at(size_t pos) { return *(elements + pos); }
	const std::string& at(size_t pos) const { return *(elements + pos); }

	void reserve(size_t new_cap);
	void resize(size_t count);
	void resize(size_t count, const std::string&);

private:
	std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
	void free();
	void chk_n_alloc() { if (size() == capacity()) reallocate(); }
	void reallocate();
	void alloc_n_move(size_t new_cap);
	void range_initialize(const std::string*, const std::string*);

private:
	std::string *elements;
	std::string *first_free;
	std::string *cap;
	std::allocator<std::string> alloc;
};

bool operator==(const StrVec&, const StrVec&);
bool operator!=(const StrVec&, const StrVec&);
bool operator< (const StrVec&, const StrVec&);
bool operator> (const StrVec&, const StrVec&);
bool operator<=(const StrVec&, const StrVec&);
bool operator>=(const StrVec&, const StrVec&);

实现:

#include "ex_14_23.h"
#include <algorithm>

void StrVec::push_back(const std::string &s)
{
	chk_n_alloc();
	alloc.construct(first_free++, s);
}

std::pair<std::string*, std::string*>
StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
	auto data = alloc.allocate(e - b);
	return{ data, std::uninitialized_copy(b, e, data) };
}

void StrVec::free()
{
	if (elements)
	{
		for_each(elements, first_free, [this](std::string &rhs) { alloc.destroy(&rhs); });
		alloc.deallocate(elements, cap - elements);
	}
}

void StrVec::range_initialize(const std::string *first, const std::string *last)
{
	auto newdata = alloc_n_copy(first, last);
	elements = newdata.first;
	first_free = cap = newdata.second;
}

StrVec::StrVec(const StrVec &rhs)
{
	range_initialize(rhs.begin(), rhs.end());
}

StrVec::StrVec(std::initializer_list<std::string> il)
{
	range_initialize(il.begin(), il.end());
}

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

StrVec& StrVec::operator = (const StrVec &rhs)
{
	auto data = alloc_n_copy(rhs.begin(), rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

void StrVec::alloc_n_move(size_t new_cap)
{
	auto newdata = alloc.allocate(new_cap);
	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 + new_cap;
}

void StrVec::reallocate()
{
	auto newcapacity = size() ? 2 * size() : 1;
	alloc_n_move(newcapacity);
}

void StrVec::reserve(size_t new_cap)
{
	if (new_cap <= capacity()) return;
	alloc_n_move(new_cap);
}

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

void StrVec::resize(size_t count, const std::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(StrVec &&s) NOEXCEPT : elements(s.elements), first_free(s.first_free), cap(s.cap)
{
	// leave s in a state in which it is safe to run the destructor.
	s.elements = s.first_free = s.cap = nullptr;
}

StrVec& StrVec::operator = (StrVec &&rhs) NOEXCEPT
{
	if (this != &rhs)
	{
		free();
		elements = rhs.elements;
		first_free = rhs.first_free;
		cap = rhs.cap;
		rhs.elements = rhs.first_free = rhs.cap = nullptr;
	}
	return *this;
}

bool operator==(const StrVec &lhs, const StrVec &rhs)
{
	return (lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()));
}

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

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

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

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

StrVec& StrVec::operator=(std::initializer_list<std::string> il)
{
	*this = StrVec(il);
	return *this;
}

测试:

#include "ex_14_23.h"
#include <vector>
#include <iostream>

int main()
{
	StrVec vec;
	vec.reserve(6);
	std::cout << "capacity(reserve to 6): " << vec.capacity() << std::endl;

	vec.reserve(4);
	std::cout << "capacity(reserve to 4): " << vec.capacity() << std::endl;

	vec.push_back("hello");
	vec.push_back("world");

	vec.resize(4);

	for (auto i = vec.begin(); i != vec.end(); ++i)
		std::cout << *i << std::endl;
	std::cout << "-EOF-" << std::endl;

	vec.resize(1);

	for (auto i = vec.begin(); i != vec.end(); ++i)
		std::cout << *i << std::endl;
	std::cout << "-EOF-" << std::endl;

	StrVec vec_list{ "hello", "world", "pezy" };

	for (auto i = vec_list.begin(); i != vec_list.end(); ++i)
		std::cout << *i << " ";
	std::cout << std::endl;

	// Test operator==

	const StrVec const_vec_list = { "hello", "world", "pezy" };
	if (vec_list == const_vec_list)
	for (const auto &str : const_vec_list)
		std::cout << str << " ";
	std::cout << std::endl;

	// Test operator<
	const StrVec const_vec_list_small = { "hello", "pezy", "ok" };
	std::cout << (const_vec_list_small < const_vec_list) << std::endl;
}

练习14.24

你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有拷贝赋值和移动赋值运算符吗?如果是,请实现它们。

解:

头文件:

#ifndef DATE_H
#define DATE_H

#ifndef _MSC_VER
#define NOEXCEPT noexcept
#else
#define NOEXCEPT
#endif

#include <iostream>
#include <vector>

class Date
{
	friend  bool            operator ==(const Date& lhs, const Date& rhs);
	friend  bool            operator < (const Date &lhs, const Date &rhs);
	friend  bool            check(const Date &d);
	friend  std::ostream&   operator <<(std::ostream& os, const Date& d);
public:
	typedef std::size_t Size;

	// default constructor
	Date() = default;
	// constructor taking Size as days
	explicit Date(Size days);
	// constructor taking three Size
	Date(Size d, Size m, Size y) : day(d), month(m), year(y) {}
	// constructor taking iostream
	Date(std::istream &is, std::ostream &os);

	// copy constructor
	Date(const Date& d);
	// move constructor
	Date(Date&& d) NOEXCEPT;

	// copy operator=
	Date& operator= (const Date& d);
	// move operator=
	Date& operator= (Date&& rhs) NOEXCEPT;

	// destructor  --  in this case, user-defined destructor is not nessary.
	~Date() { std::cout << "destroying\n"; }

	// members
	Size toDays() const;  //not implemented yet.
	Date& operator +=(Size offset);
	Date& operator -=(Size offset);


private:
	Size    day = 1;
	Size    month = 1;
	Size    year = 0;
};

static const Date::Size YtoD_400 = 146097;    //365*400 + 400/4 -3 == 146097
static const Date::Size YtoD_100 = 36524;    //365*100 + 100/4 -1 ==  36524
static const Date::Size YtoD_4 = 1461;    //365*4 + 1          ==   1461
static const Date::Size YtoD_1 = 365;    //365

// normal year
static const std::vector<Date::Size> monthsVec_n =
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// leap year
static const std::vector<Date::Size> monthsVec_l =
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// non-member operators:  <<  >>  -   ==  !=  <   <=  >   >=
//
std::ostream&
operator <<(std::ostream& os, const Date& d);
std::istream&
operator >>(std::istream& is, Date& d);
int
operator - (const Date& lhs, const Date& rhs);
bool
operator ==(const Date& lhs, const Date& rhs);
bool
operator !=(const Date& lhs, const Date& rhs);
bool
operator < (const Date& lhs, const Date& rhs);
bool
operator <=(const Date& lhs, const Date& rhs);
bool
operator  >(const Date& lhs, const Date& rhs);
bool
operator >=(const Date& lhs, const Date& rhs);
Date
operator - (const Date& lhs, Date::Size  rhs);
Date
operator  +(const Date& lhs, Date::Size  rhs);



//  utillities:
bool check(const Date &d);
inline bool
isLeapYear(Date::Size y);




// check if the date object passed in is valid
inline bool
check(const Date &d)
{
	if (d.month == 0 || d.month >12)
		return false;
	else
	{
		//    month == 1 3 5 7 8 10 12
		if (d.month == 1 || d.month == 3 || d.month == 5 || d.month == 7 ||
			d.month == 8 || d.month == 10 || d.month == 12)
		{
			if (d.day == 0 || d.day > 31) return false;
			else
				return true;
		}
		else
		{
			//    month == 4 6 9 11
			if (d.month == 4 || d.month == 6 || d.month == 9 || d.month == 11)
			{
				if (d.day == 0 || d.day > 30) return false;
				else
					return true;
			}
			else
			{
				//    month == 2
				if (isLeapYear(d.year))
				{
					if (d.day == 0 || d.day >29)  return false;
					else
						return true;
				}
				else
				{
					if (d.day == 0 || d.day >28)  return false;
					else
						return true;
				}
			}
		}
	}
}

inline bool
isLeapYear(Date::Size y)
{
	if (!(y % 400))
	{
		return true;
	}
	else
	{
		if (!(y % 100))
		{
			return false;
		}
		else
			return !(y % 4);
	}
}
#endif // DATE_H

实现:

#include "ex_14_24.h"
#include <algorithm>

// constructor taking Size as days
// the argument must be within (0, 2^32)
Date::Date(Size days)
{
	// calculate the year
	Size y400 = days / YtoD_400;
	Size y100 = (days - y400*YtoD_400) / YtoD_100;
	Size y4 = (days - y400*YtoD_400 - y100*YtoD_100) / YtoD_4;
	Size y = (days - y400*YtoD_400 - y100*YtoD_100 - y4*YtoD_4) / 365;
	Size d = days - y400*YtoD_400 - y100*YtoD_100 - y4*YtoD_4 - y * 365;
	this->year = y400 * 400 + y100 * 100 + y4 * 4 + y;

	// check if leap and choose the months vector accordingly
	std::vector<Size>currYear
		= isLeapYear(this->year) ? monthsVec_l : monthsVec_n;

	// calculate day and month using find_if + lambda
	Size D_accumu = 0, M_accumu = 0;
	// @bug    fixed:  the variabbles above hade been declared inside the find_if as static
	//                 which caused the bug. It works fine now after being move outside.

	std::find_if(currYear.cbegin(), currYear.cend(), [&](Size m)
	{

		D_accumu += m;
		M_accumu++;

		if (d < D_accumu)
		{
			this->month = M_accumu;
			this->day = d + m - D_accumu;

			return true;
		}
		else
			return false;
	});
}

// construcotr taking iostream
Date::Date(std::istream &is, std::ostream &os)
{
	is >> day >> month >> year;

	if (is)
	{
		if (check(*this)) return;
		else
		{
			os << "Invalid input! Object is default initialized.";
			*this = Date();
		}
	}
	else
	{
		os << "Invalid input! Object is default initialized.";
		*this = Date();
	}

}

// copy constructor
Date::Date(const Date &d) :
day(d.day), month(d.month), year(d.year)
{}

// move constructor
Date::Date(Date&& d) NOEXCEPT :
day(d.day), month(d.month), year(d.year)
{ std::cout << "copy moving"; }

// copy operator=
Date &Date::operator= (const Date &d)
{
	this->day = d.day;
	this->month = d.month;
	this->year = d.year;

	return *this;
}

// move operator=
Date &Date::operator =(Date&& rhs) NOEXCEPT
{
	if (this != &rhs)
	{
		this->day = rhs.day;
		this->month = rhs.month;
		this->year = rhs.year;
	}
	std::cout << "moving =";

	return *this;
}

// conver to days
Date::Size Date::toDays() const
{
	Size result = this->day;

	// check if leap and choose the months vector accordingly
	std::vector<Size>currYear
		= isLeapYear(this->year) ? monthsVec_l : monthsVec_n;

	// calculate result + days by months
	for (auto it = currYear.cbegin(); it != currYear.cbegin() + this->month - 1; ++it)
		result += *it;

	// calculate result + days by years
	result += (this->year / 400)      * YtoD_400;
	result += (this->year % 400 / 100)  * YtoD_100;
	result += (this->year % 100 / 4)    * YtoD_4;
	result += (this->year % 4)        * YtoD_1;

	return result;
}

// member operators:   +=  -=

Date &Date::operator +=(Date::Size offset)
{
	*this = Date(this->toDays() + offset);
	return *this;
}

Date &Date::operator -=(Date::Size offset)
{
	if (this->toDays() > offset)
		*this = Date(this->toDays() - offset);
	else
		*this = Date();

	return *this;
}

// non-member operators:  <<  >>  -   ==  !=  <   <=  >   >=

std::ostream&
operator <<(std::ostream& os, const Date& d)
{
	os << d.day << " " << d.month << " " << d.year;
	return os;
}

std::istream&
operator >>(std::istream& is, Date& d)
{
	if (is)
	{
		Date input = Date(is, std::cout);
		if (check(input))    d = input;
	}
	return is;
}


int operator -(const Date &lhs, const Date &rhs)
{
	return lhs.toDays() - rhs.toDays();
}


bool operator ==(const Date &lhs, const Date &rhs)
{
	return (lhs.day == rhs.day) &&
		(lhs.month == rhs.month) &&
		(lhs.year == rhs.year);
}


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


bool operator < (const Date &lhs, const Date &rhs)
{
	return lhs.toDays() < rhs.toDays();
}


bool operator <=(const Date &lhs, const Date &rhs)
{
	return (lhs < rhs) || (lhs == rhs);
}


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


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


Date operator - (const Date &lhs, Date::Size rhs)
{                                       //  ^^^ rhs must not be larger than 2^32-1
	// copy lhs
	Date result(lhs);
	result -= rhs;

	return result;
}


Date operator + (const Date &lhs, Date::Size rhs)
{                                       //  ^^^ rhs must not be larger than 2^32-1
	// copy lhs
	Date result(lhs);
	result += rhs;

	return result;
}

测试:

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

int main()
{

	Date lhs(9999999), rhs(1);

	std::cout << (lhs -= 12000) << "\n";

	return 0;
}

练习14.25

上题的这个类还需要定义其他赋值运算符吗?如果是,请实现它们;同时说明运算对象应该是什么类型并解释原因。

解:

是。如上题。

练习14.26

为你的 StrBlob 类、StrBlobPtr 类、StrVec 类和 String 类定义下标运算符。

解:

练习14.27

为你的 StrBlobPtr 类添加递增和递减运算符。

解:

只显示添加的代码:

class StrBlobPtr {
public:
    string& deref() const;
    StrBlobPtr& operator++();
    StrBlobPtr& operator--();
    StrBlobPtr operator++(int);
    StrBlobPtr operator--(int);
    StrBlobPtr& operator+=(size_t);
    StrBlobPtr& operator-=(size_t);
    StrBlobPtr operator+(size_t) const;
    StrBlobPtr operator-(size_t) const;
};

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

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

inline StrBlobPtr StrBlobPtr::operator++(int)
{
    StrBlobPtr ret = *this;
    ++*this;
    return ret;
}

inline StrBlobPtr StrBlobPtr::operator--(int)
{
    StrBlobPtr ret = *this;
    --*this;
    return ret;
}

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.28

为你的 StrBlobPtr 类添加加法和减法运算符,使其可以实现指针的算术运算。

解:

参考14.27。

练习14.29

为什么不定义const 版本的递增和递减运算符?

解:

因为递增和递减会改变对象本身,所以定义 const 版本的毫无意义。

练习14.30

为你的 StrBlobPtr 类和在12.1.6节练习12.22中定义的 ConstStrBlobPtr 的类分别添加解引用运算符和箭头运算符。注意:因为 ConstStrBlobPtr 的数据成员指向const vector,所以ConstStrBlobPtr 中的运算符必须返回常量引用。

解:

略。

练习14.31

我们的 StrBlobPtr 类没有定义拷贝构造函数、赋值运算符以及析构函数,为什么?

解:

因为使用合成的足够了。

练习14.32

定义一个类令其含有指向 StrBlobPtr 对象的指针,为这个类定义重载的箭头运算符。

解:

头文件:

class StrBlobPtr;

class StrBlobPtr_pointer
{
public:
    StrBlobPtr_pointer() = default;
    StrBlobPtr_pointer(StrBlobPtr* p) : pointer(p) { }

    StrBlobPtr& operator *() const;
    StrBlobPtr* operator->() const;

private:
    StrBlobPtr* pointer = nullptr;
};

实现:

#include "ex_14_32.h"
#include "ex_14_30_StrBlob.h"
#include <iostream>

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

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

练习14.33

一个重载的函数调用运算符应该接受几个运算对象?

解:

一个重载的函数调用运算符接受的运算对象应该和该运算符拥有的操作数一样多。

练习14.34

定义一个函数对象类,令其执行if-then-else 的操作:该类的调用运算符接受三个形参,它首先检查第一个形参,如果成功返回第二个形参值;如果不成功返回第三个形参的值。

解:

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

练习14.35

编写一个类似于 PrintString 的类,令其从 istream 中读取一行输入,然后返回一个表示我们所读内容的string。如果读取失败,返回空string

解:

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

练习14.36

使用前一个练习定义的类读取标准输入,将每一行保存为 vector 的一个元素。

解:

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

练习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> vec = { 3, 2, 1, 4, 3, 7, 8, 6 };
	std::replace_if(vec.begin(), vec.end(), IsEqual(3), 5);
	for (int i : vec) std::cout << i << " ";
	std::cout << std::endl;
}

练习14.38

编写一个类令其检查某个给定的 string 对象的长度是否与一个阈值相等。使用该对象编写程序,统计并报告在输入的文件中长度为1的单词有多少个,长度为2的单词有多少个、......、长度为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()
{
	//create predicates with various upper limits.
	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 });

	//create count_table to store counts 
	std::map<std::size_t, std::size_t> count_table;
	for (auto upper : uppers)
		count_table[upper] = 0;

	//read file and count
	std::ifstream fin("storyDataFile.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()];

	//print
	for (auto pair : count_table)
		std::cout << "count in range [1, " << pair.first << "] : " << pair.second << std::endl;

	return 0;
}

练习14.39

修改上一题的程序令其报告长度在1到9之间的单词有多少个、长度在10以上的单词有多少个。

解:

参考14.38。

练习14.40

重新编写10.3.2节的biggies 函数,使用函数对象替换其中的 lambda 表达式。

解:

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

using namespace std;

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

练习14.41

你认为 C++ 11 标准为什么要增加 lambda?对于你自己来说,什么情况下会使用 lambda,什么情况下会使用类?

解:

使用 lambda 是非常方便的,当需要使用一个函数,而这个函数不常使用并且简单时,使用lambda 是比较方便的选择。

练习14.42

使用标准库函数对象及适配器定义一条表达式,令其

(a) 统计大于1024的值有多少个。 
(b) 找到第一个不等于pooh的字符串。
(c) 将所有的值乘以2。

解:

std::count_if(ivec.cbegin(), ivec.cend(), std::bind(std::greater<int>(), _1, 1024));
std::find_if(svec.cbegin(), svec.cend(), std::bind(std::not_equal_to<std::string>(), _1, "pooh"));
std::transform(ivec.begin(), ivec.end(), ivec.begin(), std::bind(std::multiplies<int>(), _1, 2));

练习14.43

使用标准库函数对象判断一个给定的int值是否能被 int 容器中的所有元素整除。

解:

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

int main()
{
	auto data = { 2, 3, 4, 5 };
	int input;
	std::cin >> input;
	std::modulus<int> mod;
	auto predicator = [&](int i) { return 0 == mod(input, i); };
	auto is_divisible = std::any_of(data.begin(), data.end(), predicator);
	std::cout << (is_divisible ? "Yes!" : "No!") << std::endl;

	return 0;
}

练习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 },                               // function pointer 
	{ "-", std::minus<int>() },                 // library functor 
	{ "/", Div() },                             // user-defined functor 
	{ "*", [](int i, int j) { return i*j; } },  // unnamed lambda 
	{ "%", mod }                                // named lambda object 
};


int main()
{
	while (std::cout << "Pls enter as: num operator num :\n", true)
	{
		int lhs, rhs; std::string op;
		std::cin >> lhs >> op >> rhs;
		std::cout << binops[op](lhs, rhs) << std::endl;
	}

	return 0;
}

练习14.45

编写类型转换运算符将一个 Sales_data 对象分别转换成 stringdouble,你认为这些运算符的返回值应该是什么?

解:

头文件:

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

练习14.46

你认为应该为 Sales_data 类定义上面两种类型转换运算符吗?应该把它们声明成 explicit 的吗?为什么?

解:

上面的两种类型转换有歧义,应该声明成 explicit 的。

练习14.47

说明下面这两个类型转换运算符的区别。

struct Integral {
	operator const int();
	operator int() const;
}

解:

第一个无意义,会被编译器忽略。第二个合法。

练习14.48

你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有向 bool 的类型转换运算符吗?如果是,解释原因并说明该运算符是否应该是 explicit的;如果不是,也请解释原因。

解:

Date 类应该含有向 bool 的类型转换运算符,并且应该声明为 explicit 的。

练习14.49

为上一题提到的类定义一个转换目标是 bool 的类型转换运算符,先不用在意这么做是否应该。

解:

 explicit operator bool() { return (year<4000) ? true : false; }

练习14.50

在初始化 ex1ex2 的过程中,可能用到哪些类类型的转换序列呢?说明初始化是否正确并解释原因。

struct LongDouble {
	LongDouble(double = 0.0);
	operator double();
	operator float();
};
LongDouble ldObj;
int ex1 = ldObj;
float ex2 = ldObj;

解:

ex1 转换不合法,没有定义从 LongDoubleint 的转换,从double转换还是float转换存在二义性。ex2 合法。

练习14.51

在调用 calc 的过程中,可能用到哪些类型转换序列呢?说明最佳可行函数是如何被选出来的。

void calc(int);
void calc(LongDouble);

double dval;
calc(dval);  // 调用了哪个calc?

解:

最佳可行函数是 void calc(int)

转换的优先级如下:

  1. 精确匹配
  2. const 转换。
  3. 类型提升
  4. 算术转换
  5. 类类型转换

练习14.52

在下面的加法表达式中分别选用了哪个operator+?列出候选函数、可行函数及为每个可行函数的实参执行的类型转换:

struct Longdouble {
	//用于演示的成员operator+;在通常情况下是个非成员
	LongDouble operator+(const SmallInt&);
	//其他成员与14.9.2节一致
};
LongDouble operator+(LongDouble&, double);
SmallInt si;
LongDouble ld;
ld = si + ld;
ld = ld + si;

解:

ld = si + ld; 不合法。ld = ld + si 两个都可以调用,但是第一个调用更精确一些。

练习14.53

假设我们已经定义了如第522页所示的SmallInt,判断下面的加法表达式是否合法。如果合法,使用了哪个加法运算符?如果不合法,应该怎样修改代码才能使其合法?

SmallInt si;
double d = si + 3.14;

解:

不合法,存在二义性。

应该该为:

SmallInt s1;
double d = s1 + SmallInt(3.14);