# Chapter 14 Overloaded Operations and Conversions

# Basic Concepts


# 基本概念

  • 重载运算符是具有特殊名字的函数:由关键字 operator 和其后要定义的运算符号共同组成。
  • 当一个重载的运算符是成员函数时, this 绑定到左侧运算对象。动态运算符符函数的参数数量比运算对象的数量少一个
  • 只能重载大多数的运算符,而不能发明新的运算符号。
  • 重载运算符的优先级和结合律跟对应的内置运算符保持一致。
  • 调用方式:
    • data1 + data2;
    • operator+(data1, data2);
  • 是否是成员函数:
    • 赋值( = )、下标( [] )、调用( () )和成员访问箭头( -> )运算符必须是成员。
    • 复合赋值运算符一般来说是成员。
    • 改变对象状态的运算符或者和给定类型密切相关的运算符通常是成员,如递增、解引用。
    • 具有对称性的运算符如算术、相等性、关系和位运算符等,通常是非成员函数。

运算符:

可以被重载不可以被重载
+ , - , * , / , % , ^:: , .* , . , ? : ,
& , | , ~ , ! , , , =
< , > , <= , >= , ++ , --
<< , >> , == , != , && , ||
+= , -= , /= , %= , ^= , &=
|=, *= , <<= , >>= , [] , ()
-> , ->* , new , new[] , delete , delete[]

# Exercise 14.1

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

解:

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

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

# Exercise 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

# Exercise 14.4

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

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

解:

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

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

# Input and Output Operators


# 输入和输出运算符

# 重载输出运算符 <<

  • 第一个形参通常是一个非常量的 ostream 对象的引用。非常量是因为向流中写入会改变其状态;而引用是因为我们无法复制一个 ostream 对象。
  • 输入输出运算符必须是非成员函数。

# 重载输入运算符 >>

  • 第一个形参通常是运算符将要读取的流的引用,第二个形参是将要读取到的(非常量)对象的引用。
  • 输入运算符必须处理输入可能失败的情况,而输出运算符不需要。

# Exercise 14.6

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

解:

参考 14.2。

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

# Exercise 14.8

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

解:

参考 14.5。

# Exercise 14.9

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

解:

参考 14.2。

# Exercise 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

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

解:

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

# Exercise 14.12

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

解:

参考 14.5。

# Arithmetic and Relational Operators


# 算数和关系运算符(+、-、*、/)

  • 如果类同时定义了算数运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算数运算符。

# 相等运算符 ==

  • 如果定义了 operator== ,则这个类也应该定义 operator!=
  • 相等运算符和不等运算符的一个应该把工作委托给另一个。
  • 相等运算符应该具有传递性。
  • 如果某个类在逻辑上有相等性的含义,则该类应该定义 operator== ,这样做可以使用户更容易使用标准库算法来处理这个类。

# 关系运算符

  • 如果存在唯一一种逻辑可靠的 < 定义,则应该考虑为这个类定义 < 运算符。如果同时还包含 == ,则当且晋档 < 的定义和 ++ 产生的结果一直时才定义 < 运算符。

# Exercise 14.13

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

解:

没有其他了。

# Exercise 14.14

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

解:

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

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

# Exercise 14.16

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

解:

# Exercise 14.17

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

解:

参考 14.15。

# Exercise 14.18

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

解:

# Exercise 14.19

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

解:

参考 14.15。

# Assignment Operators


# 赋值运算符 =

  • 我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。
  • 赋值运算符必须定义成类的成员,复合赋值运算符通常情况下也应该这么做。这两类运算符都应该返回左侧运算对象的引用。

# Exercise 14.20

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

解:

参考 14.2。

# Exercise 14.21

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

解:

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

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

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

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

# Exercise 14.25

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

解:

是。如上题。

# Subscript Operator


# 下标运算符 []

  • 下标运算符必须是成员函数。
  • 一般会定义两个版本:
    • 1. 返回普通引用。
    • 2. 类的常量成员,并返回常量引用。

# Exercise 14.26

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

解:

# Increment and Decrement Operators


# 递增和递减运算符(++、--)

  • 定义递增和递减运算符的类应该同时定义前置版本和后置版本。
  • 通常应该被定义成类的成员。
  • 为了和内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。
  • 同样为了和内置版本保持一致,后置运算符应该返回递增或递减前对象的值,而不是引用。
  • 后置版本接受一个额外的,不被使用的 int 类型的形参。因为不会用到,所以无需命名。

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

# Exercise 14.28

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

解:

参考 14.27。

# Exercise 14.29

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

解:

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

# Member Access Operators


# 成员访问运算符(*、->)

  • 箭头运算符必须是类的成员。解引用运算符通常也是类的成员,尽管并非必须如此。
  • 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
  • 解引用和乘法的区别是一个是一元运算符,一个是二元运算符。

# Exercise 14.30

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

解:

略。

# Exercise 14.31

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

解:

因为使用合成的足够了。

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

# Function-Call Operator


# 函数调用运算符

  • 可以像使用函数一样,调用该类的对象。因为这样对待类同时也能存储状态,所以与普通函数相比更加灵活。
  • 函数调用运算符必须是成员函数。
  • 一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。
  • 如果累定义了调用运算符,则该类的对象称作函数对象

# lambda 是函数对象

  • lambda 捕获变量: lambda 产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数。

# 标准库定义的函数对象

标准库函数对象:

算术关系逻辑
plus<Type>equal_to<Type>logical_and<Type>
minus<Type>not_equal_to<Type>logical_or<Type>
multiplies<Type>greater<Type>logical_not<Type>
divides<Type>greater_equal<Type>
modulus<Type>less<Type>
negate<Type>less_equal<Type>
  • 可以在算法中使用标准库函数对象。

# 可调用对象与 function

标准库 function 类型

操作解释
function<T> f;f 是一个用来存储可调用对象的空 function ,这些可调用对象的调用形式应该与类型 T 相同。
function<T> f(nullptr);显式地构造一个空 function
function<T> f(obj)f 中存储可调用对象 obj 的副本
ff 作为条件:当 f 含有一个可调用对象时为真;否则为假。
定义为 function<T> 的成员的类型
result_typefunction 类型的可调用对象返回的类型
argument_typeT 有一个或两个实参时定义的类型。如果 T 只有一个实参,则 argument_type
first_argument_type第一个实参的类型
second_argument_type第二个实参的类型
  • 例如:声明一个 function 类型,它可以表示接受两个 int ,返回一个 int 的可调用对象。 function<int(int, int)>

# Exercise 14.33

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

解:

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

# Exercise 14.34

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

解:

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

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

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

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

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

# Exercise 14.39

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

解:

参考 14.38。

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

# Exercise 14.41

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

解:

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

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

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

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

# Overloading, Conversions, and Operators


# 重载、类型转换、运算符

# 类型转换运算符

  • 类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式如下: operator type() const;
  • 一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常应该是 const
  • 避免过度使用类型转换函数。
  • C++11 引入了显式的类型转换运算符。
  • bool 的类型转换通常用在条件部分,因此 operator bool 一般定义成 explicit 的。

# 避免有二义性的类型转换

  • 通常,不要为类第几个亿相同的类型转换,也不要在类中定义两个及以上转换源或转换目标是算术类型的转换。
  • 在调用重载函数时,如果需要额外的标准类型转换,则该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型转换不止一个,则该调用具有二义性。

# 函数匹配与重载运算符

  • 如果 a 是一种类型,则表达式 a sym b 可能是:
    • a.operatorsym(b);
    • operatorsym(a,b);
  • 如果我们队同一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将会遇到重载运算符与内置运算符的二义性问题。

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

# Exercise 14.46

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

解:

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

# Exercise 14.47

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

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

解:

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

# Exercise 14.48

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

解:

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

# Exercise 14.49

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

解:

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

# Exercise 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 合法。

# Exercise 14.51

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

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

解:

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

转换的优先级如下:

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

# Exercise 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 两个都可以调用,但是第一个调用更精确一些。

# Exercise 14.53

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

SmallInt si;
double d = si + 3.14;

解:

不合法,存在二义性。

应该该为:

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

# Chapter Summary

🍓:)