[c++primer][14]重载操作符与转换
14.1 重载操作符的定义
不能重载的操作符:. ?: sizeof :: .*
不能为任何内置类型定义额外的新的操作符;优先级和结合性是固定的;不再具备短路求值特性(不建议重载&&、||、逗号);
一般将算术和关系操作符定义为非成员函数,而将赋值操作符定义为成员;
使用重载操作符的方式:
1) 与内置类型使用操作符方式一样;
2) 也可像调用普通函数一样调用重载操作符函数,指定函数并传递适当类型适当数目的形参;
item1 += item2; // expression based "call" item1.operator+=(item2); // equivalent call to member operator function
重载操作符的设计
1)不要重载具有内置含义的操作符。如果没有特定重载版本,编译器就自己定义以下这些操作符:
- 合成赋值操作符(=):逐个成员赋值
- 取地址操作符(&)、逗号操作符(,)与内置含义一致
- 内置逻辑与(&&)和逻辑或(||)操作符使用短路求值,重新定义将会失去该特征。
2)大多数操作符对类对象没有意义,设计类的时候应该确定要支持哪些操作符。
3)复合赋值操作符。如果一个类有算术操作符或位操作符,那么提供相应的复合赋值操作符一般是个好的做法。
4)相等和关系操作符。将用作关联容器类型的类应定义<操作符;即使类只存储在顺序容器中,也应该定义相等操作符和小于操作符;如果类定义了相等操作符,它也应该定义不等操作符。
5)选择成员或非成员实现(经验原则)
- 赋值下标调用和成员访问箭头(=、[ ]、( )、->)等操作符必须定义为成员,将这些操作符定义为非成员将在编译时标记为错误;
- 复合赋值通常定义为类的成员(不是必须这样做);
- 改变对象状态或与给定类型紧密联系的一些操作符通常定义为成员,如自增自减解引用
- 对称操作符,如算术、相等、关系和位操作符,最好定义为非成员。
14.2 输入和输出操作符
输出操作符<<的重载
ostream& operator<<(ostream& out, const Sales_item& s) { out << s.isbn << "\t" << s.units_sold << "\t" << s.revenue << "\t" << s.avg_price(); return out; }//注意:IO操作符必须为非成员函数
输入操作符>>的重载
istream& operator>>(istream& in, Sales_item& s) { double price; in >> s.isbn >> s.units_sold >> price; // check that the inputs succeeded if (in) s.revenue = s.units_sold * price; else s = Sales_item(); // input failed: reset object to default state return in; }
14.3 算术操作符和关系操作符
一般而言,将算术和关系操作符定义为非成员函数
算术操作符
// assumes that both objects refer to the same isbn Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs) { Sales_item ret(lhs); // copy lhs into a local object that we'll return ret += rhs; // add in the contents of rhs return ret; // return ret by value }
注意:同时定义了算术操作符和相关复合赋值操作符的类,一般用复合赋值实现算术操作
相等操作符
inline bool operator==(const Sales_item &lhs, const Sales_item &rhs) { // must be made a friend of Sales_item return lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue && lhs.same_isbn(rhs); }
inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs) { return !(lhs == rhs); // != defined in terms of operator== }
14.4 赋值操作符
赋值操作符必须是类的成员,赋值必须返回对*this的引用
14.5 下标操作符
下标操作符返回引用。一般要定义两个版本:一个为非const成员并返回引用,一个为const成员返回const引用。
class Foo { public: int &operator[] (const size_t); const int &operator[] (const size_t) const; // other interface members private: vector<int> data; // other member data and private utility functions };
14.6 成员访问操作符
箭头(->)操作符一般为类成员,解引用(*)不要求定义为成员,但将它作为成员也是正确的。
class ScreenPtr { public: // constructor and copy control members as before Screen &operator*() { return *ptr->sp; } Screen *operator->() { return ptr->sp; } const Screen &operator*() const { return *ptr->sp; } const Screen *operator->() const { return ptr->sp; } private: ScrPtr *ptr; // points to use-counted ScrPtr class };
重载解引用
分别定义解引用操作的const和非const版本;const成员返回const引用以防止用户改变基础对象。
重载箭头操作符
表现得像二元操作符:接受一个对象和一个成员名。对对象解引用以获取成员,—>的右操作数对应着类成员的一个标识符。
当编写:point->action();
由于优先级规则,等价于 (point->action)();
1)如果point是指针,指向具有action成员的类对象,编译器将代码编译为调用该对象的action成员;
2)如果point是定义了operator->操作符的类,则point->action与point.operator->()->action相同,然后使用该结果重复这三步。
3)否则,代码出错。
重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
14.7 自增操作符和自减操作符
C++不要求自增自减一定作为类的成员,但是因为这些操作符改变对象状态,故倾向于作为成员。
/* * smart pointer: Checks access to elements throws an out_of_range * exception if attempt to access a nonexistent element * users allocate and free the array */ class CheckedPtr { public: // no default constructor; CheckedPtrs must be bound to an object CheckedPtr(int *b, int *e): beg(b), end(e), curr(b) { } // dereference and increment operations private: int* beg; // pointer to beginning of the array int* end; // one past the end of the array int* curr; // current position within the array };
前自增/自减(返回对象的引用)
class CheckedPtr { public: CheckedPtr& operator++(); // prefix operators CheckedPtr& operator--(); // other members as before }; // prefix: return reference to incremented/decremented object CheckedPtr& CheckedPtr::operator++() { if (curr == end) throw out_of_range ("increment past the end of CheckedPtr"); ++curr; // advance current state return *this; } CheckedPtr& CheckedPtr::operator--() { if (curr == beg) throw out_of_range ("decrement past the beginning of CheckedPtr"); --curr; // move current state back one element return *this; }
后自增/自减(返回对象的旧值)
为做区别,后缀操作符接受一个额外的int形参,使用时编译器提供0初始化它
class CheckedPtr { public: // increment and decrement CheckedPtr operator++(int); // postfix operators CheckedPtr operator--(int); // other members as before }; // postfix: increment/decrement object but return unchanged value CheckedPtr CheckedPtr::operator++(int) { // no check needed here, the call to prefix increment will do the check CheckedPtr ret(*this); // save current value ++*this; // advance one element, checking the increment return ret; // return saved state }
CheckedPtr CheckedPtr::operator--(int) { // no check needed here, the call to prefix decrement will do the check CheckedPtr ret(*this); // save current value --*this; // move backward one element and check return ret; // return saved state }
调用
CheckedPtr parr(ia, ia + size); // iapoints to an array of ints parr.operator++(0); // call postfix operator++ parr.operator++(); // call prefix operator++
14.8 调用操作符和函数对象
函数调用操作符必须声明为成员函数。定义了调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象。
struct absInt { int operator() (int val) { return val < 0 ? -val : val; } }; int i = -42; absInt absObj; // object that defines function call operator unsigned int ui = absObj(i); // calls absInt::operator(int)
将函数对象用于标准库算法
谓词(predicate)是做某些检测的函数,返回用于条件判断的类型,指出条件是否成立。
// determine whether a length of a given word is 6 or more bool GT6(const string &s) { return s.size() >= 6; } vector<string>::size_type wc = count_if(words.begin(), words.end(), GT6);
函数对象可以更灵活
// determine whether a length of a given word is longer than a stored bound class GT_cls { public: GT_cls(size_t val = 0): bound(val) { } bool operator()(const string &s) { return s.size() >= bound; } private: std::string::size_type bound; }; cout << count_if(words.begin(), word.end(), GT_cls(6) << " words 6 characters or longer" << endl;
标准库定义的函数对象
标准库定义了一组算术、关系与逻辑函数对象类,以及一组函数适配器,用于特化或扩展标准库所定义的以及自定义的函数对象类。标准库函数对象类型在functional头文件定义。
不同的函数对象定义了执行不同操作符的调用操作符;两个一元函数对象类:negate<Type>(一元减),logical_not<Type>(逻辑非)
每个函数对象类都是一个模板,我们需要为该模板提供一个类型
plus<int> intAdd; // function object that can add two int values negate<int> intNegate; // function object that can negate an int value // uses intAdd::operator(int, int) to add 10 and 20 int sum = intAdd(10, 20); // sum = 30 // uses intNegate::operator(int) to generate -10 as second parameter // to intAdd::operator(int, int) sum = intAdd(10, intNegate(10)); // sum = 0
函数对象常用于覆盖算法使用的默认操作符。
// passes temporary function object that applies > operator to two strings sort(svec.begin(), svec.end(), greater<string>());
函数对象的函数适配器
1)绑定器(bind1st,bind2nd)
count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));
2)求反器(not1,not2)
count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(), 10)));
14.9 转换与类类型
一个实参调用的非explicit构造函数定义了一个隐式转换,这种构造函数定义了到类型的转换。我们还可以定义从类型的转换,像其他转换一样,编译器将自动应用这个转换。
转换操作符
转换操作符是一种特殊的类成员函数。它定义将类类型转变为其他类型值的转换。转换操作符在类定义体内声明,在operator后跟着转换的目标类型。通用形式:
operator type();
1)不允许转换为数组或函数类型,转换为指针和引用是可以的;
2)转换函数必须是成员函数,不能指定返回类型,但是必须显式返回一个指定类型的值,形参表空;
3)因不改变被转换的对象,通常定义为const成员