1。在什么情况下重载操作符与内置操作符不同?在什么情况下重载操作符与内置操作符相同?
不同:重载操作符必须具有至少一个类类型或枚举类型的操作数。重载操作符不保证操作数的求值顺序,例如对 && 和 || 的重载版本不再具有“短路求值”的特性,两个操作数都要进行求值,而且不规定操作数的求值顺序。
相同:对于优先级和结合性及操作数的数目都不变。
注意:操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元
示例:
1 class Sales_item 2 { 3 friend std::istream& operator >> ( std::istream&, Sales_item& ); 4 friend std::ostream& operator <<(std::ostream&, const Sales_item&); 5 public: 6 Sales_item& operator += ( const Sales_item& ); 7 }; 8 Sales_item operator+ ( const Sales_item&, const Sales_item& ) 9 10 /* 11 其中,复合赋值操作符定义为public成员,输入输出操作符需要访问Sales_item类的成员,所以需定义为友元,加操作符可以用public成员 12 +=类实现,所以无需定义为Sales_item类的友元 13 */
2、选择成员或非成员实现
(1)赋值(=)、小标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
(2)像赋值一样,复合赋值操作符通常应该定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义为非成员复合赋值操作符,不会出现编译错误。
(3)改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常应该定义为类成员。
(4)对称的操作符,如算数操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。
3、输出操作符重载练习
1 class CheckoutRecord 2 { 3 public: 4 // .. 5 friend ostream& operator<<(ostream&, CheckoutRecond&);
6 private:
7 double book_id;
8 string title;
9 Date date_borrowed;
10 Date date_due;
11 pair<string,string> borrower;
12 vector< pair<string, string>* > wait_list;
13 };
14 15 ostream& operator << (ostream& out, const CheckoutRecord& s) 16 { 17 out << s.book_id << "\t" << s.title<< "\t" << s.date_borrowed<< "\t" << s.date_due 18 << "\t" ; 19 out<< " borrower:" <<s.borrower.first << "," << s.borrower.second << endl; 20 out << " wait_list: " << endl; 21 for ( vector< pair<string,string>* >::const_iterator it = s.wait_list.begin(); 22 it != s.wait_list.end(); ++it ) 23 { 24 out << "\t" << (*it)->first << "," << (*it)->second << endl; 25 } 26 return out; 27 }
4、输入操作符重载练习---注意第二个参数是对要读对象的非const引用,该形参必须为非const,输入和输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。
1 class CheckoutRecord 2 { 3 public: 4 // … 5 friend istream& operator>>( istream&, CheckoutRecord& ); // 声明为类的友元 6 // … 7 }; 8 istream & operator>> ( istream& in, CheckoutRecord& c ) 9 { 10 cout << " Input bookid(double) and title(string) :\n"; 11 in >> c.book_id >> c.title; 12 13 // Input Data data_borrowed and data_due 14 cout << " Input data_borrowed (3 ints: year, month , day) :\n"; 15 in >> c.date_borrowed; //.year >> c.date_borrowed.month >> c.date_borrowed.day; 16 // Input Data data_due and data_due 17 cout << " Input data_due (3 ints: year, month , day) :\n"; 18 in >> c.date_due;//.year >> c.date_due.month >> c.date_due.day; 19 20 // Input the pair<string,string> borrower 21 cout << " Input the pair<string,string> borrower (string) :\n"; 22 in >> c.borrower.first >> c.borrower.second; 23 if ( !in ) 24 { 25 c = CheckoutRecord(); 26 return in; 27 } 28 29 // Input wait_list 30 cout << " Input the wait_list (string) :\n"; 31 c.wait_list.clear(); //删除元素 32 while ( in ) 33 { 34 pair<string, string> *ppr = new pair<string, string>; 35 in >> ppr->first >> ppr->second; 36 if ( !in ) 37 { 38 delete ppr; 39 return in; 40 } 41 c.wait_list.push_back( ppr ); 42 } 43 44 return in; 45 }
5、为Sales_item类定义相等操作符
1 inline bool operator==(const Sales_item &lhs, const Sales_item &rhs) 2 { 3 //must be made a friend of Sales_item 4 return lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue && lhs.same_isbn(rhs); 5 } 6 inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs) 7 { 8 return !(lhs == rhs); 9 }
6、赋值操作符---赋值操作符可以重载,无论形参为何种类型,赋值操作符必须定义为成员函数,这点和复合赋值操作符不同;
赋值必须返回对*this的引用
示例:为CheckoutRecord类定义赋值操作符
1 CheckoutRecord& CheckoutRecord::operator =( const CheckoutRecord& cr ) 2 { 3 book_id = cr.book_id; 4 title = cr.title; 5 date_borrowed = cr.date_borrowed; // 前提:必须在Date类里也重载操作符= 6 date_due = cr.date_due; // as before 7 // 对pair进行赋值操作 8 borrower.first = cr.borrower.first; 9 borrower.second = cr.borrower.second; 10 11 // 对vector进行赋值操作 12 wait_list.clear(); // 首先清空 13 for ( vector< pair<string,string>* >::const_iterator it = cr.wait_list.begin(); 14 it != cr.wait_list.end(); ++ it ) 15 { 16 pair<string, string> *ppr = new pair<string, string>; 17 ppr->first = (*it)->first; 18 ppr->second = (*it)->second; 19 wait_list.push_back( ppr ); 20 } 21 22 return *this; 23 }
7、下标操作符
注意:
(1)下标操作符必须定义为类成员函数
(2)定义下标操作符比较复杂的地方在于:它在用作赋值的左右操作数时都应该能表现正常,下标操作符出现在左边,必须生成左值,可以知道引用作为返回类型而得到左值。只要下标操作符返回引用,就可用作赋值的任意一方。
(3)可以对const和非const对象使用下标也是个好主意。应用const对象时,返回值应为const引用,因此不能用作赋值目标。类定义下标操作符时,一般需定义两个版本,一个为非const成员并返回引用,另个一为const成员并返回const引用。
1 class Foo 2 { 3 public: 4 int &operator[](const size_t); 5 const int &operator[](const size_t) const; 6 private: 7 vector<int> data; 8 }; 9 10 int& Foo::operator[](const size_t index) 11 { 12 // return data[index]; //no range checking on index 没有检查下标是否越界 13 return data.at(index); //使用 at 可检查下标是否越界 14 } 15 const int& Foo::operator[](const size_t index) const 16 { 17 return data[index]; //no range checking on index 没有检查下标是否越界 18 }
8、自增操作符和自减操作符,成员访问操作符(解引用/箭头操作符)
定义一个CheckedPtr版本,保存Screen数组。为该类实现重载的自增、自减。解引用和箭头等操作符。
1 class CheckedPtr 2 { 3 public: 4 CheckedPtr( Screen *b, Screen *e ): beg( b ), end( e ), curr( b ) { } 5 6 // 自增、自减 前缀式 7 CheckPtr& operator++(); 8 CheckPtr& operator--(); 9 // 自增、自减 后缀式(传递的参数没用,但是是必须的,为了与前缀式区分开) 10 CheckPtr operator++( int ); //为了与内置操作符一致,后缀式操作符应返回旧值,并且,应作为值返回,而不是返回引用 11 CheckPtr operator--( int ); 12 13 // 箭头操作符 14 Screen* operator->(); 15 const Screen* operator->() const; 16 17 // 解引用操作 18 Screen& operator*(); 19 const Screen& operator*(); 20 private: 21 Screen *beg; 22 Screen *end; 23 Screen *curr; 24 }; 25 26 CheckPtr& CheckPtr::operator++() 27 { 28 if ( curr == end ) 29 throw out_of_range( "increment past the end of CheckedPtr"); 30 ++curr; 31 return *this; 32 } 33 CheckPtr& CheckPtr::operator--() 34 { 35 if ( curr == begin ) 36 throw out_of_range( "decrement past the beginning of CheckedPtr"); 37 --curr; 38 return *this; 39 } 40 // 后缀式 41 CheckPtr CheckPtr::operator++( int ) //调用前缀操作符,不需要检查是否越界,那个检查以及必要的throw在相应的前缀式操作符中完成 42 { 43 CheckedPtr temp( *this ); 44 ++*this; 45 return temp; 46 } 47 CheckPtr CheckPtr::operator--( int ) 48 { 49 CheckedPtr temp( *this ); 50 --*this; 51 return temp; 52 } 53 // 箭头操作符 54 Screen* CheckedPtr::operator->() 55 { 56 return curr; 57 } 58 const Screen* CheckedPtr::operator->() const 59 { 60 return curr; 61 } 62 // 解引用操作符 63 Screen& CheckedPtr::operator*() 64 { 65 if ( curr == end ) 66 throw out_of_range( "invalid current pointer."); 67 return *curr; 68 } 69 const Screen& CheckedPtr::operator*() const 70 { 71 if ( curr == end ) 72 throw out_of_range( "invalid current pointer."); 73 return *curr; 74 }
9、重载函数调用操作符、函数对象
(1) 函数调用操作符必须声明为成员函数。
(2)一个类可以定义函数调用操作符的多个版本,由形参的数目或类别加以区别
(3)定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象
1 #include <iostream> 2 struct absInt 3 { 4 int operator() (int val) 5 { 6 return val < 0 ? -val : val; 7 } 8 }; 9 10 int main() 11 { 12 int i = -42; 13 absInt absObj; 14 unsigned int ui = absObj(i); // calls absInt::operator(int) 15 std::cout << ui << std::endl; // print 42 16 system("pause"); 17 return 0; 18 }
函数对象可以比函数更灵活
1 /** 2 *使用标准库算法和GT_cls类,编写一个程序查找序列中第一个比指定值大的元素。 3 */ 4 #include <iostream> 5 #include <vector> 6 #include <algorithm> 7 using namespace std; 8 9 class GT_cls 10 { 11 public: 12 GT_cls(size_t val = 0):bound(val) {} 13 bool operator()(const int &ival) 14 { 15 return ival > bound; 16 } 17 private: 18 size_t bound; 19 }; 20 21 int main() 22 { 23 vector<int> ivec; 24 int ival; 25 cout << "Enter numbers(Ctrl+z to end):" << endl; 26 while(cin >> ival) 27 ivec.push_back(ival); 28 cin.clear(); //始输入流重新有效 29 int spval; 30 cout << "Enter a specified value: " << endl; 31 cin >> spval; //读入用户查找的指定值 32 33 vector<int>::iterator itor = find_if(ivec.begin(), ivec.end(), GT_cls(spval)); 34 if(itor != ivec.end()) 35 cout << "the first element that is larger than " << spval << " is " << *itor << endl; 36 else 37 cout << "no element that is larger than " << spval << endl; 38 39 system("pause"); 40 return 0; 41 }
1 /** 2 *编写BT_cls的类,测试给定string对象的长度是否与其边界相匹配。 3 *报告输入中有多少单词的长度在1到10之间。 4 */ 5 6 #include <iostream> 7 #include <string> 8 #include <vector> 9 #include <algorithm> 10 using namespace std; 11 12 class BT_cls 13 { 14 public: 15 BT_cls( size_t len1 = 0, size_t len2 = 0 ) 16 { 17 if ( len1 < len2 ) 18 { 19 minlen = len1; 20 maxlen = len2; 21 } 22 else 23 { 24 minlen = len2; 25 maxlen = len1; 26 } 27 } 28 29 bool operator() ( const string &s ) 30 { 31 return ( s.size() >= minlen && s.size() <= maxlen ); 32 } 33 34 private: 35 std::string::size_type minlen, maxlen; 36 }; 37 bool isShorter( const string &s1, const string &s2 ) 38 { 39 return s1.size() < s2.size(); 40 } 41 42 43 int main() 44 { 45 std::cout << " Input some words( ctrl + z to end ):" << std::endl; 46 vector<string> text; 47 string word; 48 while ( std::cin >> word ) 49 { 50 text.push_back( word ); 51 } // end of input the text 52 53 // deal with text---对输入的序列去掉重复的单词 54 sort( text.begin(), text.end() ); //使用unique算法之前要先排序才能将相邻重复元素排到最后 55 text.erase( unique(text.begin(), text.end()), text.end() );//删除重复的元素 56 stable_sort( text.begin(), text.end(), isShorter );//将元素按长度排序,长度相同的仍按照字典序排列 57 58 // to count how many words' length are between 1-10 59 vector<string>::size_type cnt = count_if( text.begin(), text.end(), BT_cls( 1, 10 )); 60 std::cout << " There are " << cnt << " words' length is between 1-10." << std::endl; 61 62 system("pause"); 63 return 0; 64 }
10、标准库定义的函数对象
标准库提供了一组函数适配器,用于特化和扩展一元和二元函数对象。函数适配器分为两类:
(1)绑定器,是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
(2)求反器,是一种函数适配器,它将谓词函数对象的真值求反。
标准库定义了两个绑定器适配器:bind1st 和 bind2nd,每个绑定期接收一个函数对象和一个值,
bind1st 将给定值绑定到二元函数对象的第一个实参;bind2nd 将给定值绑定到二元函数对象的第二个实参;
例如:计算一个容器中所有小于或等于 10 的元素个数,可以这样给 count_if 传递值:
count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));
第三个参数使用函数适配器,该适配器返回一个函数对象,该对象用10作右操作数应用 <= 操作符,这个调用计算输入范围中小于或等于10的元素的个数
标准库还定义了两个求反器:not1 和 not2,
not1 将一元函数对象的真值求反, not2 将二元函数对象的真值求反
例如:对于less_equal函数对象的绑定求反,可以编写这样的代码:
count_if(vec.begin(), vec,end(), not1(bind2nd(less_equal<int>(), 10)));