第10章 运算符重载
1.C++ 的内置数据类型(int, float等)可与内置算术运算符(+, *,/ 等)和关系运算符(>, <, ==, !=)配合使用。
2.并非所有的内置运算符都能与每一种数据类型配合使用。
例如,字符串不能进行乘法操作,%只适用于整型数。
3.定义一个新的类时,可以重新定义或者重载已经存在的运算符
4.通过为类添加运算符函数实现对已有的运算符重载,从而完成特定的操作
也可以像调用其他函数那样来调用它:
如 t2 = t1 +4;
可以写成:t2 = t1.operator+( 4 ) ;
Rules of operator overloading(运算符重载的规则)
1.除了后面列举的五个运算符( . .* :: ?: sizeof)以外,附录B中的所有C++运算符都可以重载。
2.不能发明新的运算符。例如,不可以重载<>来表示“不等于”或者重载**来表示“?的幂”。
3.运算符重载后的操作数个数与原运算符的操作数个数必须相同。
例如,重载运算符==必须有两个操作数,重载运算符++应只有一个操作数。
4.运算符重载后仍保持其原有的优先级。例如,无论是否对*进行重载,它的优先级都将高于 + 和 - 。
5.重载运算符时不能使用默认实参。
用于内置数据类型(如i n t 、float 等)时,运算符的含义不能被重新定义。
Overloading prefix and postfix forms of ++(重载运算符++的前缀和后缀形式)
对于自增运算符++,需要对其前缀和后缀形式分别进行重载。
1 #include <iostream> 2 using namespace std ; 3 4 class time24 // A simple 24-hour time class. 5 { 6 public: 7 time24( int h = 0, int m = 0, int s = 0 ) ; 8 void set_time( int h, int m, int s ) ; 9 void get_time( int& h, int& m, int& s ) const ; 10 time24 operator+( int secs ) const ; 11 time24 operator+( const time24& t ) const ; 12 time24 operator++() ; // 前缀形式的自增运算符重载 13 time24 operator++( int ) ; // int是虚拟的整型实参,用于区别这个是后缀形式的运算符重载 14 private: 15 int hours ; // 0 to 23 16 int minutes ; // 0 to 59 17 int seconds ; // 0 to 59 18 } ;
Overloading << and >> (重载运算符<<和>>)
>> 和 << 都被应用为非成员函数。
cout 是类ostream 的对象,cin 是类istream的对象。
这些对象和类是在头文件iostream 中定义的。
任何使用输出流的函数都会改变cout,因此向函数传递的必须是输出流的引用,而不能传值。
// 重载运算符<< ostream& operator<<( ostream& os, const time24& t ) { // Format time24 object, precede single digits with a 0. int h, m, s ; t.get_time( h, m, s ) ; os << setfill( '0' ) << setw( 2 ) << h << ":" << setw( 2 ) << m << ":" << setw( 2 )<< s << endl ; return os ; } //重载运算符>> istream& operator>>( istream& is, time24& t ) { // Input a time24 object data in the format h:m:s. int h, m, s ; do is >> h ; while ( h<0 || h>23 ) ; // Ignore the separator. is.ignore( 1 ) ; do is >> m ; while ( m<0 || m>60 ) ; // Ignore the separator. is.ignore( 1 ) ; do is >> s ; while ( s<0 || s>60 ) ; t.set_time ( h, m, s ) ; return is ; }
Conversion operators(转换运算符)
转换运算符函数用于将一个类对象转换成内置数据类型或其他类对象。
转换运算符成员函数与它要转换成的数据类型具有相同的名字。
转换运算符函数与其他重载运算符函数有两点不同:
第一,转换运算符函数无需实参。
第二,转换运算符函数没有返回类型,甚至void 也不行。
可以根据转换运算符函数的名字推出函数的返回类型,
//例如,如果要将一个time24对象转换为int类型,则在类中定义该转换运算符函数的名字为operator int。
1 #include<iostream> 2 #include <iomanip> 3 using namespace std ; 4 class time24 // A simple 24 hour time class. 5 { 6 public: 7 operator int() ; 8 } ;
9 time24::operator int()
10 {
11 int no_of_seconds = hours * 3600 + minutes * 60 + seconds ;
12 return no_of_seconds ;
13 }
14 main()
15 {
16 time24 t( 1, 2, 3 ) ;
17 int s ;
18 s = t ; // 把一个time24的对象t转换为一个int型的值
19 }
Use of friend functions(使用友元函数)
私有成员的值可以使用类成员函数来读取。
time24 类的hours、minutes 和seconds 可以使用类成员函数get_time( )和set_time( )来读取。
在一个非成员函数中,要想直接访问类的私有数据成员,该函数必须声明为这个类的友元函数。
为了将非成员函数operator >>和operator <<声明为time24 类的友元函数,
在类中应加入下列声明:
friend 返回类型 operator 运算符(形参表)
这些声明可以放在类中的任何地方,但通常将其放在public部分。
注意:友元函数破坏了面向对象程序的基本准则,即数据隐藏。
类的友元可以访问类的所有私有数据,应尽量减少对它的使用。
析构函数
析构函数总是与类名相同,只是函数名前多了一个波浪号~
如果类中含有指针数据成员,指向由该类分配的内存空间,则通常需要使用析构函数。
在这种情况下,析构函数用来释放由类分配的内存。
正如创建类对象时会自动调用构造函数一样,在类对象被撤销前也会自动调用析构函数。
当程序结束或创建该对象的函数执行结束时,该对象被撤销。
一个类只能有一个析构函数。
析构函数无形参,无返回值。
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,下列任何一种情况发生时,都会自动调用此函数。
(i) 创建并初始化一个新对象时。
float trans_amounts[] = { 56.88, 89.65, 15.76, 63.91 } ;
transactions trans_object1( 1, 4, trans_amounts ) ; transactions trans_object2 = trans_object1 ; // trans_object2 is created and initialised with // trans_object1 member values by the copy constructor.
(ii) 按传值方式将一个对象传给函数时。
1 main() 2 { 3 void any_function( transaction tr ) ; 4 float trans_amounts[] = { 39.87, 6.43, 7.34, 75.12, 9.65 } ; 5 transactions trans( 3, 5, trans_amounts ) ; 6 any_function ( trans ) ; // trans is passed by value to the function 7 // i.e. a copy of trans is passed to tr. 8 ... 9 } 10 void any_function( transactions tr )//对应第六行 11 { 12 // tr receives a copy of trans. 13 // The copy constructor does this. 14 ... 15 }
(iii) 从函数返回对象本身时。
1 main() 2 { 3 ... 4 transactions another_function() ; 5 ... 6 trans = another_function() ; // A copy of tr is passed back to trans. 7 // The copy constructor does this. 8 ... 9 } 10 transactions another_function() 11 { 12 float trans_amounts[] = { 70.00, 68.47} ; 13 transactions tr( 4, 2, trans_amounts ) ; 14 ... 15 return tr ; // tr is returned by value.//从第15行返回第6行 16 }
每个类都有一个默认的拷贝构造函数,它与默认的赋值运算符类似,逐个成员进行复制。
对于大多数的类而言,默认的拷贝构造函数和默认的赋值运算符都足以达到我们的要求。
但是,对于需要动态分配和释放内存的类,默认的拷贝构造函数与默认的赋值运算符存在同样的问题。
通常,一个定义了自己的赋值运算符的类也必须定义它自己的拷贝构造函数。
1 // Program Example P10L 2 // Program to demonstrate the use of the copy constructor for 3 // a class with a pointer data member. 4 #include <iostream> 5 using namespace std ; 6 7 class transactions // A class containing a pointer data member. 8 { 9 public: 10 transactions( int ac_num, int num_transactions, 11 const float transaction_values[] ) ; 12 // Purpose: Constructor. 13 transactions( const transactions& tr ) ; 14 // Purpose: Copy Constructor. 15 ~transactions() ; 16 / / Purpose: Destructor. 17 void modify_transaction( int transaction_index, float amount ) ; 18 // Purpose : Modify a transaction amount. 19 // Arguments: transaction_index - transaction to change. 20 // amount - new transaction amount. 21 void display() const ; 22 // Purpose: Display transactions. 23 const transactions& operator=( const transactions& t ) ; 24 // Purpose: Overloaded assignment operator. 25 private: 26 int account_number ; 27 int number_of_transactions ; 28 float *transaction_amounts ; // Pointer to array of transactions. 29 } ; 31 // Constructor. 32 transactions::transactions( int ac_num, int num_transactions, 33 const float transaction_values[] ) 34 { 35 account_number = ac_num ; 36 number_of_transactions = num_transactions ; 37 // Allocate storage for transactions. 38 transaction_amounts = new float[number_of_transactions] ; 39 for ( int i = 0 ; i < number_of_transactions ; i++ ) 40 transaction_amounts[i] = transaction_values[i] ; 41 }
42
43 //Copy constructor
44 transactions::transactions( const transactions& tr ) 45 { 46 account_number = tr.account_number ; 47 number_of_transactions = tr.number_of_transactions ; 48 transaction_amounts = new float[number_of_transactions] ; 49 for ( int i = 0 ; i < number_of_transactions ; i++ ) 50 transaction_amounts[i] = tr.transaction_amounts[i] ; 51 }
交易类的拷贝构造函数被定义在第44到51行。
与其他的构造函数相同,拷贝构造函数没有返回类型,甚至连void 也不能有。
一个拷贝构造函数只能有一个形参,它是指向同一类的对象的引用。
这个形参是一个const 引用,拷贝构造函数不能通过这个引用来修改传递给它的对象。
69 void transactions::display() const 70 { 71 cout << " Account Number " << account_number << " Transactions: " ; 72 for ( int i = 0 ; i < number_of_transactions ; i++ ) 73 cout << transaction_amounts[i] << ' ' ; 74 cout << endl ; 75 } 94 main() 95 { 96 float trans_amounts1[] = { 25.67, 6.12, 19.86, 23.41, 1.21 } ; 97 transactions trans_object1( 1, 5, trans_amounts1 ) ; 98 transactions trans_object2 = trans_object1 ; 99 100 cout << "trans_object1:" << endl ; 101 trans_object1.display() ; 102 cout << "trans_object2:" << endl ; 103 trans_object2.display() ; 104 }
在第98行中,拷贝构造函数被调用。
输出结果为:
重载下标运算符
略....
Programming Pitfalls(易犯的错误)
1. 重载算术运算符(如+、-)并没有重载相应的复合运算符(如+=、-=),
如果要使用,必须对它们单独进行重载。
2.Using a class such as the time24 class, the following will work as expected:
time24 t( 1, 2, 3 ) ;
++( ++t ) ; // t is 1:02:05
++(++t);等价于(t.operator++( )).operator++( );
( t.operator++() ).operator++() ;
这里,表达式t.operator++( )先将t 的值增1,然后返回t 的引用用于第二次调用,于是t的值被增1两次。 Consider the equivalent test using the overloaded postfix operator++.
time24 t( 1, 2, 3 ) ;
( t++ )++ ; // t is now 1:02:04
(t++)++;等价于(t.operator++(0)).operator++(0) ;
( t.operator++(0) ).operator++( 0 ) ; // 0 = dummy int argument
这里,表达式t.operator++(0)将t的值增1,同时返回一个临时对象(不是t)的副本用于第二次调用。结果是t增1了一次,临时对象的副本也增1 了一次。
没有其他方法能解决这个问题,除非将(t++)++;拆分成两个语句t++; t++;才能使t 的值增1 两次。
3. 如果一个类使用了一个指针数据成员指向动态分配的内存,那么这个类必须定义一个重载赋值运算符、一个析构函数和一个拷贝构造函数。通常,如果一个类需要其中之一,那么必然会三个都需要。