笔记:
14.1 基本概念
当一个重载的运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显示)参数数量比运算对象的数量少一个。
逻辑与运算符、逻辑或运算符和逗号运算符以及&&、||运算符,这些运算符的重载版本无法保留求值顺序和/或短路求值属性,因此不建议重载它们。
一般不重载逗号运算符和取地址运算符。
赋值运算符和符合赋值运算符则应该返回左侧运算对象的一个引用。
14.2 输入和输出运算符(非成员)
输出运算符:第一个形参通常情况下是一个非常量ostream对象的引用,第二个形参一般来说是一个常量的引用。
输出运算符尽量减少格式化操作,不应该打印换行符。
输入输出运算符必须是非成员函数。
输入运算符第一个形参是运算符将要读取的流的引用,第二个形参是将要读取到的(非常量)对象的引用。
输入运算符必须处理输入可能失败的情况,而输出运算符不需要。
14.3 算术和关系运算符(非成员)
把算术和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换,形参都是常量的引用。
如果类同时定义了算术运算符和相关的符合赋值运算符,则通常情况下应该使用符合赋值运算符来实现算术运算符。
14.4 赋值运算符(成员)
其他重载的赋值运算符也必须先释放当前内存空间。
我们可以重载赋值运算符。无论形参的类型是什么,赋值运算符都必须定义为成员函数。
赋值运算符必须定义成类的成员,复合赋值运算符通常情况下也应该这样做。这两类运算符都应该返回左侧运算对象的引用。
14.5 下标运算符(成员)
最好同时定义下标运算符的常量版本和非常量版本。
14.6 递增和递减运算符(建议成员)
定义递增和递减运算符的类应该同时定义前置版本和后置版本。这些运算符通常应该被定义成类的成员。
前置运算符应该返回递增或递减后对象的引用;后置运算符应该返回对象的原值,返回形式是一个值而非引用。
为了区分前置版本和后置版本,后置版本接受一个额外的(不被使用)int类型的形参。
对于前置版本,要先检查有效性,对于后置版本,不用检查有效性,但是需要首先记录对象的状态。
14.7 成员访问运算符(成员)
箭头运算符必须是类的成员。解引用运算符通常也是类的成员,尽管并非必须如此。
箭头运算符永远不能丢掉成员访问这个最基本的含义。
重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
14.8 函数调用运算符(成员)
函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符。
如果类定义了调用运算符,则该类的对象称作函数对象。
函数对象常常作为泛型算法的实参。
lambda是函数对象。
标准库的函数对象,标准库规定函数对象对于指针同样适用。
C++语言的可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。
每个lambda有它自己唯一的(未命名)类类型。
14.9 重载、类型转换与运算符
类型转换运算符是类的一种特殊成员函数。
一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常应该是const。
对于类来说,定义向bool的类型转换还是比较普遍的现象。
向bool的类型转换通常用在条件部分,因此operator bool一般定义成explicit。
课后习题:
练习14.1:在什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样?
不同点:
重载运算符必须具有至少一个class或枚举类型的操作数。
重载运算符不保证操作数的求值顺序,例如对&&和||的重载版本不再具有“短路求值”的特性,两个操作数都要求值,而且不规定操作数的求值顺序。
相同点:
对于优先级和结合性级操作数的数目都不变。
练习14.2:为Sales_data 编写重载的输入、输出、加法和复合赋值运算符的声明。
class Sales_data
{
friend istream& operator>>(istream&, Sales_data&);
friend ostream& operator<<(ostream&, Sales_data&);
public:
Sales_data& operator+=(const Sales_data&); //成员函数,比一般的非成员函数少一个参数
};
Sales_data operator+(const Sales_data&, const Sales_data&);
练习14.3:string 和vector 都定义了重载的=以比较各自的对象, 假设svec1和svec2 是存放string的vector,确定在下面的表达式中分别使用了哪个版本的==?
(a)"cobble" == "stone" (b)svec1[0] == svec2[0]
(c)svec1 = svec2 (d)svec1[0] == "stone"
(a)"cobble" == "stone"应用了C++语言内置版本的==,比较两个指针。
(b)svec1[0] == svec2[0]应用了string版本的重载==。
(c)svec1 = svec2应用了vector版本的重载==。
(d)svec1[0] == "stone"应用了string版本的重载==,字符串字面常量被转换成string。
练习14.4:如何确定下列运算符是否应该是类的成员?
(a) % (b) %= (c) ++ (d)-> (e) << (f) && (g) == (h) ()
(a)%通常定义为非成员。
(b)%=通常定义为类成员,因为它会改变对象的状态。
(c)++通常定义为类成员,因为它会改变对象的状态。
(d)->必须为类成员,否则编译会报错。
(e)<<通常定义为非成员。
(f)&&通常定义为非成员。
(g)==通常定义为非成员。
(h)( )必须为类成员,否则编译会报错。
练习14.5:在7.5.1 节的练习7.40 (第261 页)中,编写了下列类中某一个的框架,请问在这个类中应该定义重载的运算符吗?如果是,请写出来。
(a)Book (b)Date (c)Employee
(d)Vehicle (e)Object (f)Tree
// 练习 14.5
#include <iostream>
using namespace std;
class Date
{
public:
Date();
Date(int y, int m, int d){ year = y; month = m; day = d; }
friend ostream& operator<<(ostream &os, const Date &d);
private:
int year, month, day;
};
ostream& operator<<(ostream &os, const Date &d)
{
const char sep = '\t';
os << "year:" << d.year << sep << "month:" << d.month << sep << "day:" << d.day << sep << endl;
return os;
}
int main()
{
Date data(2019, 5, 29);
cout << data;
system("pause");
return 0;
}
练习14.6:为你的Sales_data 类定义输出运算符。
// 练习 14.6
#include <iostream>
using namespace std;
class Sales_data
{
friend ostream& operator<<(ostream &os, const Sales_data &item);
};
ostream& operator<<(ostream &os, const Sales_data &item)
{
const char sep = ' ';
os << item.isbn() << sep << item.units_sold << sep <<
item.revenue << sep << item.avg_price();
return os;
}
练习14.7:你在13. 5 节的练习(第470 页)中曾经编写一个String 类,为它定义一个输出运算符。
// 练习 14.7
#include <iostream>
using namespace std;
class String
{
public:
String();
String(const char *str);
friend ostream& operator<<(ostream &os, const String &str);
private:
char *str;
};
ostream& operator<<(ostream &os, const String &str)
{
cout << str;
return os;
}
练习14.8:你在7.5.1 节的练习7.40 (第261 页)中曾经选择并编写了一个类,为它定义一个输出运算符。
同练习14.5。
练习14.9:为你的Sales_data 类定义输入运算符。
// 练习 14.9
#include <iostream>
using namespace std;
class Sales_data
{
friend istream& operator>>(istream &is, Sales_data &item);
};
istream& operator>>(istream &is, Sales_data &item)
{
double price;
is >> item.bookNo >> item.units_sold >> price;
if (is)
{
item.revenue = item.units_sold * price;
}
else
{
item = Sales_data();
}
return is;
}
练习14.10:对于Sales_data 的输入运算符来说如果给定了下面的输入将发生什么情况?
(a) 0-201-99999-9 10 24.95 (b)10 24.95 0-210-99999-9
(a)参数中传入的Sales_data对象将会得到输入值,其中bookNo、units_sold、price的值分别是:0-201-99999-9、10、24.95,同时revenue的值是249.5。
(b)输入错误,参数中传入的Sales_data对象将会得到默认值。
练习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;
item = Sales_data();
return in;
}
这个实现没有判断输入数据的正确性,是错误的。
(a)如果输入的是0-201-99999-9 10 24.95,程序不会报错,Sales_data能得到正确的值。
(b)如果输入的是10 24.95 0-201-99999-9,Sales_data会得到错误的值。
练习14.12:你在7.5.1 节的练习7.40 (第261 页)中曾经选择并编写了一个类, 为它定义一个输入运算符并确保该运算符可以处理输入错误。
// 练习 14.12
#include <iostream>
using namespace std;
class Date
{
public:
Date();
Date(int y, int m, int d){ year = y; month = m; day = d; }
friend istream& operator>>(istream &is, Date &d);
private:
int year, month, day;
};
istream& operator>>(istream &is, Date &d)
{
is >> d.year >> d.month >> d.day;
if (!is)
{
d = Date(0, 0, 0);
}
return is;
}
练习14.13:你认为Sales_data 类还应该支持哪些其他算术运算符(参见表4.1,第124 页)?如果有的话,请给出它们的定义。
// 练习 14.13
class Sales_data
{
friend Sales_data operator-(const Sales_data &lhs, const Sales_data &rhs);
public:
Sales_data& operator-=(const Sales_data &rhs);
//其他成员
};
Sales_data operator-(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sub = lhs;
sub -= rhs;
return sub;
}
Sales_data& Sales_data::operator-=(const Sales_data &rhs)
{
units_sold -= rhs.units_sold;
revenue -= rhs.revenue;
return *this;
}
练习14.14:你觉得为什么调用operator+=来定义operator+比其他方法更有效?
答:从头实现operator+的方式与借助operator+=实现的方式相比,在性能上没有优势,而可读性上后者显然更好。
练习14.15:你在7.5.1 节的练习7.40 (第261 页)中曾经选择并编写了一个类,你认为它应该含有其他算术运算符吗?如果是, 请实现它们;如果不是,解释原因。
答:算术运算对Date没有太大意义,不需要为Date重载算术运算符。
练习14.16:为你的StrBlob 类(参见12.1.1 节,第405 页)、StrBlobPtr 类(参见12.1.6 节,第421 页) 、StrVec类(参见13.5 节, 第465 页)和String 类(参见13.5节,第470 页)分别定义相等运算符和不相等运算符。
// 练习 14.16
//StrBlob
class StrBlob
{
friend bool operator==(const StrBlob &lhs, const StrBlob &rhs);
friend bool operator!=(const StrBlob &lhs, const StrBlob &rhs);
//其他成员
};
bool operator==(const StrBlob &lhs, const StrBlob &rhs)
{
return lhs.data == rhs.data;
}
bool operator!=(const StrBlob &lhs, const StrBlob &rhs)
{
return !(lhs == rhs);
}
//StrBlobPtr
class StrBlobPtr
{
friend bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
friend bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
//其他成员
};
bool operator==(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
if (l == r)
return (!r || lhs.curr == rhs.curr);
else
return false;
}
bool operator!=(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
return !(lhs == rhs);
}
//StrVec
class StrVec
{
friend bool operator==(const StrVec &lhs, const StrVec &rhs);
friend bool operator!=(const StrVec &lhs, const StrVec &rhs);
//其他成员
};
bool operator==(const StrVec &lhs, const StrVec &rhs)
{
if (lhs.size() == rhs.size())
return false;
for (auto itr1 = lhs.begin(), itr2 = rhs.begin(); itr1 != lhs.end() && itr2 != rhs.end(); ++itr1, ++itr2)
{
if (*itr1 != *itr2)
return false;
}
return true;
}
bool operator!=(const StrVec &lhs, const StrVec &rhs)
{
return !(lhs == rhs);
}
//String
class String
{
friend bool operator==(const String &lhs, const String &rhs);
friend bool operator!=(const String &lhs, const String &rhs);
//其他成员
private:
const char *str;
};
bool operator==(const String &lhs, const String &rhs)
{
return strcmp(lhs.str, rhs.str);
}
bool operator!=(const String &lhs, const String &rhs)
{
return !(lhs == rhs);
}
练习14.17:你在7.5.1 节的练习 .40(第261 页)中曾经选择并编写了一个类,你认为它应该含有相等运算符吗?如果是,请实现它; 如果不是, 解释原因。
// 练习 14.19
#include <iostream>
using namespace std;
class Date
{
public:
Date();
Date(int y, int m, int d){ year = y; month = m; day = d; }
friend bool operator==(const Date &rhs, const Date &lhs);
friend bool operator!=(const Date &rhs, const Date &lhs);
private:
int year, month, day;
};
bool operator==(const Date &rhs, const Date &lhs)
{
return rhs.year == lhs.year && rhs.month == lhs.month &&
rhs.day == lhs.day;
}
bool operator!=(const Date &rhs, const Date &lhs)
{
return !(rhs == lhs);
}
练习14.18:为你的StrBlob 类、StrBlobPtr 类、StrVec 类和String 类定义关系运算符。
// 练习 14.18
class String
{
friend bool operator<(const String &s1, const String &s2);
friend bool operator<=(const String &s1, const String &s2);
friend bool operator>(const String &s1, const String &s2);
friend bool operator>=(const String &s1, const String &s2);
//其他成员
};
friend bool operator<(const String &s1, const String &s2)
{
return strcmp(s1.str, s2.str) < 0;
}
friend bool operator<=(const String &s1, const String &s2)
{
return strcmp(s1.str, s2.str) <= 0;
}
friend bool operator>(const String &s1, const String &s2)
{
return strcmp(s1.str, s2.str) > 0;
}
friend bool operator>=(const String &s1, const String &s2)
{
return strcmp(s1.str, s2.str) >= 0;
}
class StrBlob
{
friend bool operator<(const StrBlob &s1, const StrBlob &s2);
friend bool operator<=(const StrBlob &s1, const StrBlob &s2);
friend bool operator>(const StrBlob &s1, const StrBlob &s2);
friend bool operator>=(const StrBlob &s1, const StrBlob &s2);
};
bool operator<(const StrBlob &s1, const StrBlob &s2)
{
return *s1.data < *s2.data;
}
bool operator<=(const StrBlob &s1, const StrBlob &s2)
{
return *s1.data <= *s2.data;
}
bool operator>(const StrBlob &s1, const StrBlob &s2)
{
return *s1.data > *s2.data;
}
bool operator>=(const StrBlob &s1, const StrBlob &s2)
{
return *s1.data >= *s2.data;
}
class StrBlobPtr
{
friend operator<(const StrBlobPtr &s1, const StrBlobPtr &s2);
friend operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2);
friend operator>(const StrBlobPtr &s1, const StrBlobPtr &s2);
friend operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2);
};
bool operator<(const StrBlobPtr &s1, const StrBlobPtr &s2)
{
auto l = s1.wptr.lock(), r = s2.wptr.lock();
if (l == r)
{
if (!r)
return false;
return (s1.curr < s2.curr);
}
else
return false;
}
bool operator<=(const StrBlobPtr &s1, const StrBlobPtr &s2)
{
auto l = s1.wptr.lock(), r = s2.wptr.lock();
if (l == r)
return (!r || s1.curr <= s2.curr);
else
return false;
}
bool operator>(const StrBlobPtr &s1, const StrBlobPtr &s2)
{
auto l = s1.wptr.lock(), r = s2.wptr.lock();
if (l == r)
{
if (!r)
return false;
return (s1.curr > s2.curr);
}
else
return false;
}
bool operator>=(const StrBlobPtr &s1, const StrBlobPtr &s2)
{
auto l = s1.wptr.lock(), r = s2.wptr.lock();
if (l == r)
return (!r || s1.curr >= s2.curr);
else
return false;
}
class StrVec
{
friend operator<(const StrVec &s1, const StrVec &s2);
friend operator<=(const StrVec &s1, const StrVec &s2);
friend operator>(const StrVec &s1, const StrVec &s2);
friend operator>=(const StrVec &s1, const StrVec &s2);
//其他成员
};
bool operator<(const StrVec &s1, const StrVec &s2)
{
for (auto p1 = s1.begin(), p2 = s2.begin(); p1 != s1.end(), p2 != s2.end(); ++p1, ++p2)
{
if (*p1 < *p2)
return true;
else if (*p1 > *p2)
return false;
}
if (p1 == s1.end() && p2 != s2.end())
return true;
return false;
}
bool operator<=(const StrVec &s1, const StrVec &s2)
{
for (auto p1 = s1.begin(), p2 = s2.begin(); p1 != s1.end(), p2 != s2.end(); ++p1, ++p2)
{
if (*p1 < *p2)
return true;
else if (*p1 > *p2)
return false;
}
if (p1 == s1.end())
return true;
return false;
}
bool operator>(const StrVec &s1, const StrVec &s2)
{
for (auto p1 = s1.begin(), p2 = s2.begin(); p1 != s1.end(), p2 != s2.end(); ++p1, ++p2)
{
if (*p1 < *p2)
return false;
else if (*p1 > *p2)
return true;
}
if (p1 == s1.end() && p2 != s2.end())
return true;
return false;
}
bool operator>=(const StrVec &s1, const StrVec &s2)
{
for (auto p1 = s1.begin(), p2 = s2.begin(); p1 != s1.end(), p2 != s2.end(); ++p1, ++p2)
{
if (*p1 < *p2)
return false;
else if (*p1 > *p2)
return true;
}
if (p2 == s2.end())
return true;
return false;
}
练习14.19:你在7.5.1 节的练习7.40 (第261 页)中曾经选择并编写了一个类,你认为它应该含有关系运算符吗?如果是, 请实现它; 如果不是, 解释原因。
// 练习 14.19
#include <iostream>
using namespace std;
class Date
{
friend bool operator<(const Date &d1, const Date &d2);
friend bool operator<=(const Date &d1, const Date &d2);
friend bool operator>(const Date &d1, const Date &d2);
friend bool operator>=(const Date &d1, const Date &d2);
//其他成员
};
bool operator<(const Date &d1, const Date &d2)
{
return (d1.year < d2.year) || (d1.year == d2.year && d1.month < d2.month) || (d1.year == d2.year && d1.month == d2.month && d1.day < d2.day);
}
bool operator<=(const Date &d1, const Date &d2)
{
return (d1 < d2) || (d1 == d2);
}
bool operator>(const Date &d1, const Date &d2)
{
return !(d1 <= d2);
}
bool operator>=(const Date &d1, const Date &d2)
{
return (d1 > d2) || (d1 == d2);
}
练习14.20:为你的Sales_data 类定义加法和复合赋值运算符。
// 练习 14.20
class Sales_data
{
friend Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs);
public:
Sales_data& operator+=(const Sales_data &rhs);
//其他成员
};
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 Sales_data &rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
练习14.21:编写Sales data 类的+和+=运算符,使得+执行实际的加法操作而+=调用+ 。相比于14. 3 节(第497 页)和14.4 节(第500 页)对这两个运算符的定义,本题的定义有何缺点?试讨论之。
// 练习 14.21
class Sales_data
{
friend Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs);
public:
Sales_data& operator+=(const Sales_data &rhs);
//其他成员
};
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs;
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return sum;
}
}
Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
*this = (*this) + rhs;
}
在性能上没有优势,可读性也不好。
练习14.22:定义赋值运算符的一个新版本,使得我们能把一个表示ISBN 的string赋给一个Sales_data 对象。
// 练习 14.22
class Sales_data
{
public:
Sales_data& operator=(const string &isbn);
//其他成员
};
Sales_data& Sales_data::operator=(const string &isbn)
{
bookNo = isbn;
return *this;
)
练习14.23:为你的StrVec 类定义一个initializer_list 赋值运算符。
// 练习 14.23
class StrVec
{
public:
StrVec& operator=(std::initializer_list<std::string> il);
//其他成员
};
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.24:你在7.5.1 节的练习7.40 (第261 页)中曾经选择并编写了一个类,你认为它应该含有拷贝赋值和移动赋值运算符吗?如果是,请实现它们。
答:我们定义的Date类它只有三个int类型的数据成员,浅拷贝就能满足要求,因此不需要另外定义拷贝赋值和移动赋值运算符。
练习14.25:上题的这个类还需要定义其他赋值运算符吗?如果是,请实现它们;同时说明运算对象应该是什么类型并解释原因。
我们希望用string形式的日期来初始化Date,就需要定一个接收string的赋值运算符。
// 练习 14.25
class Date
{
public:
Date& operator=(const string &date);
//其他成员
};
Date& Sales_data::operator=(const string &date)
{
istringstream in(date);
char ch1, cha2;
in >> year >> ch1 >> month >> ch2 >> day;
if (!in || ch1 != '-' || ch2 != '-')
throw std::invalid_argument("Bad date");
if (month < 1 || month >12 || day < 1 || day > 31)
throw std::invalid_argument("Bad date");
return *this;
}
练习14.26:为你的StrBlob 类、StrBlobPtr 类、StrVec 类和String 类定义下标运算符。
// 练习 14.26
class StrBlob
{
public:
std::string& operator[](std::size_t n) { return data[n]; }
const std::string& operator[](std::size_t n) const { return data[n]; }
};
class StrBlobPtr
{
std::string& operator[](std::size_t n) { return (*wptr.lock())[n]; }
const std::string& operator[](std::size_t n) const { return (*wptr.lock())[n]; }
};
class StrVec
{
public:
std::string& operator[])(std::size_t n) { return elements[n]; }
const std::string& operator[])(std::size_t n) const { return elements[n]; }
};
class String
{
public:
char& operator[](std::size_t n) { return (char) str[n]; }
const char& operator[](std::size_t n) const { return (char) str[n]; }
private:
char *str;
}
练习14.27:为你的StrBlobPtr 类添加递增和递减运算符。
// 练习 14.27
class StrBlobPtr
{
//前缀
StrBlobPtr& operator++();
StrBlobPtr& operator--();
//后缀
StrBlobPtr operator++(int);
StrBlobPtr operator--(int);
};
StrBlobPtr& StrBlobPtr::operator++()
{
check(curr, " increment past end of StrBlobPtr ");
++curr;
return *this;
}
StrBlobPtr& 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 类添加加法和减法运算符,使其可以实现指针的算术运算(参见3.5.3 节,第106 页)。
// 练习14.28
class StrBlobPtr
{
friend StrBlobPtr operator+(int n);
friend StrBlobPtr operator-(int n);
//其他成员
};
StrBlobPtr StrBlobPtr::operator+(int n)
{
auto ret = *this;
ret.curr += n;
return ret;
}
StrBlobPtr StrBlobPtr::operator-(int n)
{
auto ret = *this;
ret.curr -= n;
return ret;
}
练习14.29:为什么不定义const 版本的递增和递减运算符?
答:对于++和--运算符,无论他是前缀版本还是后缀版本,都会改变对象本身的值,因此不能定义成const的。
练习14.30:为你的StrBlobPtr 类和在12.1.6 节练习12.22(第423 页)中定义的ConstStrBlobPtr 类分别添加解引用运算符和箭头运算符。注意:因为ConstStrBlobPtr 的数据成员指向const vector ,所以ConstStrBlobPtr 中的运算符必须返回常量引用。
// 练习 14.30
#include <iostream>
using namespace std;
class StrBlobPtr
{
public:
std::string& operator*() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
std::string* operator->() const
{
return &(this->operator*());
}
};
class ConstStrBlobPtr
{
public:
const std::string& operator*() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
const std::string* operator->() const
{
return &(this->operator*());
}
};
练习14.31:我们的StrBlobPtr 类没有定义拷贝构造函数、赋值运算符及析构函数,为什么?
答:对于StrBlobPtr类,它的数据成员有两个,分别是weak_ptr<vector<string>>和size_t类型的,前者定义了自己的拷贝构造函数、赋值运算符和析构函数,后者是内置类型,因此默认的拷贝语义即可,无须为StrBlobPtr定义拷贝构造函数、赋值运算符和析构函数。
练习14.32:定义一个类令其含有指向StrBlobPtr 对象的指针,为这个类定义重载的箭头运算符。
// 练习 14.32
class Myclass
{
public:
std::string* operator->() const
{
return ptr->operator->();
}
private:
StrBlobPtr *ptr;
};
练习14.33:一个重载的函数调用运算符应该接受几个运算对象?
答:0或多个。
练习14.34:定义一个函数对象类,令其执行if-then-else 的操作:该类的调用运算符接受三个形参,它首先检查第一个形参,如果成功返回第二个形参的值;如果不成功返回第三个形参的值。
// 练习 14.34
class IfElseThen
{
public:
IfElseThen() { }
IfElseThen(int i1, int i2, int i3) : iVal1(i1), iVal2(i2), iVal3(i3) { }
int operator()(int i1, int i2, int i3)
{
return i1 ? i2 : i3;
}
private:
int iVal1, iVal2, iVal3;
};
练习14.35:编写一个类似于PrintString 的类,令其从istream 中读取一行输入,然后返回一个表示我们所读内容的string 。如果读取失败,返回空string 。
// 练习 14.35
class ReadString
{
public:
ReadString(istream &is = cin) : is(is) { }
string operator()()
{
string line;
if (!getline(is, line))
{
line = " ";
}
return line;
}
private:
istream &is;
};
练习14.36:使用前一个练习定义的类读取标准输入,将每一行保存为vector 的一个元素。
// 练习 14.35
void testReadString()
{
ReadString rs;
vector<string> vec;
while (true)
{
string line = rs();
if (!line.empty())
{
vec.push_back(line);
}
else
break;
}
}
练习14.37:编写一个类令其检查两个值是否相等。使用该对象及标准库算法编写程序,令其替换某个序列中具有给定值的所有实例。
// 练习 14.37
class IntCompare
{
public:
IntCompare(int v) : val(v) { }
bool operator()(int v) { return val == v; }
private:
int val;
};
int main()
{
vector<int> vec = { 1, 2, 3, 2, 1 };
const int oldValue = 2;
const int newValue = 200;
IntCompare icmp(oldValue);
std::replace_if(vec.begin(), vec.end(), icmp, newValue);
return 0;
}
练习14.38:编写一个类令其检查某个给定的string 对象的长度是否与一个阈值相等。使用该对象编写程序, 统计并报告在输入的文件中长度为1的单词有多少个、长度为2的单词有多少个、……、长度为10 的单词又有多少个。
// 练习 14.38
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class StrLenIs
{
public:
StrLenIs(int len) : len(len) { }
bool operator()(const string &str) { return str.length() == len; }
private:
int len;
};
void readStr(istream &is, vector<string> &vec)
{
string word;
while (is >> word)
{
vec.push_back(word);
}
}
int main()
{
vector<string> vec;
readStr(cin, vec);
const int minLen = 1;
const int maxLen = 10;
for (int i = minLen; i <= maxLen; ++i)
{
StrLenIs slenIs(i);
cout << "len: " << i << ", cnt: " << count_if(vec.begin(), vec.end(), slenIs) << endl;
}
system("pause");
return 0;
}
练习14.39:修改上一题的程序令其报告长度在1至9 之间的单词有多少个、长度在10以上的单词又有多少个。
// 练习 14.39
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class StrLenBetween
{
public:
StrLenBetween(int minLen, int maxLen) : minLen(minLen), maxLen(maxLen) { }
bool operator()(const string &str) { return str.length() >= minLen && str.length() < maxLen; }
private:
int minLen, maxLen;
};
class StrNotShorterThan
{
public:
StrNotShorterThan(int minLen) : minLen(minLen) { }
bool operator()(const string &str) { return str.length() >= minLen; }
private:
int minLen;
};
void readStr(istream &is, vector<string> &vec)
{
string word;
while (is >> word)
{
vec.push_back(word);
}
}
int main()
{
vector<string> vec;
readStr(cin, vec);
const int minLen = 1;
const int maxLen = 10;
StrLenBetween slenBetween(minLen, maxLen);
StrNotShorterThan sNotShorterThan(maxLen);
cout << "len 1-9 : " << count_if(vec.begin(), vec.end(), slenBetween) << endl;
cout << "len >= 10 : " << count_if(vec.begin(), vec.end(), sNotShorterThan) << endl;
system("pause");
return 0;
}
练习14.40:重新编写10.3.2 节(第349 页)的biggies 函数,使用函数对象类替换其中的lambda 表达式。
// 练习 14.40
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <fstream>
using namespace std;
string make_plural(size_t ctr, const string &word, const string &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());
}
class stableSort
{
public:
stableSort() { };
bool operator()(const string &a, const string &b)
{
return a.size() < b.size();
}
};
class findIf
{
public:
findIf(size_t n) : sz(n) { }
bool operator()(const string &a){ return a.size() >= sz; }
private:
size_t sz;
};
class forEach
{
public:
forEach() { };
bool operator()(const string &s) { cout << s << " "; }
};
void biggies(vector<string> &words, vector<string>::size_type sz)
{
stableSort sSort;
findIf fIf(sz);
forEach fEach;
elimdups(words); //将words按字典序排序,删除重复词
// 按长度排序,长度相同的单词维持字典序
stable_sort(words.begin(), words.end(), sSort);
// 获取一个迭代器,指向第一个满足size()>=sz的元素
auto wc = find_if(words.begin(), words.end(), fIf);
// 计算满足size()>=sz的元素的数目
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl;
// 打印长度大于等于给定值的单词,每个单词后面接一个空格
for_each(wc, words.end(), fEach);
cout << endl;
}
int main(int argc, char *argv[])
{
ifstream in(argv[1]);
if (!in){
cout << "Open input file failed." << endl;
return -1;
}
string str;
vector<string> vec;
while (in >> str)
vec.push_back(str);
biggies(vec, 5);
system("pause");
return 0;
}
练习14.41:你认为C++11 新标准为什么要增加lambda ?对于你自己来说,什么情况下会使用lambda,什么情况下会使用类?
答:在C++11中,lambda是通过匿名的函数对象来实现的,因此我们可以把lambda看作是对函数对象在使用方式上进行的简化。当代码需要一个简单的函数,并且这个函数并不会在其他地方被使用时,就可以使用lambda来实现,此时它所起的作用类似于匿名函数。但如果这个函数需要多次使用,并且它需要保存某些状态的话,使用函数对象更合适一些。
练习14.42:使用标准库函数对象及适配器定义一条表达式, 令其
(a) 统计大于1024 的值有多少个。
(b)找到第一个不等于pooh 的字符串。
(c)将所有的值乘以2 。
(a)count_if(vec.begin(), vec.end() bind2nd(greater<int>(), 1024));
(b)find_if(vec.begin(), vec.end() bind2nd(not_equal_to<string>(), "pooh")
(c)transform(vec.begin(), vec.end() vec.begin(), bind2nd(multiplies<int>(), 2))
练习14.43:使用标准库函数对象判断一个给定的int 值是否能被int 容器中的所有元素整除。
bool dividedByAll(vector<int> &ivec, int dividend)
{
return count_if(ivec.begin(), ivec.end(), bind2nd(modulus<int>(), dividend)) == 0;
}
练习14.44:编写一个简单的桌面计算器使其能处理二元运算。
function<int(int, int)>> binops =
{
{ "+", plus<int>() },
{ "-", minus<int>() },
{ "*", multiplies<int>() },
{ "/", divides<int>() },
{ "%", modulus<int>() }
};
int main()
{
int a, b;
string op;
cin >> a >> op >> b;
cout << binops[op](a, b) << endl;
system("pause");
return 0;
}
练习14.45:编写类型转换运算符将一个Sales_data 对象分别转换成string 和double ,你认为这些运算符的返回值应该是什么?
// 练习 14.45
class Sales_data
{
public:
operator string() const { return bookNo; }
operator double() const { return revenue; }
private:
string bookNO;
double revenue;
};
练习14.46:你认为应该为Sales_data 类定义上面两种类型转换运算符吗?应该把它们声明成explicit 的吗?为什么?
答:Sales_data不应该定义这两种类型转换运算符,因为对于类来说,它包含三个数据成员:bookNo,units_sold和revenue,只有三者在一起才是有效的数据。但是如果确实想要定义这两个类型转换运算符的话,应该把它们声明成explicit的,这样可以防止sales_data 在默写情况下被默认转换成string或double类型,这有可能导致意料之外的运算结果。
练习14.47:说明下面这两个类型转换运算符的区别。
struct
{
operator const int();
operator int() const;
}
答:前者将对象转换成const int,在接受const int值的地方才能够使用。
后者则将对象转换成int值,相对来说更加通用一些。
练习14.48:你在7.5.1 节的练习7.40 (第261 页)中曾经选择并编写了一个类,你认为它应该含有向bool 的类型转换运算符吗?如果是, 解释原因并说明该运算符是否应该是explicit 的:如果不是,也请解释原因。
答:我们可以为Date提供一个bool类型的转换运算符,用来检查3个数据成员是否都是有效值,bool类型转换运算符应该声明为explicit的,因为我们是有意要在条件表达式中使用它的。
练习14.49:为上一题提到的类定义一个转换目标是bool 的类型转换运算符,先不用在意这么做是否应该。
// 练习 14.49
class Date
{
explicit operator bool()
{
vector<vector<int>> days_per_month = { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 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 isLeapYear()
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
};
练习14.50:在初始化ex1 和ex2 的过程中,可能用到哪些类类型的转换序列呢?说明初始化是否正确并解释原因。
struct LongDouble
{
LongDouble(double = 0.0);
operator double();
operator float();
};
LongDouble ldObj;
int ex1 = ldObj;
float ex2 = ldObj;
对于int ex1 = ldObj;,它需要把LongDouble类型转换成int类型,但是LongDouble并没有定义对应的类型转换运算符,因此它会尝试使用其他的来进行转换。题中给出的两个都满足需求,但编译器无法确定那一个更合适,因此会产生二义性错误。
对于foloat ex2 = ldObj;,它需要把LongDouble转换成float类型,而我们恰好定义了对应的类型转换运算符,因此直接调用operator float()即可。
练习14.51:在调用calc 的过程中, 可能用到哪些类型转换序列呢?说明最佳可行函数是如何被选出来的。
void calc(int);
void calc(LongDouble);
double dval;
calc(dval);
答:这里会优先调用void calc(int)函数。因为double转换为int是标准类型转换,而转换为LongDouble则是转换为用户自定义类型,实际上调用了转换构造函数,因此前者优先。
练习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,由于LongDouble不能转换为SmallInt,因此Smallint的成员operator+和friend operator都不可行。
由于Smallint不能转换为LongDouble,LongDouble的成员operator+和非成员operator+也都不可行。
由于SmallInt可以转换为int, LongDouble了可以转换为float和double,所以内置的operator+(int, float)和operator+(int, double)都可行,会产生二义性。
对于ld=ld+si,类似上一个加法表达式,由于Smallint不能转换为double,LongDouble也不能转换为SmallInt,因此SmallInt的成员operator+和两个非成员operator+都不匹配。
LongDouble的成员operator+可行,且为精确匹配。
SmallInt可以转换为int,longDouble可以转换为float和double,因此内置的operator+(float, int)和operator(double, int)都可行。但它们都需要类型转换,因此LongDouble的成员operator+优先匹配。
练习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);即可。