- C++预定义数据类型提供了丰富的运算符集,以简捷,明确的方式操作数据。程序员也可以重载运算符函数,将运算符用于操作自定义的数据类型。
- 本章讨论运算符重载的语法和使用,还讨论与运算符函数较为相似的类型转换函数。
例如:
int x , y ;
y = x + y ;
complex c1 , c2 ; // 复数类对象
c1 = Cadd (c1 , c2 ) ; // 调用函数计算两个复数的和
matrix m1 , m2 ; // 矩阵类对象
m1 = Madd ( m1 , m2 ) ; // 调用函数计算两个矩阵的和
7.1 运算符重载规则
7.1.1 重载运算符的限制
重载运算符函数可以对运算符作出新的解释,但原有基本语义不变:
- 不改变运算符的优先级
- 不改变运算符的结合性
- 不改变运算符所需要的操作数
- 不能创建新的运算符
7.1.2 运算符重载的语法形式
- 运算符函数是一种特殊的成员函数或友元函数
- 成员函数的语法形式为:
类型 类名 :: operator op ( 参数表 )
{
// 相对于该类定义的操作
}
- 一个运算符被重载后,原有意义没有失去,只是定义了相对一特定类的一个新运算符
例: 设计一个安全计数器
#include<iostream>
#include<cstdlib>
using namespace std;
class Calculator
{ public:
Calculator() { value = 0 ; } ;
void operator ++ () ;//重载自增运算符
void operator -- () ; //重载自减运算符
unsigned int operator() () ;//重载括号运算符
private:
unsigned int value;
};
int main()
{ Calculator Counter ;
int i ;
for( i = 0 ; i < 5 ; i ++ )
{
//Counter.operator++();//调用重载版本
++ Counter;//调用重载版本
cout << "\n Counter = " << Counter() ;
}
for( i = 0 ; i <= 5 ; i ++ )
{
//Counter.operator--();调用重载版本
-- Counter;
cout << "\n Counter = " << Counter() ;
}
}
void Calculator::operator ++ ()
{ if ( value < 65535 ) value ++ ;//使用系统预定义版本
else //溢出处理
{ cout << "\nData overflow !" << endl ;
exit( 0 ) ; //abort() //退出
}
}
void Calculator::operator --()
{ if ( value > 0 ) value -- ;//使用系统预定义版本
else
{ cout << "\n Data overflow !" << endl ;
exit( 0 ) ;
}
}
unsigned int Calculator::operator() ()
{ return value ; }
结果为:
7.2 用成员或友元函数重载运算符
- 运算符函数可以重载为成员函数或友元函数
- 关键区别在于成员函数具有 this 指针,友元函数没有this指针
- 不管是成员函数还是友元函数重载,运算符的使用方法相同。
- 但传递参数的方式不同,实现代码不同,应用场合也不同
- 一元运算符
Object op 或 op Object
2. 二元运算符
ObjectL op ObjectR
7.2.1 用成员函数重载运算符
- 当一元运算符的操作数,或者二元运算符的左操作数是类的对象时,定义重载算符函数为成员函数。
例:建立一个描述3维坐标的类 Tri_Coor,重载运算符 “+”、“++”、和 “=” ,实现简单的算术运算
#include<iostream>
using namespace std;
class TriCoor
{ public:
TriCoor( int mx = 0, int my = 0, int mz = 0 ) { x = mx ; y = my ; z = mz ; }
TriCoor operator + ( TriCoor t )
{ TriCoor temp ;
temp.x = x+t.x ; temp.y = y+t.y ; temp.z = z+t.z ;
return temp ;
}
TriCoor operator = ( TriCoor t ) { x = t.x ; y = t.y ; z = t.z ; return * this ; }
TriCoor operator ++ () { x ++ ; y ++ ; z ++ ; return *this ; }
void show() { cout << x << " , " << y << " , " << z << "\n"; }
void assign( int mx, int my, int mz ) { x = mx; y = my; z = mz; }
private: int x, y, z ; // 3_d coordinates
} ;
int main()
{ TriCoor a( 1, 2, 3 ), b, c ;
a.show(); b.show(); c.show();
for( int i = 0; i < 5; i ++ ) ++ b; b.show() ;
c.assign( 3, 3, 3 ) ; c = a + b + c ; c.show() ;
c = b = a ; c.show() ;
}
程序中,重载运算符“+”的成员函数参数表只有一个参数,另一个操作数由this指针隐含传送。语句:
temp.x=x+t.x;
相当于:temp.x=this->x+t.x;
*this是引起调用函数的对象,它是运算符的左操作数。例如,表达式:
a+b
激活函数的是对象a,运算符右边的对象被作为参数传递给函数。因此,该表达式解释为:a.operator+(b)
重载运算符函数像其他函数一样,可以返回其他C++合法类型。在该程序中,重载“+”运算符函数返回类类型TriCoor,重载“++”和“=”运算符函数返回类类型的引用TriCoor&。“++”和“=”的运算可以作为左值表达式,函数返回类引用既符合运算符原来的语义,又减少了函数返回时对匿名对象数据复制的开销。
重载运算符函数中的语句:
return *this;
返回调用函数的对象,复杂表达式:
a+b+c 和 c=b=a
分别被解释为:
(a+b)+ c 和 c=(b=a)
它符合系统版本运算符的操作方式,使表达式能够正确执行。
“++”是一元运算符,由调用对象的this指针隐含传递一个参数,所以参数表为空。表达式:b++
被解释为:b.opreator++()
“++” 运算符由前置和后置的方式,它们的实现代码有区别。后面讨论。
7.2.2 用友元函数重载
友元函数重载运算符常用于运算符的左右操作数类型不同的情况
例如:
class Complex
{ int Real ; int Imag ;
public :
Complex ( int a ) { Real = a ; Imag = 0 ; }
Complex ( int a , int b ) { Real = a ; Imag = b ; }
Complex operator + ( Complex ) ;
…...
} ;
int f ( )
{ Complex z ( 2 , 3 ) , k ( 3 , 4 ) ;
z = z + 27 ; //正确 被解释为:z.operator+(25)
z = 27 + z ; //错误,被解释为 25.operator+(z) 整数值25不是Complex类对象,无法驱动函数进行运算
…...
}
我们通过这个简单的例子说明了成员函数重载的“ + ”运算符不支持交换律。
例:复数运算
#include<iostream>
using namespace std;
class Complex
{ public:
Complex( double r =0, double i =0 ) { Real = r ; Image = i ; }
Complex(int a) { Real = a ; Image = 0 ; } //构造形参对象把整型常数25转换成复数对象(25,0)
void print() const ;
friend Complex operator+ ( const Complex & c1, const Complex & c2 ) ;
friend Complex operator- ( const Complex & c1, const Complex & c2 ) ;
friend Complex operator- ( const Complex & c ) ;
private:
double Real, Image ;
};
Complex operator + ( const Complex & c1, const Complex & c2 )
{ double r = c1.Real + c2.Real ; double i = c1.Image+c2.Image ;
return Complex ( r, i ) ;//构造返回对象
}
Complex operator - ( const Complex & c1, const Complex & c2 )
{ double r = c1.Real - c2.Real ; double i = c1.Image - c2.Image ;
return Complex ( r, i ) ;
}
Complex operator- ( const Complex & c )
{ return Complex ( -c.Real, - c.Image ) ; }
void Complex :: print() const //成员函数
{ cout << '(' << Real << " , " << Image << ')' << endl ; }
int main()
{ Complex c1( 2.5,3.7 ), c2( 4.2, 6.5 ) ;
Complex c ;
c = c1 - c2 ; // operator-(c1,c2)
c.print() ;
c = 25 + c2 ; // operator+(25,c2)
c.print() ;
c = c2 + 25 ; // operator+(c2,52)
c.print() ;
c = - c1 ; // operator-(c1)
c.print() ;
}
使用友元重载运算符的讨论
用友元函数重载像“++”这样的运算符时,有时会碰到问题。
例如,类 TriCoor 用成员函数重载“++”的版本是:
TriCoor TriCoor :: operator ++ ()
{ x ++ ; y ++ ; z ++ ; return *this ; } // ok , 修改了this 指针所指对象
用成员函数重载一元运算符时,所需要的唯一变元通过 this 指针传递,
对 this 所指对象数据的任何改变都会影响到激活运算符函数的对象。
- 若定义友元函数 friend operator ++( ) 版本:
TriCoor operator ++ (TriCoor opl )
{ opl . x ++ ; opl . y ++ ; opl . z ++ ; return opl ; }
问题
函数使用传值参数,对 opl 的所有修改都无法传到函数体外,不会影响被调用的对象
- 用指向激活对象的指针定义友元函数:
TriCoor operator ++ ( TriCoor * opl )
{ opl -> x ++ ; opl -> y ++ ; opl -> z ++ ; return *opl ; }
问题
C++ 不知道如何激活该函数,下述代码无法编译:
TriCoor ob ( 1 , 2 , 3 ) ;
&ob ++ ; // error 二义性对 ob 的地址进行递加? 还是将对象 ob 递加?
- 使用引用参数:
TriCoor operator ++ ( TriCoor & opl )
{ opl . x ++; opl . y ++; opl . z ++; return opl ; }
下述代码是正确的:
TriCoor ob ( 1 , 2 , 3 ) ;
ob ++; // ok,传名
如果一个运算符的操作要修改类的对象的状态,要重载为友元函数时,应该使用引用参数。
- 若一运算符的操作需要修改类对象状态时,应该用成员函数重载;需要左值操作数的运算符(如 ++,–),若重载为友元函数时要用引用参数
- C++不能用友元重载的运算符: = () [] ->
- 如果运算符的操作数(尤其是第一个操作数)希望有隐式转换,则必须用友元函数重载
7.3 几个典型运算符重载
数学类中常用的几个运算符重载的特点和应用
7.3.1 重载 ++ 与 –
例:成员函数重载++
#include<iostream>
using namespace std;
class Increase
{ public :
Increase ( ) { value=0; }
void display( ) const { cout<<value<<'\n'; } ;
Increase operator ++ ( ) ; // 前置
Increase operator ++ ( int ) ; // 后置
private: unsigned value ;
};
Increase Increase :: operator ++ ( )
{ value ++ ; return *this ; }//预定义版本
Increase Increase :: operator ++ ( int )
{ Increase temp; temp.value = value ++ ; return temp; }//预定义版本
int main( )
{ Increase a , b , n ; int i ;
for ( i = 0 ; i < 10 ; i ++ ) a = n ++ ;//重载版本
cout <<"n= " ; n.display( ) ; cout <<"a= " ; a.display( ) ;
for ( i = 0 ; i < 10 ; i ++ ) b = ++ n ;//重载版本
cout << "n= " ; n.display( ) ; cout << "b= " ; b.display( ) ;
}
例:友元函数重载++
#include<iostream>
using namespace std;
class Increase
{ public :
Increase ( ) { value=0; }
void display( ) const { cout<<value<<'\n'; } ;
friend Increase operator ++ ( Increase & ) ; // 前置
friend Increase operator ++ ( Increase &, int ) ; // 后置
private: unsigned value ;
};
Increase operator ++ ( Increase & a )//通过引用参数操作对象
{ a.value ++ ; return a ; }
Increase operator ++ ( Increase & a, int )//复制构造局部对象
{ Increase temp(a); a.value ++ ; return temp; }
int main( )
{ Increase a , b , n ; int i ;
for ( i = 0 ; i < 10 ; i ++ ) a = n ++ ;
cout <<"n= " ; n.display( ) ; cout <<"a= " ; a.display( ) ;
for ( i = 0 ; i < 10 ; i ++ ) b = ++ n ;
cout << "n= " ; n.display( ) ; cout << "b= " ; b.display( ) ;
}
7.3.2 重载赋值运算符
例:定义Name类的重载赋值函数
#include<iostream>
#include<cstring>
using namespace std;
class Name
{ public :
Name ( char *pN ) ;
Name( const Name & ) ; //复制构造函数
Name& operator=( const Name& ) ; // 重载赋值运算符
~ Name() ;
protected :
char *pName ;
int size ;
} ;
Name::Name ( char *pN )
{ cout <<" Constructing " << pN << endl ;
pName = new char[ strlen( pN ) + 1 ] ;
if( pName != 0 ) strcpy( pName,pN ) ;
size = strlen( pN ) ;
}
Name::Name( const Name & Obj ) //复制构造函数
{ cout << " Copying " << Obj.pName << " into its own block\n";
pName = new char[strlen( Obj.pName ) + 1 ] ;
if ( pName != 0 ) strcpy( pName, Obj.pName ) ;
size = Obj.size;
}
Name & Name::operator= ( const Name & Obj ) // 重载赋值运算符
{ delete []pName ;
pName = new char[ strlen( Obj.pName ) + 1 ] ;
if ( pName != 0 ) strcpy( pName , Obj.pName ) ;
size = Obj.size ;
return *this ;
}
Name::~ Name()
{ cout << " Destructing " << pName << endl ;
delete []pName ;
size = 0;
}
int main()
{ Name Obj1( "ZhangSan" ) ;
Name Obj2 = Obj1 ; // 初始化对象时调用复制构造函数
Name Obj3( "NoName" ) ;
Obj3 = Obj2 = Obj1 ; // 修改对象时调用重载赋值运算符函数
}
7.3.3 重载运算符[]和()
- 运算符 [] 和 () 是二元运算符
- [] 和 () 只能用成员函数重载,不能用友元函数重载
- 重载下标运算符 []
[] 运算符用于访问数据对象的元素
重载格式 类型 类 :: operator[] ( 类型 ) ;
- 重载函数调用符 ()
() 运算符用于函数调用
重载格式 类型 类 :: operator() ( 参数表 ) ;
7.3.4 重载流插入和流提取运算符
例:为vector类重载流插入运算符和提取运算符
#include<iostream>
#include<cstdlib>
using namespace std;
class vector
{ public :
vector( int size =1 ) ; ~vector() ;
int & operator[] ( int i ) ;
friend ostream & operator << ( ostream & output , vector & ) ;
friend istream & operator >> ( istream & input, vector & ) ;
private :
int * v ; int len ;
};
int main()
{ int k ;
cout << "Input the length of vector A :\n" ; cin >> k ;
vector A( k ) ;
cout << "Input the elements of vector A :\n" ; cin >> A ;
cout << "Output the elements of vector A :\n" ;
cout << A ;
}
vector::vector( int size )
{ if (size <= 0 || size > 100 )
{ cout << "The size of " << size << " is null !\n" ; exit( 0 ) ; }
v = new int[ size ] ; len = size ;
}
vector :: ~vector() { delete[] v ; len = 0 ; }
int & vector :: operator [] ( int i )
{ if( i >=0 && i < len ) return v[ i ] ;
cout << "The subscript " << i << " is outside !\n" ; exit( 0 ) ;
}
ostream & operator << ( ostream & output, vector & ary )
{ for(int i = 0 ; i < ary.len ; i ++ ) output << ary[ i ] << " " ;
output << endl ;
return output ;
}
istream & operator >> ( istream & input, vector & ary )
{ for( int i = 0 ; i < ary.len ; i ++ ) input >> ary[ i ] ;
return input ;
}
7.4 类类型转换
7.4.1 构造函数进行类类型转换
例:
class X
{ // ……
public :
X ( int ) ;
X ( const char * , int = 0 ) ;
};
void f ( X arg ) ;
:
//调用构造函数 X ( int ) 把 1 转换为类类型 X 后赋给对象 a也称 X ( 1 ) 为 X 类的类型常量
X a = X( 1 ) ; // a = 1
// 调用构造函数X ( const char * , int = 0 ) 把字符串转换为类类型 X 后赋给对象 b
X b = "Jessie" ; // b = X ( "Jessie" , 0 )
//隐式调用构造函数 X ( int ) 把 2 转换为类类型 X 后赋给对象
a = 2 ; // a = X ( 2 )
//隐式调用构造函数 X ( int ) 对实参作类类型转换,然后做参数结合
f ( 3 ) ; // f ( X ( 3 ) )
//当找不到匹配的构造函数时转换失败
f ( 10 , 20 ) ; // error
7.4.2 类型转换函数
- 带参数的构造函数不能把一个类类型转换成基本类型
- 类类型转换函数是一种特殊的成员函数,提供类对象之间显式类型转换的机制
例如:
class X
{ ……
public :
operator int ( ) ;
……
} ;
void f ( X a )
{ int i = int ( a ) ;
i = ( int ) a ;
i = a ;
}
使用三种类型转换规则?
No,a 是一个类对象,它们都用类型转换函数作类型转换
X :: operator int ()
除了赋值和初始化,类型转换函数还可以这样使用:
void g ( X a , X b )
{ int i = ( a ) ? 1 + a : 0 ;
int j = ( a && b ) ? a + b : i ;
if ( a ) { …… } ;
}
对象 a 、b 可用在整型变量出现的地方
例:有理数计算
#include<iostream>
using namespace std;
class Rational
{ public :
Rational() ; //构造函数
Rational(int n , int d=1) ; //构造函数
Rational(double x) ; //构造函数,double-->Rational
operator double() ; //类型转换函数,Rational-->double
friend Rational operator+(const Rational &,const Rational &);
friend ostream & operator<<(ostream &,const Rational &);
private :
int Numerator , Denominator ;
} ;
int gcd( int a, int b ); //求最大公约数
int main()
{ Rational a( 2, 4 );
Rational b = 0.3;
Rational c = a + b;
cout << double(a) << " + “ << double(b) << " = " << double(c) << endl ;
cout << a << " + " << b << " = " << c << endl;
double x = b ;
c = x + 1 + 0.6 ;
cout << x << " + " << 1 << " + " << 0.6 << " = " << double(c) << endl ;
cout<< Rational(x) << " + " << Rational(1) << " + " << Rational(0.6) << " = " << c << endl ;
}
Rational::Rational(int n , int d) //用分子、分母构造对象
{ int g;
if( d==1 ) //分母等于1
{ Numerator = n ; //分子
Denominator = d ; //分母
}
else //分母不等于1的有理数
{ g = gcd( n,d ); //求分子、分母的最大公约数
Numerator = n/g; //约分
Denominator = d/g;
};
}
Rational::Rational(double x) //用实数构造对象
{ int a, b, g;
a = int( x*1e5 ); //分子
b = int( 1e5 ); //分母
g = gcd( a,b ); //求分子、分母的最大公约数
Numerator = a/g; //约分
Denominator = b/g;
}
Rational::operator double() //类型转换函数,Rational-->double
{ return double( Numerator ) / double( Denominator );
}
//重载运算符 +
Rational operator+( const Rational & r1, const Rational & r2 )
{ int n , d ;
n = r1.Numerator * r2.Denominator
+ r1.Denominator * r2.Numerator ;
d = r1.Denominator * r2.Denominator ;
return Rational( n, d ) ;
}
//重载运算符 <<
ostream & operator<<(ostream & output, const Rational & x) { output << x.Numerator;
if( x.Denominator!=1 )
output<< "/" << x.Denominator ;
return output;
}
int gcd( int a, int b ) //求最大公约数
{ int g ;
if( b==0 ) g = a ;
else g = gcd( b, a%b ) ;
return g ;
}