C++拓展笔记3-1:三类操作符重载学习总结
2017-01-24 00:00
627 查看
本文代码引用和免责声明:
一、重载单元操作符
重载单元操作符的一个最广泛用途即重载判断操作符,它既可以用于同类对象的比较(在相应规则下逻辑或数值比较),也可以用于不同类对象的比较。作为同类对象的比较时,重载函数必须是类的成员函数;作为不同类的对象比较时,重载函数必须是全局函数。举例如下:
Circle.h文件:
Circle.cpp的重点部分:
本例实现了根据圆的周长比较圆的大小,调用示例如下:
main.cpp文件中main函数(部分):
运行结果:
对于不同对象的比较操作,可以用全局函数实现。如本例要将圆和矩形以周长为标准进行比较,则示例代码如下:
二、重载二元操作符(以重载流操作符为例)
C++默认以<<为输出基本数据类型的操作符,以>>为输入基本数据类型的操作符,并且该操作符一般情况下可层叠调用。该操作符也可重载,重载时,头函数应分别为std::ostream& 和 std::istream&。返回对象地址的目的类似于返回*this,目的是为了可以实现层叠调用。关于this指针层叠调用的操作,请参考:https://my.oschina.net/SamYjy/blog/828757 《C++中this指针用法简介》。下面就是一个在电话号码这个对象中重载输入输出操作符,使得该对象能够被直接输入和输出的例子:
PhoneNumber.h文件
PhoneNumber.cpp文件
此处,ostream&和istream&的重载操作并不是类的成员函数,而是以友元函数的方式呈现。若ostream和istream被写成类内函数,则书写和调用方式如下所示:
PhoneNumber.h:
PhoneNumber.cpp:
可能的一个调用示例:
需要注意,cout <<对于同一个文件中的非类内的ostream &方法可以进行重载,如本例的bell方法。而对于类中的方法就不行了。如本例的inputPhoneNum和outputPhoneNum方法。因此,这种写成类内函数调用的方式并不比写成类的方法简便多少。因此更加推荐将流写成类的友元函数的形式。
对于重载二元操作符的情形(操作符需要两个对象完成相应操作),重载函数仅仅在左操作对象是类的一个对象并且该函数是类的一个成员时,重载函数才推荐写成成为该类的成员函数。 例如上例的重载输出电话簿函数,输入输出流不是电话簿类的对象,该函数的功能也不是该类的成员,因此推荐第一种写法,即写成友元函数供该类使用。关于友元函数,详见博客:《C++扩展笔记3-1-1:C++友元函数和友元类用法谈》,URL:https://my.oschina.net/SamYjy/blog/828590
在这个类中,电话号码的输出还可以用C++的类型转换操作符进行实现。具体做法是将PhoneNumber.h中的重载输出符号的友元函数改为:
然后,在PhoneNumber.cpp中去掉原来重写的ostream&函数,改成以下函数:
主程序进行调用输出PhoneNumber时,应写成:
此处在PhoneNumber对象中以成员函数形式完成了对于从PhoneNumber向string的类型转化,编译时,编译器自动寻找是否有从PhoneNumber对象到string的参数,找到后进行类型转化,输出效果看上去较之重载操作符也是完全相同的。
再举一个稍微复杂一点的例子,在大数加法的实现中,重载符号+可能涉及的两个数为一个大数(作为对象)和一个基本数据。此时,如果+号表示大数加基本数据,那么+号对应的重载函数应该是成员函数。而如果倒过来,格式为基本数据+大数时,则重载函数就要写成大数类的友元函数了。
三、重载单元运算操作符(以字符串库以及++操作为例)
关于字符串库的实现,限于篇幅,具体请参考代码:
https://code.csdn.net/SamYjy/cpp_lib_string/tree/master
需要说明的是,字符串是一种非常特殊的字符数组,因此若需要修改其中一个字母,可重载[](常用于取得数组下标的符号)如下:
而如果数组是const,拒绝其他代码修改,方法头可以变为 char string::operator[](int subscript) const
有的同一个操作符可以前置于操作对象,或后置于操作对象,此时重写这样的单元操作符就要特别注意返回值。比如,参考以下代码:
这里,显然y还是5。然而如果改成y = ++x,则y的值就成了6。那么重载对象中的操作符时怎么区分呢?这就取决于重载相应操作符函数的返回值了。研究x++的本质,我们会发现其实x既在编译时被备份,又以一个被加了一的新值返回给了自身。因此,重载操作符时,返回一个备份的变量并在内存中进行操作,就能实现后置操作符;同理,直接返回对象地址则实现前置操作符。这里以Date库为例,本库具体代码请参考:https://code.csdn.net/SamYjy/cpp_date_library/tree/master
其中,日期增加一天的函数如下:
这里,重载前置++,使得代表Date对象直接被加一天的实现如下:
重载后置++操作符,使得Date先保留其值,待下一次操作时被加一天的实现如下:
这里,传入一个int虚参数的目的仅仅是为了能够使得该方法有别于前面的前置++重载方法。
总结
重载操作符实际上是一种非常特殊的C++函数,用来给予普通操作符在对象中新的操作定义。书写此类函数,要注意其返回值、是否返回地址等要素,同时在定义时也要思考该函数与此对象的关系。写好C++重载操作符,在操作对象和流等问题时可以达到事半功倍的效果。
参考资料:
Paul Deitel & Harvey Deitel, 《C++大学教程(第六版)》英文版
Paul Deitel & Harvey Deitel, C++11程序设计(英文版)(第2版)
/************************************************************************** * (C) Copyright 1992-2012 by Deitel & Associates, Inc. and * * Pearson Education, Inc. All Rights Reserved. * * * * DISCLAIMER: The authors and publisher of this book have used their * * best efforts in preparing the book. These efforts include the * * development, research, and testing of the theories and programs * * to determine their effectiveness. The authors and publisher make * * no warranty of any kind, expressed or implied, with regard to these * * programs or to the documentation contained in these books. The authors * * and publisher shall not be liable in any event for incidental or * * consequential damages in connection with, or arising out of, the * * furnishing, performance, or use of these programs. * **************************************************************************/
一、重载单元操作符
重载单元操作符的一个最广泛用途即重载判断操作符,它既可以用于同类对象的比较(在相应规则下逻辑或数值比较),也可以用于不同类对象的比较。作为同类对象的比较时,重载函数必须是类的成员函数;作为不同类的对象比较时,重载函数必须是全局函数。举例如下:
Circle.h文件:
#ifndef CIRCLE_H #define CIRCLE_H class Circle { public: Circle(int); bool operator<(const Circle &) const; bool operator>(const Circle &) const; int getPerimeter() const; //计算圆的周长 ~Circle(); private: int radius; }; #endif /* CIRCLE_H */
Circle.cpp的重点部分:
bool Circle::operator < (const Circle & c) const { if(getPerimeter() < c.getPerimeter()) return true; return false; } //大于、小于、等于、大于等于、小于等于都要分别写清楚。 bool Circle::operator > (const Circle & c) const { if(getPerimeter() > c.getPerimeter()) return true; return false; }
本例实现了根据圆的周长比较圆的大小,调用示例如下:
main.cpp文件中main函数(部分):
Circle c1(4); Circle c2(5); cout << "Area c1 < c2: " << ((c1 < c2) ? "true" : "false") << endl;
运行结果:
对于不同对象的比较操作,可以用全局函数实现。如本例要将圆和矩形以周长为标准进行比较,则示例代码如下:
#ifndef COMPARE_H #define COMPARE_H #include "Rectangle.h" #include "Circle.h" bool operator<(const Circle & c, const Rectangle & r) { if(c.getPerimeter() < r.getPerimeter()) return true; return false; } #endif /* COMPARE_H */
二、重载二元操作符(以重载流操作符为例)
C++默认以<<为输出基本数据类型的操作符,以>>为输入基本数据类型的操作符,并且该操作符一般情况下可层叠调用。该操作符也可重载,重载时,头函数应分别为std::ostream& 和 std::istream&。返回对象地址的目的类似于返回*this,目的是为了可以实现层叠调用。关于this指针层叠调用的操作,请参考:https://my.oschina.net/SamYjy/blog/828757 《C++中this指针用法简介》。下面就是一个在电话号码这个对象中重载输入输出操作符,使得该对象能够被直接输入和输出的例子:
PhoneNumber.h文件
//此处省略头文件和预处理 class PhoneNumber { friend std::ostream & operator<<( std::ostream &, const PhoneNumber & ); friend std::istream & operator>>( std::istream &, PhoneNumber & ); private: std::string areaCode; // 4-digit area code std::string exchange; // 4-digit exchange std::string line; // 4-digit line }; // end class PhoneNumber #endif
PhoneNumber.cpp文件
// overloaded stream insertion operator; cannot be // a member function if we would like to invoke it with // cout << somePhoneNumber; ostream& operator<<( ostream &output, const PhoneNumber &number ) { output << "(" << number.areaCode << ") " << number.exchange << "-" << number.line; return output; // enables cout << a << b << c; } // end function operator<< // overloaded stream extraction operator; cannot be // a member function if we would like to invoke it with // cin >> somePhoneNumber; istream& operator>>( istream &input, PhoneNumber &number ) { input >> setw( 4 ) >> number.areaCode; // input area code input.ignore( 1 ); // skip ) and space input >> setw( 4 ) >> number.exchange; // input exchange input.ignore(); // skip dash (-) input >> setw( 4 ) >> number.line; // input line return input; // enables cin >> a >> b >> c; } // end function operator>>
PhoneNumber phone1, phone2; // create object phone cout << "Enter phone number in the form 0123 4567 8890:" << endl; // cin >> phone invokes operator>> by implicitly issuing // the global function call operator>>( cin, phone ) cin >> phone1; cout << "Enter phone number in the form 0123 4567 8890:" << endl; // cin >> phone invokes operator>> by implicitly issuing // the global function call operator>>( cin, phone ) cin >> phone2; cout << "The phone number entered were: "; // cout << phone invokes operator<< by implicitly issuing // the global function call operator<<( cout, phone ) cout << phone1 << " and " << phone2 << endl;
此处,ostream&和istream&的重载操作并不是类的成员函数,而是以友元函数的方式呈现。若ostream和istream被写成类内函数,则书写和调用方式如下所示:
PhoneNumber.h:
//此处省略头文件和预处理 class PhoneNumber { public: std::ostream & outputPhoneNum( std::ostream & ); std::istream & inputPhoneNum( std::istream & ); private: std::string areaCode; // 4-digit area code std::string exchange; // 4-digit exchange std::string line; // 4-digit line }; // end class PhoneNumber #endif
PhoneNumber.cpp:
ostream& PhoneNumber::outputPhoneNum(ostream &output) { return output << "(" << areaCode << ") " << exchange << "-" << line; } // end function operator<< // overloaded stream extraction operator; cannot be // a member function if we would like to invoke it with // cin >> somePhoneNumber; istream& PhoneNumber::inputPhoneNum( istream &input ) { input >> setw( 4 ) >> areaCode; // input area code input.ignore( 1 ); // skip ) and space input >> setw( 4 ) >> exchange; // input exchange input.ignore(); // skip dash (-) input >> setw( 4 ) >> line; // input line return input; // enables cin >> a >> b >> c; } // end function operator>>
可能的一个调用示例:
//此处省略头文件和预处理 ostream& bell(ostream& output) { return output << "Hello" << '\a'; } int main(int argc, char** argv) { cout << bell << endl; PhoneNumber phone1, phone2; // create object phone cout << "Enter phone number in the form 0123 4567 8890:" << endl; //对象操作符不支持<<符号 phone1.inputPhoneNum(cin); phone1.outputPhoneNum(cout); return 0; }
需要注意,cout <<对于同一个文件中的非类内的ostream &方法可以进行重载,如本例的bell方法。而对于类中的方法就不行了。如本例的inputPhoneNum和outputPhoneNum方法。因此,这种写成类内函数调用的方式并不比写成类的方法简便多少。因此更加推荐将流写成类的友元函数的形式。
对于重载二元操作符的情形(操作符需要两个对象完成相应操作),重载函数仅仅在左操作对象是类的一个对象并且该函数是类的一个成员时,重载函数才推荐写成成为该类的成员函数。 例如上例的重载输出电话簿函数,输入输出流不是电话簿类的对象,该函数的功能也不是该类的成员,因此推荐第一种写法,即写成友元函数供该类使用。关于友元函数,详见博客:《C++扩展笔记3-1-1:C++友元函数和友元类用法谈》,URL:https://my.oschina.net/SamYjy/blog/828590
在这个类中,电话号码的输出还可以用C++的类型转换操作符进行实现。具体做法是将PhoneNumber.h中的重载输出符号的友元函数改为:
explicit operator std::string() const;
然后,在PhoneNumber.cpp中去掉原来重写的ostream&函数,改成以下函数:
PhoneNumber::operator string() const { return "(" + areaCode + ") " + exchange + "-" + line; }
主程序进行调用输出PhoneNumber时,应写成:
cout << static_cast<string>(phone) << endl;
此处在PhoneNumber对象中以成员函数形式完成了对于从PhoneNumber向string的类型转化,编译时,编译器自动寻找是否有从PhoneNumber对象到string的参数,找到后进行类型转化,输出效果看上去较之重载操作符也是完全相同的。
再举一个稍微复杂一点的例子,在大数加法的实现中,重载符号+可能涉及的两个数为一个大数(作为对象)和一个基本数据。此时,如果+号表示大数加基本数据,那么+号对应的重载函数应该是成员函数。而如果倒过来,格式为基本数据+大数时,则重载函数就要写成大数类的友元函数了。
三、重载单元运算操作符(以字符串库以及++操作为例)
关于字符串库的实现,限于篇幅,具体请参考代码:
https://code.csdn.net/SamYjy/cpp_lib_string/tree/master
需要说明的是,字符串是一种非常特殊的字符数组,因此若需要修改其中一个字母,可重载[](常用于取得数组下标的符号)如下:
char& string::operator[](int subscript) { // test for subscript out of range if (subscript < 0 || subscript >= length) { cerr << "Error: Subscript " << subscript << " out of range" << endl; exit(1); // terminate program } // end if return sPtr[ subscript ]; // non-const return; modifiable lvalue } // end function operator[]
而如果数组是const,拒绝其他代码修改,方法头可以变为 char string::operator[](int subscript) const
有的同一个操作符可以前置于操作对象,或后置于操作对象,此时重写这样的单元操作符就要特别注意返回值。比如,参考以下代码:
int x = 5; int y = x++; cout << "if x = 5, y = x++ is: " << y << endl; cout << "However, x becomes " << x << endl;
这里,显然y还是5。然而如果改成y = ++x,则y的值就成了6。那么重载对象中的操作符时怎么区分呢?这就取决于重载相应操作符函数的返回值了。研究x++的本质,我们会发现其实x既在编译时被备份,又以一个被加了一的新值返回给了自身。因此,重载操作符时,返回一个备份的变量并在内存中进行操作,就能实现后置操作符;同理,直接返回对象地址则实现前置操作符。这里以Date库为例,本库具体代码请参考:https://code.csdn.net/SamYjy/cpp_date_library/tree/master
其中,日期增加一天的函数如下:
// function to help increment the date void Date::helpIncrement() { // day is not end of month if ( !endOfMonth( day ) ) ++day; // increment day else if ( month < 12 ) // day is end of month and month < 12 { ++month; // increment month day = 1; // first day of new month } // end if else // last day of year { ++year; // increment year month = 1; // first month of new year day = 1; // first day of new month } // end else } // end function helpIncrement
这里,重载前置++,使得代表Date对象直接被加一天的实现如下:
// overloaded prefix increment operator Date& Date::operator++() { helpIncrement(); // increment date return *this; // reference return to create an lvalue } // end function operator++
重载后置++操作符,使得Date先保留其值,待下一次操作时被加一天的实现如下:
// overloaded postfix increment operator; note that the // dummy integer parameter does not have a parameter name Date Date::operator++(int) { Date temp = *this; // hold current state of object helpIncrement(); // return unincremented, saved, temporary object return temp; // value return; not a reference return } // end function operator++
这里,传入一个int虚参数的目的仅仅是为了能够使得该方法有别于前面的前置++重载方法。
总结
重载操作符实际上是一种非常特殊的C++函数,用来给予普通操作符在对象中新的操作定义。书写此类函数,要注意其返回值、是否返回地址等要素,同时在定义时也要思考该函数与此对象的关系。写好C++重载操作符,在操作对象和流等问题时可以达到事半功倍的效果。
参考资料:
Paul Deitel & Harvey Deitel, 《C++大学教程(第六版)》英文版
Paul Deitel & Harvey Deitel, C++11程序设计(英文版)(第2版)
相关文章推荐
- C++学习笔记序列之返回语句区别总结
- 嵌入式开发之C++基础学习笔记2--总结所有基础用法
- C++学习笔记14,private/protected/public继承,私有继承,保护继承,公有继承(五)(总结)
- C++ 学习笔记(一些新特性总结3)
- C_C++的union的学习笔记总结
- C++学习笔记9-操作符重载
- C++ 学习笔记(一些新特性总结 1)
- C/C++学习笔记9:sizeof总结
- C++ 学习笔记(一些新特性总结3)
- C_C++的union的学习笔记总结
- C++学习笔记14,private/protected/public继承,私有继承,保护继承,公有继承(五)(总结)
- C++基础学习笔记----第十三课(操作符重载-下)
- C++学习笔记-类相关问题总结
- 操作符重载——C/C++学习笔记
- C++基础学习笔记----第十二课(操作符重载-上)
- C++学习之路—继承与派生(四)拓展与总结
- C++学习笔记&总结
- C++入门学习笔记(三)--类的操作符重载
- C++ 学习笔记(一些新特性总结 1)
- C/C++学习笔记8:内存中数据对齐的问题总结