2013.7.18 运算符的加载
所谓函数的重载是值完成不同功能的函数具有相同的函数名。
C++编译器根据函数的实参来确定应该调用哪一个函数的。
注意:
- 定义的重载函数必须具有不同的参数个数,或者不同的参数类型。只有这样编译系统才有可能根据不同的参数去调用不同的重载函数。
- 仅返回值不同时,不能定义为重载函数。
int sum,a=3,b=2; sum=a+b; (int)=(int)+(int) //可以 char str[4],c1[2]="a",c2[2]="b"; str=c1+c2; (char*)=(char*)+(char*)
//编译系统中的运算符“+”本身不具有这种运算。若使上式可以运算,必须重新定义“+”运算符,这种重新加载的过程成为运算符的重载。
class A
{
float x,y;
public:
A(float a=0,float b=0){x=a;y=b}
}
void main(void)
{
A a(2,3),b(3,4),c;
c=a+b;
}
//两对象不能使用+,必须重新定义。
运算符的重载从另外一个方面体现了OOP技术的多态性,且同一运算根据不同的运算对象可以完成不同的操作。
为了重载运算符必须重新定义一个函数,由这个函数完成这个重载运算符应该完成的操作。它通常是类的成员函数或者是友元函数。运算符的操作数通常也是该类的对象。
一、重载为类的成员函数
格式如下:
<类名>operator<运算符>(<参数表>)
{
函数体
}
A operator +(A &);//重载了类的‘+’运算符,其中operator和其后的运算符一起构成函数名。
class A { int i; public:A(int a=0) { i=a; } void Show(void){ cout<<"i="<<i<<endl; } void AddA(A &a, A &b) //利用函数进行类之间的运算 { i=a.i+b.i; } }; void main(void) { A a1(10),a2(20),a3; a1.Show (); a2.Show (); // a3=a1+a2; //不可直接运算 a3.AddA(a1,a2); //调用专门的功能函数 a3.Show ();
class A { int i; public:A(int a=0){ i=a; } void Show(void){ cout<<"i="<<i<<endl; } void AddA(A &a, A &b) //利用函数进行类之间的运算 { i=a.i+b.i; } A operator +(A &a) //重载运算符+ { A t; t.i=i+a.i; return t; } }; void main(void) { A a1(10),a2(20),a3; a1.Show (); a2.Show (); a3=a1+a2; //重新解释了加法,可以直接进行类的运算 相当于a3=a1.operator+(a2) a3.AddA(a1,a2); //调用专门的功能函数 a3.Show ();
重载运算符和一般函数的比较
void AddA(A &a, A &b) { i=a.i+b.i; } 函数调用: a3.AddA(a1,a2); A operator +(A &a) { A t; t.i=i+a.i; return t; } 函数调用 a3=a1+a2; a3=a1.operator+(a2)
重新定义运算符,由左操作符调用右操作符。最后将函数返回值赋给运算结果的对象。
class A { int i; public:A(int a=0){ i=a; } void Show(void){ cout<<"i="<<i<<endl; } void AddA(A &a, A &b) //利用函数进行类之间的运算 { i=a.i+b.i; } A operator +(A &a) //重载运算符+ { A t; t.i=i+a.i; return t; } }; void main(void) { A a1(10),a2(20),a3; a1.Show (); a2.Show (); a3=a1+a2; //重新解释了加法,可以直接进行类的运算 a3.AddA(a1,a2); //调用专门的功能函数 a3.Show (); }
当用成员函数实现运算符的重载时,运算符重载函数的参数只能用两种情况:没有参数或带有一个参数。对于一个操作数的运算符(如++),在重载这种运算符时,通常不能有参数;而对于有二个操作数的运算符,只能带有一个参数。着参数可以是对象,对象的引用,或其他类型的参数。在C++中不允许重载有三个操作数的运算符。
运算符的优先级和结合律是不能改变的。
单目运算符的重载
只具有一个操作数的运算符为单目运算符,最常用的为++及——。
A a; ++a; a++; A a, b; b=++a; b=a++; 可以看出,虽然运算后对象a的值一致,但先自加或后自加的重载运算符函数的返回值不一致,必须在重载时予以区分。
++为前置运算时,它的运算符重载函数的一般格式为:
<type> operator ++( )
{ ......;}
++为后置运算时,它的运算符重载函数的一般格式为:
<type> operator ++(int)
{ ......;}
A a, b; b=++a; A operator ++( ){ .... } b=a++; A operator ++(int){ .... }
class A { float x, y; public: A(float a=0, float b=0){ x=a; y=b; } A operator ++( ){A t; t.x=++ x; t.y=++y; return t;//return *this} A operator ++(int) { A t; t.x=x++; t.y=y++; return t;} }; void main(void) { A a(2,3), b; b=++a; b=a++; }
用成员函数实现运算符的重载时,运算符的左操作数为当前对象,并且要用到隐含的this指针。运算符重载函数不能定义为静态的成员函数,因为静态的成员函数中没有this指针
二、运算符重载为友元函数
函数重载为友元函数时,是由一个操作数调用另外一个操作数。即函数的实参只有一个或者没有。
c=a+b;实际上是c=a.operator+(b);
c=++a;实际上是c=a.operator++( );
友元函数就是在类外的普通函数,与一般函数的区别是可以调用类中的私有或者保护数据。
将运算符的重载函数定义为友元函数,参与运算的对象全部成为函数参数。
c=a+b;实际上是 c=operator+(a, b);
c=++a;实际上是 c=operator++(a);
对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。有些运算符不能重载为友元函数,它们是:=,(),[ ],->等
格式为:
friend <类型说明> operator<运算符>(<参数表>)
{......}
c=a+b; // c=operator+( a, b)
friend A operator + (A &a, A &b)
{.....}
++为前置运算时,它的运算符重载函数的一般格式为:
A operator ++(A &a)
{ ......;}
++为后置运算时,它的运算符重载函数的一般格式为:
A operator ++(A &a, int)
{ ......;}
class A { int i; public:public: A(int a=0) { i=a; } void Show(void) { cout<<"i="<<i<<endl; } friend A operator++(A &a){ a.i++; retrurn a;} friend A operator++(A &a, int n) { A t; t.i=a.i; a.i++; return t; } }; void main(void) { A a1(10),a2,a3; a2=++a1; //相当于a2=operator++(a1) a3=a1++; //相当于a3=operator++(a1,int) a2.Show(); a3.Show ();
对双目运算符,重载为成员函数时,仅一个参数,另一个被隐含;重载为友元函数时,有两个参数,没有隐含参数。
一般来说,单目运算符最好被重载为成员函数;对双目运算符最好被重载友元函数。
三、转换函数
转换函数就是在类中定义一个成员函数,其作用是将类转换为某种数据类型。
1. 转换函数必须是类的成员函数。
2. 转换函数的调用是隐含的,没有参数。
A :: operator float ( )
{ return x+y; }
class Complex{ float Real,Image; public: Complex(float real=0,float image=0) { Real=real; Image=image; } void Show(void) {cout<<"Real="<<Real<<'\t'<<"Image="<<Image<<endl; } operator float(); //成员函数,定义类转换 Complex->float }; Complex::operator float () { return Real*Real+Image*Image;} void main(void) { Complex c(10,20); c.Show (); cout<<c<<endl;//可以直接输出c,因为已经进行类型转换 }
注意,转换函数只能是成员函数,不能是友元函数。转换函数的操作数是对象。转换函数可以被派生类继承,也可以被说明为虚函数。
赋值运算符和运算符的重载
同类型的对象间可以相互赋值,等同于对象的各个成员的一一赋值。
A a(2,3), b;
b=a;
但当对象的成员中使用了动态的数据类型时(用new开辟空间),就不能直接相互赋值,否则在程序的执行期间会出现运行错误。
这时,利用编译系统的默认赋值无法正确运行程序,必须重载赋值运算符“=”,即重新定义“=”。
格式为:
<函数类型> <ClassName>::operator=(<参数表>)
赋值运算符必须重载为成员函数。
class A{ char *ps; public: A( ){ ps=0;} A(char *s ){ ps =new char [strlen(s)+1]; strcpy(ps,s);} ~A( ){ if (ps) delete ps;} void Show(void) { cout<<ps<<endl;} A& operator=(A &b); }; void main(void ) { A s1("China!"),s2("Computer!"); s1.Show(); s2.Show(); s2=s1; s1.Show(); s2.Show(); } A &A::operator = ( A &b)//重载赋值运算符 { if ( ps ) delete [ ] ps; if ( b.ps) { ps = new char [ strlen(b.ps)+1]; strcpy( ps, b.ps); } else ps =0; return *this; }
class A{ char *ps; public: A( ){ ps=0;} A(char *s ){ ps =new char [strlen(s)+1]; strcpy(ps,s); } ~A( ){ if (ps) delete ps;} char *GetS( ) {return ps;} A & operator = ( A &b);//重载赋值运算符 }; A &A::operator = ( A &b)//重载赋值运算符 { if ( ps ) delete [ ] ps; if ( b.ps) { ps = new char [ strlen(b.ps)+1]; strcpy( ps, b.ps); } else ps =0; return *this; } void main(void ) { A s1("China!"),s2("Computer!"); s2=s1; cout <<"s1= "<< s1.GetS()<<'\t'; cout <<"s2= "<< s2.GetS()<<'\n';
一个字符串类
在C++中,系统提供的字符串处理能力比较弱,都是通过字符处理函数来实现的,并且不能直接对字符串进行加法、减法,字符串的拼接,字符串之间的相互赋值等操作。可以通过应用C++提供的运算符重载机制,可以提供字符串的直接操作能力,使得字符串的操作与一般的数据一样方便。
class String
{ int Length;//字符串长度
char *Sp; //字符串在内存中的首地址
public:
.....
}
可见,字符串类只定义了指针,并没有开辟具体的空间以存放字符串的内容,所以,无论是构造、析构还是加减等,均需要考虑动态开辟空间的问题,这也是字符串类的难点。
class String{ int Length; //字符串的长度 char *Sp; //指向字符串的指针 public: String(){Sp=0;Length=0;} //缺省的构造函数 String( char *s) //以一个字符串常量作为参数 { Length = strlen(s); Sp=new char[Length+1]; strcpy(Sp,s); } ~String(){ if(Sp) delete [ ] Sp; } friend String operator +(String &,String &);//友元函数重载+ String & operator =(String &);//成员函数重载赋值= String (String &s); //拷贝的构造函数(必须有) }; void main(void) { String str1("China"); String str2("CCTV"); String str3; str3=str1+str2; str2=str1; cout<<str3<<endl; } String & String:: operator =(String &str) { if (Sp) delete []Sp; Length=str.Length ; Sp =new char[Length +1]; strcpy(Sp,str.Sp); return *this; } String operator +(String &str1,String &str2) { String str; str.Length=str1.Length+str2.Length; str.Sp=new char[str.Length +1]; strcpy(str.Sp,str1.Sp); strcat(str.Sp,str2.Sp); return str; }
若不定义字符串的析构函数,则可以不定义它的拷贝的构造及赋值函数,若定义了析构函数,必须重新定义这两个成员函数。
原则:每个对象都有自己的独立空间。