C++(27)输入/输出、算术/关系操作符
2015-08-08 19:54
489 查看
重载操作符与转换
--输入/输出、算术/关系操作符
支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同,因此,许多类都需要重载输入和输出操作符。一、输出操作符<<的重载
为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回ostream形参的引用!
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
ostream &operator<<(ostream &os,const ClassType &object)
{
os << //....
return os;
}
1、Sales_item输出操作符
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
ostream &operator<<(ostream &out,const Sales_item &object)
{
out << object.isbn << '\t' << object.units_sold << '\t'
<< object.revenue << '\t' << object.avg_price();
return out;
}
2、输出操作符通常所做格式化应尽量少
一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符!尽量减少操作符的格式化,可以让用户自己控制输出细节。
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
Sales_item item("C++ Primer");
cout << item << endl; //用户自己控制输出换行符
3、IO操作符必须为非成员函数
我们不能将该操作符定义为类的成员,否则,左操作符只能是该类型的对象:
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
ostream &Sales_item::operator<<(ostream &out)
{
out << isbn << '\t' << units_sold << '\t'
<< revenue << '\t' << avg_price();
return out;
}
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
//测试
Sales_item item("C++ Primer");
//这个用法与正常使用方式恰好相反
item << cout << endl;
//OR
item.operator<<(cout);
//Error
cout << item << endl;
如果想要支持正常用法,则左操作数必须为ostream类型。这意味着,如果该操作符是类的成员,则它必须是ostream类的成员,然而,ostream类是标准库的组成部分,我们(以及任何想要定义IO操作符的人)是不能为标准库中的类增加成员的。
由于IO操作符通常对非公用数据成员进行读写,因此,类通常将IO操作符设为友元。
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
//P437 习题14.7
class CheckoutRecord
{
friend ostream &operator<<(ostream &os,const CheckoutRecord &object);
public:
typedef unsigned Date;
//...
private:
double book_id;
string title;
Date date_borrowed;
Date date_due;
pair<string,string> borrower;
vector< pair<string,string> * > wait_list;
};
ostream &operator<<(ostream &os,const CheckoutRecord &obj)
{
os << obj.book_id << ": " << obj.title << '\t' << obj.date_borrowed
<< '\t' << obj.date_due << '\t' << obj.borrower.first << ' '
<< obj.borrower.second << endl;
os << "Wait_list:" << endl;
for (vector< pair<string,string> * >::const_iterator iter = obj.wait_list.begin();
iter != obj.wait_list.end(); ++iter)
{
os << (*iter) -> first << '\t' << (*iter) -> second << endl;
}
}
二、输入操作符>>的重载
与输出操作符类似,输入操作符的第一个形参是一个引用,指向它要读的流,并且返回的也是对同一个流的引用。它的第二个形参是对要读入的对象的非const引用,该形参必须为非const,因为输入操作符的目的是将数据读到这个对象中。
输入操作符必须处理错误和文件结束的可能性!
1、Sales_item的输入操作符
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
istream &operator>>(istream &in,Sales_item &s)
{
double price;
in >> s.isbn >> s.units_sold >> price;
if (in)
{
s.revenue = price * s.units_sold;
}
else
{
//如果读入失败,则将对象重新设置成为默认状态
s = Sales_item();
}
return in;
}
2、输入期间的错误
可能发生的错误包括:
1)任何读操作都可能因为提供的值不正确而失败。例如,读入isbn之后,输入操作符将期望下两项是数值型数据。如果输入非数值型数据,这次的读入以及流的后续使用都将失败。
2)任何读入都可能碰到输入流中的文件结束或其他一些错误。
但是我们无需检查每次读入,只在使用读入数据之前检查一次即可。
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
if (in)
{
s.revenue = price * s.units_sold;
}
else
{
s = Sales_item();
}
如果一旦出现了错误,我们不用关心是哪个输入失败了,相反,我们将整个对象复位!
3、处理输入错误
如果输入操作符检测到输入失败了,则确保对象处于可用和一致状态是个好做法!如果对象在发生错误之前已经写入了部分信息,这样做就特别重要!
例如,在Sales_item的输入操作符中,可能成功地读入了一个新的isbn,然后遇到流错误。在读入isbn之后发生错误意味着旧对象的units_sold和revenue成员没变,结果会将另一个isbn与那个数据关联(悲剧了...)。因此,将形参恢复为空Sales_item对象,可以避免给他一个无效的状态!
【最佳实践】
设计输入操作符时,如果可能,要确定错误恢复措施,这很重要!
4、指出错误
除了处理可能发生的任何错误之外,输入操作符还可能需要设置输入形参的条件状态。
有些输入操作符的确需要进行附加检查。例如,我们的输入操作符可以检查读到的 isbn格式是否恰当。也许我们已成功读取了数据,但这些数据不能恰当解释为ISBN,在这种情况下,尽管从技术上说实际的IO是成功的,但输入操作符仍可能需要设置条件状态以指出失败。通常输入操作符仅需设置failbit。设置eofbit意思是文件耗尽,设置badbit可以指出流被破坏,这些错误最好留给 IO标准库自己来指出。
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
//P439 习题14.11
class CheckoutRecord
{
friend istream &operator>>(istream &in,CheckoutRecord &object);
public:
typedef unsigned Date;
//...
private:
double book_id;
string title;
Date date_borrowed;
Date date_due;
pair<string,string> borrower;
vector< pair<string,string> * > wait_list;
};
istream &operator>>(istream &in,CheckoutRecord &obj)
{
in >> obj.book_id >> obj.title >> obj.date_borrowed >> obj.date_due;
in >> obj.borrower.first >> obj.borrower.second;
if (!in)
{
obj = CheckoutRecord();
return in;
}
obj.wait_list.clear();
while (in)
{
pair<string,string> *p = new pair<string,string>;
in >> p -> first >> p -> second;
if (in)
{
obj.wait_list.push_back(p);
delete p;
}
}
return in;
}
三、算术运算符
一般而言,将算术和关系操作符定义为非成员函数:
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
Sales_item operator+(const Sales_item &lhs,const Sales_item &rhs)
{
Sales_item ret(lhs);
//使用Sales_item的复合复制操作符来加入rhs的值
ret += rhs;
return ret;
}
加法操作符并不改变操作数的状态,操作数是对const对象的引用。
【最佳实践】
为了与内置操作符保持一致,加法返回一个右值,而不是一个引用!
既定义了算术操作符又定义了先关复合赋值操作符的类,一般应使用复合赋值实现算术操作符。
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
//P440 习题14.12
Sales_item operator+(const Sales_item &lhs,const Sales_item &rhs)
{
Sales_item tmp;
tmp.units_sold = lhs.units_sold + rhs.units_sold;
tmp.revenue = lhs.revenue + rhs.revenue;
return tmp;
}
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
*this = *this + rhs;
return *this;
}
四、关系运算符
1、相等运算符
如果所有对应成员都相等,则认为两个对象相等。
[cpp] view
plaincopy
![](https://code.csdn.net/assets/CODE_ico.png)
inline
bool operator==(const Sales_item &lhs,const Sales_item &rhs)
{
return lhs.revenue == rhs.revenue && lhs.units_sold == rhs.units_sold &&
lhs.same_isbn(rhs);
}
inline
bool operator!=(const Sales_item &lhs,const Sales_item &rhs)
{
return !(lhs == rhs);
}
1)如果类定义了==操作符,该操作符的含义是两个对象包含同样的数据。
2)如果类具有一个操作,能确定该类型的两个对象是否相等,通常将该函数定义为 operator==而不是创造命名函数。用户将习惯于用==来比较对象,而且这样做比记住新名字更容易。
3)如果类定义了operator==,它也应该定义operator!=。用户会期待如果可以用某个操作符,则另一个也存在。
4)相等和不操作符一般应该相互联系起来定义,让一个操作符完成比较对象的实际工作,而另一个操作符只是调用前者。
定义了operator==的类更容易与标准库一起使用。有些算法,如find,默认使用==操作符,如果类定义了==,则这些算法可以无需任何特殊处理而用于该类类型!
2、关系操作符
定义了相等操作符的类一般也具有关系操作符。尤其是,因为关联容器和某些算法使用小于操作符(<),所以定义了operator<可能相当有用。
如果因为<的逻辑定义与==的逻辑定义不一致,所以这样的话,不定义<会更好。
【注释】
关联容器以及某些算法,默认使用<操作符(此处本人认为译者翻译有误,原文:...usethe
< operator bydefult...,译者翻译为:使用默认<操作符,但本人认为默认使用更为恰当!)。一般而言,关系操作符,诸如相等操作符,应定义为非成员函数(“对称”操作符)。
相关文章推荐
- C++(26)重载操作符的定义
- C++(25)管理指针成员
- C++(24)消息处理示例
- C语言文件读写操作
- C++对象模型——Member的各种调用方式(第四章)
- C++(23)析构函数
- C++(22)复制构造函数
- C++(21)友元、static成员
- C++(20)构造函数
- CVTE内推面试(c++)
- C++(19)类与数据抽象
- c++中的重载
- 关于C语言中的存储区划分以及相关内容
- c/c++下 long long与__int64不同编译环境的比较
- 【C++】智能指针的实现
- C/C++ 数据范围(int ,char long, float,double)
- 【C++基金会 04】vector详细解释
- 排序算法的C语言实现-冒泡排序
- CZY的组合数烦恼
- 排序算法的C语言实现-插入排序