《C++ Primer》第14章 操作重载与类型转换
本章介绍了运算符重载的基本概念,介绍了各种运算符的重载方式,并介绍了类型转换与运算符重载的关系。本章的练习着重让读者掌握各种运算符的重载,包括输入/输出运算符、各种算术运算符、下标运算符以及函数调用运算符等。特别是,结合自定义的Sales_data、StrBlob等较为复杂的类进行运算符重载的练习,让读者体会运算符重载是如何帮助改进代码质量的。此外,还通过一些练习帮助读者弄清类型转换与运算符重载的关系。特别是,当有多个重载版本时,在进行函数匹配时涉及类型转换应如何处理。
14.1节 基本概念 习题答案
练习14.1:在什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样?
【出题思路】
理解重载运算符与内置运算符的区别。
【解答】
不同点:
重载操作符必须具有至少一个class或枚举类型的操作数。重载操作符不保证操作数的求值顺序,例如对&&和||的重载版本不再具有“短路求值”的特性,两个操作数都要进行求值,而且不规定操作数的求值顺序。相同点:对于优先级和结合性及操作数的数目都不变。
练习14.2:为Sales_data编写重载的输入、输出、加法和复合赋值运算符的声明。
【出题思路】
本题练习重载运算符的声明。
【解答】
几个运算符的声明如下所示:
#ifndef SALES_DATA14_02_H
#define SALES_DATA14_02_H
#include <iostream>
#include <string>
class Sales_item
{
friend std::istream& operator>>(std::istream&, Sales_item&);
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
friend bool operator<(const Sales_item&, const Sales_item&);
friend bool operator==(const Sales_item&, const Sales_item&);
public:
Sales_item() = default;
Sales_item(const std::string &book): bookNo(book) { }
Sales_item(std::istream &is) { is >> *this; }
public:
Sales_item& operator+=(const Sales_item&);
std::string isbn() const { return bookNo; }
double avg_price() const;
private:
std::string bookNo; //书号
unsigned units_sold = 0; //出售册数
double revenue = 0.0; //收入
};
inline bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
{
return lhs.isbn() == rhs.isbn();
}
Sales_item operator+(const Sales_item&, const Sales_item&);
inline bool operator==(const Sales_item &lhs, const Sales_item &rhs)
{
return (lhs.units_sold == rhs.units_sold) &&
(lhs.revenue == rhs.revenue) &&
(lhs.isbn() == rhs.isbn());
}
inline bool operator !=(const Sales_item &lhs, const Sales_item &rhs)
{
return !(lhs == rhs);
}
Sales_item& Sales_item::operator +=(const Sales_item& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
Sales_item operator +(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs);
ret += rhs;
return ret;
}
std::istream& operator>>(std::istream& in, Sales_item& s)
{
double price;
in >> s.bookNo >> s.units_sold >> price;
if(in)
s.revenue = s.units_sold * price;
else
s = Sales_item();
return in;
}
std::ostream& operator <<(std::ostream& out, const Sales_item& s)
{
out << s.isbn() << " " << s.units_sold << " "
<< s.revenue << " " << s.avg_price();
return out;
}
double Sales_item::avg_price() const
{
if(units_sold)
return revenue / units_sold;
else
return 0;
}
#endif // SALES_DATA14_02_H
#include "Sales_data14_02.h"
#include <iostream>
using namespace std;
int main()
{
Sales_item trans1, trans2;
std::cout << "请输入两条ISBN相同的销售记录:" << std::endl;
std::cin >> trans1 >> trans2;
if(compareIsbn(trans1, trans2))
std::cout << "汇总信息:ISBN,售出本数,销售额和平均售价为: " << trans1 + trans2 << std::endl;
else
std::cout << "两条销售记录的ISBN不同" << std::endl;
cout << "Hello World!" << endl;
return 0;
}
运行结果:
练习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" == "store"应用了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
(b) Vehicle (e) Object (f) Tree
【出题思路】
学会判断是否需要为类定义重载运算符。
【解答】
以(b)Date为例,为其定义重载的输出运算符,输入运算符可参照实现。显然,为Date定义输入输出运算符,可以让我们像输入输出内置类型对象那样输入输出Date,在易用性和代码的可读性上有明显的好处。因此,定义这两个重载运算符是合理的。
#include <iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream &os, const Date &dt);
public:
Date();
Date(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
private:
int year;
int month;
int 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;
}
int main()
{
Date date(2029, 8, 6);
cout << "date==========" << date << endl;
cout << "Hello World!" << endl;
return 0;
}
运行结果:
date==========year:2029 month:8 day:6