C++primer第14章习题解答
2016-04-19 22:07
351 查看
练习14.1:在什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样?
不同点:
重载运算符必须具有至少一个class或枚举类型的操作数。
重载运算符不保证操作数的求值顺序,例如对&&和||的重载版本不再具有“短路求值”的特性,两个操作数都要求值,而且不规定操作数的求值顺序。
相同点:
对于优先级和结合性级操作数的数目都不变。
练习14.2:为Sales_data编写重载的输入、输出、加法和复合赋值运算符的声明。
练习14.3:string和vector都定义了重载的==以比较各自的对象,假设svec1和svec2是存放string的vector,确定在下面的表达式中分别使用了哪个版本的==?
(a) "cobble" == "stone" (b) svec1[0] ==svec2[0] (c)svec1 ==svec2 (d) svec[0] == "stone"
(a)应用了C++内置版本的==,比较两个指针。(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
练习14.6:为你的Sales_data类定义输出运算符。
练习14.7:你在13.5节的练习中曾经编写了一个String类,为它定义一个输出运算符。
练习14.8:你在7.51节的练习7.40中曾经选择并编写了一个类,为它定义一个输出运算符。
见练习14.5。
练习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输入运算符存在错误吗?如果有,请指出来。对于这个输入运算符如果仍然给定上一个练习的输入将发生什么情况?
这个实现没有判断输入数据的正确性,是错误的。
(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节的练习中曾经选择并编写了一个类,为它定义一个输入运算符并确保该运算符可以处理输入错误。
练习14.13:你认为Sales_data类还应该支持 哪些其他算术运算符?如果有的话,请给出它们的定义。
可以定义一个减法运算符
练习14.14:你觉得为什么调用operator+=来定义operator+比其他方法要更有效?
从头实现operator+的方式与借助operator+=实现的方式相比,在性能上没有优势,而可读性上后者显然更好。
练习14.15:你在7.5.1节的练习7.40中曾经选择并编写了一个类。你认为它应该含有其他算术运算符吗?如果是,请实现它们;如果不是,解释原因。
在练习7.40中,变写了Date类,算术运算对Date没有太大意义,不需要为Date重载算术运算符。
练习14.16:为你的StrBlob类、StrBlobPtr类、StrVec类和String类分别定义相等和不相等运算符。
练习14.17:你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有相等运算符吗?如果是,请实现它;如果不是,解释原因。
14.18:为你的StrBlob类、StrBlobPtr类、StrVec类、String类定义关系运算符。
练习14.19:你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有关系运算符吗?如果是,请实现它;如果不是,解释原因。
练习14.20:为你的Sales_data类实现加法和复合赋值运算符。
练习14.21:编写Sales_data类的+和+=运算符,是的+执行实际的加法操作,而+=调用+。相比于14.3节和14.4节对这两个运算符的定义,本题的定义有何缺点?试讨论之。
在性能上没有优势,可读性也不好。
练习14.22:定义赋值运算符的一个新版本,使得我们能把一个表示ISBN的string赋给一个Sales_data对象。
练习14.23:为你的StrVec类定义一个initializer_list赋值运算符。
练习14.24:你在7.5.1节的脸7.40中曾经选择并编写了一个类,你认为它应该含有拷贝赋值和移动赋值运算符吗?如果是,请实现它们。
再联系7.40中,我们变细了Date类,它只有三个int类型的数据成员,浅拷贝就能满足要求,因此不需要另外定义拷贝赋值和移动赋值运算符。
练习14.25:上题的这个类还需要定义其他赋值运算符吗?如果是,请实现它们;同时说明运算对象应该是什么类型并解释原因。
练习14.26:为你的StrBlob类、StrBlobPtr类,StrVec类和String类定义下标运算符。
练习14.27:为你的StrBlobPtr类添加递增和递减运算符。
练习14.28:为你的StrBlobPtr类添加加法和减法运算符,使其可以实现指针的算术运算。
练习14.29:为什么不定义const版本的递增和递减运算符?
对于++和--运算符,无论他是前缀版本还是后缀版本,都会改变对象本身的值,因此不能定义成const的。
练习14.30:为你的StrBlob类和在12.1.6节练习12.22中定义的ConstStrBlobPtr类分别添加解引用运算符和箭头运算符。注意:因为ConstStrBlobPtr的数据成员指向const vector,所以ConstStrBlobPtr中的运算符必须返回常量引用。
练习14.31:我们的StrBlobPtr类没有定义拷贝构造函数、赋值运算符和析构函数,为什么?
对于StrBlobPtr类,它的数据成员有两个,分别是weak_ptr<vector<string>>和size_t类型的,前者定义了自己的拷贝构造函数、赋值运算符和析构函数,后者是内置类型,因此默认的拷贝语义即可,无须为StrBlobPtr定义拷贝构造函数、赋值运算符和析构函数。
练习14.32:定义一个类令其含有指向StrBlobPtr对象的指针,为这个类定义重载的建投运算符。
练习14.33:一个重载的函数调用运算符应该接受几个运算对象?
0个或多个。
练习14.34:定义一个函数对象类,伶气质型if-then-else的操作:该类的调用运算符接受三个形参,它首先检查第一个形参,如果成功返回第二个形参的值,如果不成功返回第三个形参的值。
练习14.35:编写一个类似PrintString的类,令其从istream中读取一行输入,然后返回一个表示我们所读内容的string。如果读取失败,返回空string。
练习14.36:使用前一个练习定义的类读取标准输入,将每一行保存为vector的一个元素。
练习14.37:编写一个类令其检查两个值是否相等。使用该对象及标准库算法编写程序,令其替换某个序列中具有给定值的所有实例。
练习14.38:编写一个类令其检查某个给定的string对象的长度是否与一个阀值相等。使用该对象编写程序,统计并报告在输入的文件中长度为1的单词有多少个、长度为2的单词有多少个。
练习14.39:修改上一题的程序令其报告长度在1至9之间的单词有多少个、长度在10以上的单词又有多少个。
练习14.40:重新编写10.3.2节的biggies函数,使用函数对象替换其中的lambda表达式。
class IsShorter
{
public:
bool operator()(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
};
class NotShorterThan
{
public:
NotShorterThan(int len) : minLen(len) { }
bool operator()(const string &str)
{
return str.size() >= minLen;
}
private:
int minLen;
};
class PrintString
{
public:
void operator()(const string &str)
{
cout << str << " ";
}
};
void biggies(vector<string> &words, vector<string>::size_type sz)
{
elimDups(words);
IsShorter is;
stable_sort(words.begin(), words.end(), is);
NotShorterThan nst(sz);
auto wc = find_if(words.begin(), words.end(), nst);
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "words", "s") << " of length " <, sz << " or longer" <<endl;
PrintString ps;
for_each(wc, words.end(), ps);
cout << endl;
}
练习14.41:你认为C++11新标准为什么要增加lambda?对于你自己来说,什么情况下会使用lambda,什么情况下会使用类?
在C++11中,lambda是通过匿名的函数对象来实现的,因此我们可以把lambda看作是对函数对象在使用方式上进行的简化。当代码需要一个简单的函数,并且这个函数并不会在其他地方被使用时,就可以使用lambda来实现,此时它所起的作用类似于匿名函数。但如果这个函数需要多次使用,并且它需要保存某些状态的话,使用函数对象更合适一些。
练习14.42:使用标准库函数对象及适配器定义一条表达式,令其
(a)统计大于1024的值有多少个。
(b)找到第一个不等于pooh的字符串。
(c)将所有的值乘以2.
count_if(vec.begin(), vec.end(), bind2nd(greater<int>(), 1024));
find_if(vec.begin(), vec.end(), bind2nd(not_equal_to<string>(), "pooh"));
transform(vec.begin(), vec.end(), vec.begin(), bind2nd(multiplies<int>(), 2));
练习14.43:使用标准库函数对象判断一个给定的int值是否能被int容器中的所有元素整除。
bool divideByAll(vector<int> &ivec, int dividend)
{
return count_if(ivec.begin(), ivec.end(), bindlst(modulus<int>, dividend)) == 0;
}
练习14.44:编写一个简单的桌面计算器使其能处理二元运算。
#include <iostream>
#include <map>
#include <algorithm>
#include <string>
#include <functional>
using std::function;
using std::map;
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::plus;
using std::minus;
using std::multiplies;
using std::divides;
using std::modulus;
map<string, 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;
return 0;
}
练习14.45:编写类型转换运算符将一个Sales_data对象分别转换成string和double,你认为这些运算符的返回值应该是什么?
如果要转换成string,那么返回值应该是bookNo。
如果要转换成double,那么返回值应该是revenue。
练习14.46:你认为应该为Sales_data类定义上面两种类型转换运算符吗?应该把它们声明成explicit的吗?为什么?
Sales_data不应该定义这两种类型转换运算符,因为对于类来说,它包含三个数据成员:bookNo,units_sold和revenue,只有三者在一起才是有效的数据。但是如果确实想要定义这两个类型转换运算符的话,应该把它们声明成explicit的,这样可以防止sales_data 在默写情况下被默认转换成string或double类型,这有可能导致意料之外的运算结果。
练习14.47:说明下面这两个类型转换运算符的区别。
struct Integral
{
operator const int();
operator int() const;
};
前者将对象转换成const int,在接受const int值的地方才能够使用。
后者将对象转换成int值,相对来说更加通用一些。
练习14.48:你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有bool的类型转换运算符吗?如果是,解释原因并说明该运算符是否应该是explicit的;如果不是,也请解释原因。
之前我们编写了Date类,它含有3个数据成员:year、month和day。
我们可以为Date提供一个bool类型的转换运算符,用来检查3个数据成员是否都是有效值,bool类型转换运算符应该声明为explicit的,因为我们是有意要在条件表达式中使用它的。
练习14.49:为上一题提到的类定义一个转换目标是bool的类型转换运算符,先不用在意这么做是否应该。
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 = ldOb;,它需要把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+; 在通常情况下+s是个非成员
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,判断下面的加法表达式是否合法。如果合法,使用了哪个加法运算符?如果不合法,应该怎样修改代码才能使其合法?
SamllInt sl;
double d = s1 + 3.14;
内置的operator+(int, double)是可行的,而3.14可以转换为int,然后再转换为SmallInt,所以SmallInt的成员operator+也是可行的。两者都需要进行类型转换,所以会产生二义性。改为:double d = s1 +Smallint(3.14);即可。
不同点:
重载运算符必须具有至少一个class或枚举类型的操作数。
重载运算符不保证操作数的求值顺序,例如对&&和||的重载版本不再具有“短路求值”的特性,两个操作数都要求值,而且不规定操作数的求值顺序。
相同点:
对于优先级和结合性级操作数的数目都不变。
练习14.2:为Sales_data编写重载的输入、输出、加法和复合赋值运算符的声明。
class Sales_data { friend std::istream& operator>>(std::istream&, Sales_data &); friend std::ostream& operator<<(std::ostream&, const 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) svec[0] == "stone"
(a)应用了C++内置版本的==,比较两个指针。(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
#include <iostream> using std::ostream; using std::endl; class Date { public: Date() { } Date(int y, int m, int d) {year = y; month = m; day = d;} friend ostream& operator<<(ostream &os, const Date &dt); 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 << endl; return os; }
练习14.6:为你的Sales_data类定义输出运算符。
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节的练习中曾经编写了一个String类,为它定义一个输出运算符。
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.51节的练习7.40中曾经选择并编写了一个类,为它定义一个输出运算符。
见练习14.5。
练习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.unite_sold >> price; s.revenue = s.unite_sold * price; 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节的练习中曾经选择并编写了一个类,为它定义一个输入运算符并确保该运算符可以处理输入错误。
<pre name="code" class="cpp">#include <iostream> using std::istream; class Date { public: Date() { } Date(int y, int m, int d) {year = y; month = m; day = d;} friend istream& operator>>(istream &is, Date &dt); private: int year, month, day; }; istream& operator>>(istream &is, Date &dt) { is >> dt.year >> dt.month >> dt.day; if (!is) dt = Date(0, 0, 0); return is; }
练习14.13:你认为Sales_data类还应该支持 哪些其他算术运算符?如果有的话,请给出它们的定义。
可以定义一个减法运算符
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中曾经选择并编写了一个类。你认为它应该含有其他算术运算符吗?如果是,请实现它们;如果不是,解释原因。
在练习7.40中,变写了Date类,算术运算对Date没有太大意义,不需要为Date重载算术运算符。
练习14.16:为你的StrBlob类、StrBlobPtr类、StrVec类和String类分别定义相等和不相等运算符。
//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.loc(); 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节的练习7.40中曾经选择并编写了一个类,你认为它应该含有相等运算符吗?如果是,请实现它;如果不是,解释原因。
class Date { friend bool operator==(const Date &dl, 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.month == d2.month && d1.day == d2.day; } bool operator!=(const Date &d1, const Date &d2) { return !(da == d2); }
14.18:为你的StrBlob类、StrBlobPtr类、StrVec类、String类定义关系运算符。
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中曾经选择并编写了一个类,你认为它应该含有关系运算符吗?如果是,请实现它;如果不是,解释原因。
class Date { friend operator<(const Date &d1, const Date &d2); friend operator<=(const Date &d1, const Date &d2); friend operator>(const Date &d1, const Date &d2); friend operator>=(const Date &d1, const Date &d2); //其他成员 }; bool operator<(const Date &d1, const Date &s2) { 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 &s2) { return (d1 < d2) || (d1 == d2); } bool operator>(const Date &d1, const Date &s2) { return !(d1 <= d2); } bool operator>=(const Date &d1, const Date &s2) { return (d1 > d2) || (d1 == d2); }
练习14.20:为你的Sales_data类实现加法和复合赋值运算符。
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节和14.4节对这两个运算符的定义,本题的定义有何缺点?试讨论之。
在性能上没有优势,可读性也不好。
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对象。
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赋值运算符。
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中曾经选择并编写了一个类,你认为它应该含有拷贝赋值和移动赋值运算符吗?如果是,请实现它们。
再联系7.40中,我们变细了Date类,它只有三个int类型的数据成员,浅拷贝就能满足要求,因此不需要另外定义拷贝赋值和移动赋值运算符。
练习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类定义下标运算符。
class StrBlob { public: std:;string& operator[](std:;size_t n) { return data ; } const std:;string& operator[](std:;size_t n) const { return data ; } }; class StrBlobPtr { std::string& operator[](std::size_t n) { return (*wptr.lock()) ; } const std::string& operator[](std::size_t n) const { return (*wptr.lock()) ; } }; class StrVec { public: std:;string& operator[])(std:;size_t n) { return elements ; } const std:;string& operator[])(std:;size_t n) const { return elements ; } }; class String { public: char& operator[](std::size_t n) { return (char) str ; } const char& operator[](std::size_t n) const { return (char) str ; } private: char *str; }
练习14.27:为你的StrBlobPtr类添加递增和递减运算符。
class StrBlobPtr { public: //前缀 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(-1, " 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类添加加法和减法运算符,使其可以实现指针的算术运算。
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:为你的StrBlob类和在12.1.6节练习12.22中定义的ConstStrBlobPtr类分别添加解引用运算符和箭头运算符。注意:因为ConstStrBlobPtr的数据成员指向const vector,所以ConstStrBlobPtr中的运算符必须返回常量引用。
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对象的指针,为这个类定义重载的建投运算符。
class MyClass { public: std::string* operator->() const { return ptr->operator->(); } private: StrBlobPtr *ptr; }
练习14.33:一个重载的函数调用运算符应该接受几个运算对象?
0个或多个。
练习14.34:定义一个函数对象类,伶气质型if-then-else的操作:该类的调用运算符接受三个形参,它首先检查第一个形参,如果成功返回第二个形参的值,如果不成功返回第三个形参的值。
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。
class ReadString { public: ReadString(istream &is = cin) : is(is) { } std:;string operator()() { string line; if (!getline(is, line)) { line = " "; } return line; } private: istream &is; };
练习14.36:使用前一个练习定义的类读取标准输入,将每一行保存为vector的一个元素。
void testReadString() { ReadString rs; vector<string> vec; while (true) { string line = rs(); if (!line.empty()) { vec.push_back(line); } else break; } }
练习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的单词有多少个。
#include <iostream> #include <vector> #include <string> #include <algorithm> using std::istream; using std::cout; using std::cin; using std::endl; using std::vector; using std::string; 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; } return 0; }
练习14.39:修改上一题的程序令其报告长度在1至9之间的单词有多少个、长度在10以上的单词又有多少个。
#include <iostream> #include <vector> #include <string> #include <algorithm> using std::istream; using std::cout; using std::cin; using std::endl; using std::vector; using std::string; 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 StrNoShorterThan { public: StrNoShorterThan(int len) : minLen(len) { } 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); StrLenBetween slenBetween(1, 9); StrNoShorterThan sNoShorterThan(10); cout << "len 1-9 :" << count_if(vec.begin(), vec.end(), slenBetween) << endl; cout << "len >= 10 : " << count_if(vec.begin(), vec.end(), sNoShorterThan) << endl; return 0; }
练习14.40:重新编写10.3.2节的biggies函数,使用函数对象替换其中的lambda表达式。
class IsShorter
{
public:
bool operator()(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
};
class NotShorterThan
{
public:
NotShorterThan(int len) : minLen(len) { }
bool operator()(const string &str)
{
return str.size() >= minLen;
}
private:
int minLen;
};
class PrintString
{
public:
void operator()(const string &str)
{
cout << str << " ";
}
};
void biggies(vector<string> &words, vector<string>::size_type sz)
{
elimDups(words);
IsShorter is;
stable_sort(words.begin(), words.end(), is);
NotShorterThan nst(sz);
auto wc = find_if(words.begin(), words.end(), nst);
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "words", "s") << " of length " <, sz << " or longer" <<endl;
PrintString ps;
for_each(wc, words.end(), ps);
cout << endl;
}
练习14.41:你认为C++11新标准为什么要增加lambda?对于你自己来说,什么情况下会使用lambda,什么情况下会使用类?
在C++11中,lambda是通过匿名的函数对象来实现的,因此我们可以把lambda看作是对函数对象在使用方式上进行的简化。当代码需要一个简单的函数,并且这个函数并不会在其他地方被使用时,就可以使用lambda来实现,此时它所起的作用类似于匿名函数。但如果这个函数需要多次使用,并且它需要保存某些状态的话,使用函数对象更合适一些。
练习14.42:使用标准库函数对象及适配器定义一条表达式,令其
(a)统计大于1024的值有多少个。
(b)找到第一个不等于pooh的字符串。
(c)将所有的值乘以2.
count_if(vec.begin(), vec.end(), bind2nd(greater<int>(), 1024));
find_if(vec.begin(), vec.end(), bind2nd(not_equal_to<string>(), "pooh"));
transform(vec.begin(), vec.end(), vec.begin(), bind2nd(multiplies<int>(), 2));
练习14.43:使用标准库函数对象判断一个给定的int值是否能被int容器中的所有元素整除。
bool divideByAll(vector<int> &ivec, int dividend)
{
return count_if(ivec.begin(), ivec.end(), bindlst(modulus<int>, dividend)) == 0;
}
练习14.44:编写一个简单的桌面计算器使其能处理二元运算。
#include <iostream>
#include <map>
#include <algorithm>
#include <string>
#include <functional>
using std::function;
using std::map;
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::plus;
using std::minus;
using std::multiplies;
using std::divides;
using std::modulus;
map<string, 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;
return 0;
}
练习14.45:编写类型转换运算符将一个Sales_data对象分别转换成string和double,你认为这些运算符的返回值应该是什么?
如果要转换成string,那么返回值应该是bookNo。
如果要转换成double,那么返回值应该是revenue。
练习14.46:你认为应该为Sales_data类定义上面两种类型转换运算符吗?应该把它们声明成explicit的吗?为什么?
Sales_data不应该定义这两种类型转换运算符,因为对于类来说,它包含三个数据成员:bookNo,units_sold和revenue,只有三者在一起才是有效的数据。但是如果确实想要定义这两个类型转换运算符的话,应该把它们声明成explicit的,这样可以防止sales_data 在默写情况下被默认转换成string或double类型,这有可能导致意料之外的运算结果。
练习14.47:说明下面这两个类型转换运算符的区别。
struct Integral
{
operator const int();
operator int() const;
};
前者将对象转换成const int,在接受const int值的地方才能够使用。
后者将对象转换成int值,相对来说更加通用一些。
练习14.48:你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有bool的类型转换运算符吗?如果是,解释原因并说明该运算符是否应该是explicit的;如果不是,也请解释原因。
之前我们编写了Date类,它含有3个数据成员:year、month和day。
我们可以为Date提供一个bool类型的转换运算符,用来检查3个数据成员是否都是有效值,bool类型转换运算符应该声明为explicit的,因为我们是有意要在条件表达式中使用它的。
练习14.49:为上一题提到的类定义一个转换目标是bool的类型转换运算符,先不用在意这么做是否应该。
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 = ldOb;,它需要把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+; 在通常情况下+s是个非成员
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,判断下面的加法表达式是否合法。如果合法,使用了哪个加法运算符?如果不合法,应该怎样修改代码才能使其合法?
SamllInt sl;
double d = s1 + 3.14;
内置的operator+(int, double)是可行的,而3.14可以转换为int,然后再转换为SmallInt,所以SmallInt的成员operator+也是可行的。两者都需要进行类型转换,所以会产生二义性。改为:double d = s1 +Smallint(3.14);即可。
相关文章推荐
- [C++学习笔记]虚函数用法及注意事项
- VC++6.0 Debug单步调试简单入门
- Effective C++笔记(五):实现
- 输入n个数和输出调整后的b个数
- C/C++基础知识04---难点记录
- SM2算法第三篇:实现SM2秘钥交换协议的算法流程
- c语言中的memset的用法
- C++I/O输入输出(看不懂的你可以咬我)
- C++ STL算法系列---copy函数
- c语言入门
- C++ 二叉查找树
- Rotate Array
- C++里的模板
- C++ string类型的一些操作
- leetcode223题 题解 翻译 C语言版 Python版
- 模板的特化
- c++修改文件名后缀 文件路径分解与批处理文件遍历
- 字符串转整数一种实现
- C++之继承
- C语言 打开文件时改变文件大小