C++ Primer 学习笔记_59_重载操作符与转换 --输入/输出、算术/关系操作符

重载操作符与转换

--输入/输出、算术/关系操作符

支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同,因此,许多类都需要重载输入和输出操作符。

一、输出操作符<<的重载

为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回ostream形参的引用!

ostream &operator<<(ostream &os,const ClassType &object)
{
    os << //....
    return os;
}

1、Sales_item输出操作符

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、输出操作符通常所做格式化应尽量少

一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符!尽量减少操作符的格式化,可以让用户自己控制输出细节。

    Sales_item item("C++ Primer");
    cout << item << endl;	//用户自己控制输出换行符

3、IO操作符必须为非成员函数

我们不能将该操作符定义为类的成员,否则,左操作符只能是该类型的对象:

ostream &Sales_item::operator<<(ostream &out)
{
    out << isbn << ‘\t‘ << units_sold << ‘\t‘
        << revenue << ‘\t‘ << avg_price();

    return out;
}

//测试
    Sales_item item("C++ Primer");
    //这个用法与正常使用方式恰好相反
    item << cout << endl;
    //OR
    item.operator<<(cout);

    //Error
    cout << item << endl;

如果想要支持正常用法,则左操作数必须为ostream类型。这意味着,如果该操作符是类的成员,则它必须是ostream类的成员,然而,ostream类是标准库的组成部分,我们(以及任何想要定义IO操作符的人)是不能为标准库中的类增加成员的。

由于IO操作符通常对非公用数据成员进行读写,因此,类通常将IO操作符设为友元。

//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的输入操作符

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)任何读入都可能碰到输入流中的文件结束或其他一些错误。

但是我们无需检查每次读入,只在使用读入数据之前检查一次即可。

    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标准库自己来指出

//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;
}

三、算术运算符

一般而言,将算术和关系操作符定义为非成员函数:

Sales_item operator+(const Sales_item &lhs,const Sales_item &rhs)
{
    Sales_item ret(lhs);
    //使用Sales_item的复合复制操作符来加入rhs的值
    ret += rhs;
    return ret;
}

加法操作符并不改变操作数的状态,操作数是对const对象的引用。

【最佳实践】

为了与内置操作符保持一致,加法返回一个右值,而不是一个引用!

既定义了算术操作符又定义了先关复合赋值操作符的类,一般应使用复合赋值实现算术操作符。

//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、相等运算符

如果所有对应成员都相等,则认为两个对象相等。

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++ Primer 学习笔记_59_重载操作符与转换 --输入/输出、算术/关系操作符

时间: 2024-10-06 10:13:14

C++ Primer 学习笔记_59_重载操作符与转换 --输入/输出、算术/关系操作符的相关文章

C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型【下】

重载操作符与转换 --转换与类类型[下] 四.重载确定和类的实参 在需要转换函数的实参时,编译器自动应用类的转换操作符或构造函数.因此,应该在函数确定期间考虑类转换操作符.函数重载确定由三步组成: 1)确定候选函数集合:这些是与被调用函数同名的函数. 2)选择可行的函数:这些是形参数目和类型与函数调用中的实参相匹配的候选函数.选择可行函数时,如果有转换操作,编译器还要确定需要哪个转换操作来匹配每个形参. 3)选择最佳匹配的函数.为了确定最佳匹配,对将实参转换为对应形参所需的类型转换进行分类.对于

C++ Primer 学习笔记_58_重载操作符与转换 --重载操作符的定义

重载操作符与转换 --重载操作符的定义 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 重载操作符的定义 重载操作符是具有特殊名称的函数:保留字operator后接定义的操作符符号.如: Sales_item operator+(const Sales_item &,const Sales_item &); 除了函数调用操作符之外,重载操作符的形参数目(包括成员函数的隐式this指针)与操作符的操作数数目相同.函数调用操作符可以接受任意数目的操作数. 1.重载的操作符名

C++ Primer 学习笔记_63_重载操作符与转换 --转换与类类型【上】

重载操作符与转换 --转换与类类型[上] 引言: 在前面我们提到过:可以用一个实参调用的非explicit构造函数定义一个隐式转换.当提供了实参类型的对象需要一个类类型的对象时,编译器将使用该转换.于是:这种构造函数定义了到类类型的转换. 除了定义到类类型的转换之外,还可以定义从类类型到其他类型的转换.即:我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象.和其他转换一样,编译器将自动应用这个转换. 一.转换为什么有用? 定义一个SmallInt的类,该类实现安全小整数,这个

C++ Primer 学习笔记_60_重载操作符与转换 --赋值、下标、成员訪问操作符

重载操作符与转换 --赋值.下标.成员訪问操作符 一.赋值操作符 类赋值操作符接受类类型形參,通常该形參是对类类型的const引用,但也能够是类类型或对类类型的非const引用.假设未定义这个操作符,则编译器将合成它.类赋值操作符必须是类的成员,以便编译器能够知道是否须要合成一个.并且还能够为一个类定义很多附加的赋值操作符,这些赋值操作符会由于右操作数的不同而构成重载!如string类型: string car("Volks"); car = "Studebaker"

C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符

重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪问检查: class CheckedPtr { public: //这个类没有默认构造函数,必须提供指向数组的指针. /**构造函数的參数是两个指针:一个指向数组的開始,还有一个指向数组的末端. *构造函数用这两个指针初始化 beg 和 end *并将 curr 初始化为指向第一个元素 */ Che

C++ Primer 学习笔记_62_重载操作符与转换 --调用操作符和函数对象

重载操作符与转换 --调用操作符和函数对象 引言: 能够为类类型的对象重载函数调用操作符:一般为表示操作的类重载调用操作符! struct absInt { int operator() (int val) { return val > 0 ? val : -val; } }; 通过为类类型的对象提供一个实參表而使用调用操作符,所用的方式看起来系那个一个函数调用: absInt absObj; int i = -1; cout << absObj(i) << endl; 虽然

C++ Primer 学习笔记_63_重载运算符和转换 --转换和类类型【上】

重载运算符和转换 --转换与类类型[上] 引言: 在前面我们提到过:能够用一个实參调用的非explicit构造函数定义一个隐式转换.当提供了实參类型的对象须要一个类类型的对象时.编译器将使用该转换. 于是:这样的构造函数定义了到类类型的转换. 除了定义到类类型的转换之外,还能够定义从类类型到其它类型的转换.即:我们能够定义转换操作符,给定类类型的对象.该操作符将产生其它类型的对象.和其它转换一样,编译器将自己主动应用这个转换. 一.转换为什么实用? 定义一个SmallInt的类,该类实现安全小整

C++primer读书笔记8重载操作符

函数的重载操作符中也有不少细节 1 重载操作符特点 重载操作符本质上就是重载函数,它的特点: <1>除了函数调用操作符'()'外,所有的重载操作符的形参数目必须与操作符的操作数目相等. <2>重载操作符的操作数必须至少有一个类类型,否则就不是重载而是原型了. <3>优先级和结合性不会发生改变,例如对于算数操作符的重载,*的优先级肯定还是大于+ <4>不具有短路求值特性(就是根据已有的值就可以判断出最终的逻辑结果的话就会停止计算) <5>操作符可以

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符、成员函数方式重载、友元函数方式重载

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符.成员函数方式重载.友元函数方式重载 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 一.重载的操作符名 像任何其他函数一样,操作符重载函数有一个返回值和一个形参表.形参表必须具有操作符数目相同的形参.比如赋值时二元运算,所以该操作符函数有两个参数:第一个形参对应着左操作数,第二个形参对应右操作数. 大多数操作符可以定义为成员函数或非成员函数.当操作符为成员函数时,它的第一个操作数隐式绑定