运算符重载

1、运算符重载的规则
运算符重载 是指同一个运算符可以施加于不同类型的操作数上面。 
重载运算符的函数的一般格式如下:

 函数类型 operator运算符名称(形参列表)

 {  对运算符的重载处理 }

函数名是由operator和运算符组成,operator+意思是对运算符+重载

重载运算符的函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也非类的友元函数的普通函数

对“+”运算符进行重载来实现两个Money类对象的加法运算。

#include <iostream>
using namespace std;
#include <string>
class Money
{public:
   Money(int y = 0, int j = 0, int f = 0);
   Money operator+(Money&); 
   void Display(string);
 private:
   int yuan, jiao,fen;
   void Optimize( );}; 
 void Money::Optimize( )
 {   if ( fen >= 10 ){   jiao++; fen -=10;  }
     if ( jiao >= 10 ){   yuan++; jiao -=10;  }}
  Money:: Money(int y, int j, int f)
 {   yuan = y; jiao = j; fen = f; Optimize( );  }
Money Money::operator+(Money &c2) 
{   return Money ( yuan + c2.yuan, jiao + c2.jiao, fen + c2.fen );  }
void Money::Display(string str)
{   cout << str << " = " << yuan << "." << jiao << fen << "¥" << endl;  }
int main( )
{   Money cost1(300, 5, 6), cost2(105, 7, 6), total1, total2;
     total1 = cost1 + cost2
     total2 = cost1.operator+(cost2);  
     total1.Display("total1 = cost1 + cost2");
     total2.Display("total2 = cost1 + cost2");
     return 0;} 

在执行本句时,系统自动调用operator+函数,把cost1作为当前对象,把cost2作为实参,与形参进行虚实结合。  cost1+cost2解释为:cost1.operator+(cost2)

1 C++ 不允许用户自己定义新的运算符 ,只能对 C++ 语言中已有的运算符进行重载。例如,虽然在某些程序设计语言中用双字符 ** 作为求幂运算符,但是在使用 C++ 进行程序设计时,不能将 ** 作为运算符进行重载,因为 ** 不是 C++ 语言的合法运算符。
2 )运算符重载针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲, 重载的功能应当与原有功能相类似
3C++允许重载的运算符包括C++中几乎所有的运算符,不代表全部,有的不允许重载

C++允许重载的运算符

运算符名称

具体运算符

算术运算符

+(加),-(减),*(乘),/(除),%(取模),++(自增),--(自减)

位操作运算符

&(按位与),~(按位取反),^(按位异或),|(按位或),<<(左移),>>(右移)

逻辑运算符

!(逻辑非),&&(逻辑与),||(逻辑或)

比较运算符

<(小于),>(大于),>=(大于等于),<=(小于等于),==(等于),!=(不等于)

赋值运算符

=+=-=,*=/=%=&=|=^=,<<=,>>=

其他运算符

[](下标),()(函数调用),->(成员访问),,(逗号),newdeletenew[]delete[]->*(成员指针访问)

C++不允许重载的运算符

运算符

功能

.

成员访问运算符

.*

成员指针访问运算符

::

域运算符

sizeof

长度运算符

?:

条件运算符


4 坚持 4 不能改变 。即 不能改变 运算符操作数的个数 不能改变 运算符原有的优先级 不能改变 运算符原有的结合性 不能改变 运算符原有的语法结构
单目运算符重载后只能是单目运算符,双目运算符重载后依然是双目运算符。
C++ 语言已经预先规定了每个运算符的优先级,以决定运算次序。不论怎么进行重载,各运算符之间的优先级别不会改变。
C++ 语言已经预先规定了每个运算符的结合性。
5 )重载的运算符必须和用户定义的自定义类型对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。也就是说 ,参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质。
6 )重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数。
7 )用于类对象的运算符一般必须重载,但有两个例外,运算符 = & 可以不必用户重载。

 ① 赋值运算符(=)可以用于每一个类对象,可以利用它在同类对象之间相互赋值。

 ② 地址运算符&也不必重载,它能返回类对象在内存中的起始地址。

2、 运算符重载函数作为类的成员函数及友元函数
2.1 作为类的成员函数
将运算符重载函数定义为类的成员函数的原型在类的内部声明格式如下:

class 类名{

    

   返回类型 operator运算符(形参表);

    

};

在类外定义运算符重载函数的格式如下:

返回类型 类名::operator运算符(形参表)

{

    函数体

}

通过运算符重载为类的成员函数来实现两个有理数对象的加、减、乘和除运算。

#include <math.h>
#include <iostream>
using namespace std;
class Rational            //声明有理数类
{public:
   Rational(int x=0,int y=1); //构造函数
   void Print();
   Rational operator+(Rational a);//重载运算符"+"
   Rational operator-(Rational a);//重载运算符"-"
private:
   int num,den;
   void Optimi(); }; //优化有理数函数
void Rational::Optimi()  //定义有理数优化函数
{  int gcd;
   if(num==0) //若分子为0,则置分母为1后返回
   {   den=1; return;  }
   gcd=(abs(num)>abs(den)?abs(num):abs(den));
   if(gcd==0) return;      //若为0,则返回
   for(int i=gcd;i>1;i--)  //用循环找最大公约数
   if((num%i==0)&&(den%i==0))   break;
   num/=i;//i为最大公约数,将分子、分母均整除它,重新赋值
   den/=i;
   //若分子和分母均为负数,则结果为正,所以均改为正
   if(num<0&&den<0) { num= -num; den=-den;  }
  else if(num<0||den<0)
  {//若分子和分母中只有一个为负数,则调整为分子取负,分母取正
   num=-abs(num);  den=abs(den);}
}
void Rational::Print()   //输出有理数
{  cout<<num;
   //当分子不为0且分母不为1时才显示"/分母“
   if(num!=0&&den!=1) cout<<"/"<<den<<"\n";
   else cout<<"\n";
}
Rational Rational::operator+(Rational a)
{//“+”运算符重载函数,根据前面所列的算法写出表达式
      Rational r;
      r.den=a.den*den;
      r.num=a.num*den+a.den*num;
      r.Optimi();
      return r;
}
Rational Rational::operator-(Rational a)
{//“-”运算符重载函数,根据前面所列的算法写出表达式
      Rational r;
      r.den=a.den*den;
      r.num=num*a.den-den*a.num;
      r.Optimi();
      return r;
}
int main()
{   Rational r1(3,14),r2(4,14),r3,r4;
    r1.Print();
    r2.Print();
    r3=r1+r2; //使用重载了的运算符“+”
    r3.Print();
    r4=r1-r2; //使用重载了的运算符“-”
    r4.Print();
    return 0;
}

2.2 作为类的友元函数
将运算符重载函数定义为类的友元函数,其原型在类的内部声明格式如下:

class 类名{

   

   friend返回类型operator运算符(形参表);

   

};

在类外定义友元运算符重载函数的格式如下:

返回类型 operator运算符(形参表)

{

    函数体

}

将运算符+-重载为适合于有理数加减法,重载函数不作为成员函数,而放在类外,作为rational类的友元函数。

<pre name="code" class="cpp">#include<iostream>
using namespace std;
 class Point
{public:
   Point();
  Point(int vx,int vy);
  Point & operator++(); //重载前置自增为类的成员函数
   Point  operator++(int);//重载后置自增为类的成员函数
   //重载前置自减为类的友元函数
   friend  Point& operator--( Point &p1);
   //重载后置自减为类的友元函数
   friend Point operator--( Point &p1,int); 
    void Display();
private:
   int x,y;
};
Point::Point(){  x=0;   y=0;  }
Point::Point(int vx,int vy){   x=vx;   y=vy; }
void Point::Display()
{ cout<<" ("<<x<<","<<y<<") "<<endl;   }
Point & Point::operator++() //前置自增
{  if(x<640)  x++;  //不超过屏幕的横界
   if(y<480)  y++;  //不超过屏幕的竖界
   return *this;
}
Point Point::operator++(int) //后置自增
{//先将当前对象通过复制构造函数临时保存起来
   Point temp(*this); 
   if(x<640)  x++;  //不超过屏幕的横界
   if(y<480)  y++;  //不超过屏幕的竖界
   return temp;
}
Point & operator--( Point &p) //前置自减
{  if(p.x>0)  p.x--;
   if(p.y>0)  p.y--;
    return p;
}
Point operator--( Point &p,int) //后置自减
{ //先将当前对象通过复制构造函数临时保存起来
   Point temp(p);
   if(p.x>0)  p.x--;
   if(p.y>0)  p.y--;
   return temp;
}
int main()
{  Point p1(10,10), p2(150,150), p3(20,20), 
         p4(160,160), p5;
   cout<<"p1=";
   p1.Display();
   ++p1; //测试前置自增
   cout<<"++p1=";
   p1.Display();
   cout<<"p3=";
   p3.Display();
   p5=p3++;//测试后置自增
   cout<<" p3++=";
   p3.Display();
   cout<<"p5=p3++=";
   p5.Display();
   cout<<"p2=";
  p2.Display();
  --p2; //测试前置自减
  cout<<"--p2=";
  p2.Display();
  cout<<"p4=";
  p4.Display();
  p5=p4--; //测试后置自增
  cout<<" p4--=";
  p4.Display();
  cout<<" p5= p4--=";
   p5.Display();
  return 0;   }


 
  
运行结果如下:
p1= (10,10)++p1= (11,11)p3= (20,20) p3++= (21,21)p5=p3++= (20,20)p2= (150,150)--p2= (149,149)p4= (160,160) p4--= (159,159) p5= p4--= (160,160)从上述过程可以看出,如果只想执行递增运算,使用前置方式效率更高。
3.2  赋值运算符“ =” 的重载
对任一类 X ,如果没有用户自定义的赋值运算符函数,那么系统自动地为其生成一个缺省的赋值运算符重载函数,定义为类 X 中的成员到成员的赋值,例如:

X&X::operator=(constX& s)

{  //成员间赋值

}

obj1 obj2 是类 X 的两个对象, obj2 已被创建,则编译程序遇到如下语句:

obj1=obj2;

就调用缺省的赋值运算符重载函数,将对象 obj2 的数据成员的值逐个赋给对象 obj1 的对应数据成员中。
通常,缺省的赋值运算符重载函数是能够胜任工作的。但是, 如果类的数据成员中包含指向动态分配的内存的
成员时,系统提供的缺省赋值运算符重载函数会出现危险,造成指针悬挂。 下面的例子重载 String 的赋值运算符,解决赋值操作引起的指针悬挂问题。

重载赋值运算符函数解决指针悬挂问题。

#include <iostream>
using namespace std;
#include <string>
#include <cassert>
class String   //自定义字符串类
{public:
   String( ); //默认构造函数
   String(const char *src); //带参数的构造函数
   ~String( );  //析构函数
   const char* ToString( ) const {  return str;  } 
   unsigned int Length( ) const {  return len;  } 
   String &operator=(const String &right);
   //赋值运算符重载函数
private:
   char *str; 
   unsigned int len;   //存放字符串的长度
};
String:: String( ) //默认构造函数
{   len = 0;
    str = new char[len+1]; 
    str[0] = '\0';
}
String:: String(const char *src) //带参数的构造函数
{   len = strlen(src); 
    str = new char[len+1];
    if (!str ) {   
       cerr << "Allocation Error!\n"; 
       exit(1);   }
    strcpy(str, src);
}
String:: ~String( )  //析构函数
{   delete str; 
    str = NULL;  
}
String &String:: operator=(const String &right)
    //赋值运算符重载函数
{    if ( &right != this ) {
       int length = right.Length( );
       if ( len < length ) {
	        delete[] str;
          str = new char[length+1];
          assert(str != 0);
          }
       int i;
       for (i = 0; right.str[i] != '\0'; i++ )  
           str[i] = right.str[i];
       str[i] = '\0';
       len = length;
	     }
     return *this;
}
int main( ) 
{ String str1("Hi!"), str2("Hello!");
  cout << "str1: " << str1.ToString( ) << endl;
  cout << "str2: " << str2.ToString( ) << endl;
  str1 = str2;
  cout << "str1: " << str1.ToString( ) << endl;
  return 0;
}


3.3  流插入运算符“ <<” 和流提取运算符“ >>” 的重载

在类库提供的头文件中已经对 << >> 进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入 C++ 标准类型的数据。
用户自己定义的类型的数据,是不能直接用 << >> 来输出和输入的。如果想用它们输出和输入自己定义的类型的数据,就必须对它们进行重载。

对“<<”和“>>”重载的函数形式如下:

ostream& operateor<< (ostream&,自定义类);

istream& operateor>> (istream&,自定义类);


在重载时要注意下面两点:

1 )要对 << >> 运算符进行重载, 必须重载为类的友元函数
2 )重载的 友元函数的返回类型 应是 ostream 对象或 istream 对象的 引用 ,即 ostream & istream &

定义一个 Timer 类,用来存放做某件事所花费的时间,如 3 小时 15 分钟,分别重载运算符“ +” 用于求两段时间的和,重载运算符“ -” 用于求两段时间的差,重载流插入运算符“ <<” 和流提取运算符“ >>” ,用“ cout<<” 输出 Timer 类的对象,用“ cin >>” 输入 Timer 类的对象。


#include<iostream>
using namespace std;
class Timer
{ public:
    Timer( );
    Timer(int h, int m = 0);
    friend Timer operator+( Timer &t1, Timer &t2);
    friend Timer operator-(Timer &t1, Timer &t2);
    friend ostream& operator<<(ostream &out,
    Timer &t);
    friend istream& operator>>(istream&in, 
    Timer &t);
private:
    int hours;
    int minutes;
};
Timer::Timer( )
 {  hours = minutes = 0;  }
Timer::Timer(int h, int m)
 {  hours = h; minutes = m;  }
Timer operator+( Timer &t1, Timer &t2)
 {  Timer sum;
    sum.minutes = t1.minutes + t2. minutes;
    sum.hours = t1.hours + t2.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;}
Timer operator-(Timer &t1, Timer &t2)
 {  Timer dif;
    int x1, x2;
    x1 = t2.hours*60 + t2.minutes;
    x2 = t1.hours*60 + t1.minutes;
    dif.minutes = (x2 - x1) % 60;
    dif.hours = (x2 - x1) / 60;
    return dif;
}
ostream& operator<<(ostream &out, Timer &t)
{  out << t.hours << "hours," << t.minutes << "minutes";
   return out;
}
istream &operator>>(istream &in, Timer &t)
{  cout << "Input hours and minutes:";
   in >> t.hours >> t.minutes;
   return in;
}
int main( )
{   Timer t1, t2, t3, t4;
    cin >> t1 >> t2;
    cout << "t1 = " << t1 << "\n";
    cout << "t2 = " << t2 << "\n";
    t3 = t1 + t2; 
    cout << "t3 = t1 + t2 = " << t3 << "\n";
    t4 = t1 - t2;
    cout << "t4 = t1 - t2 = " << t4 << "\n";
    return 0;}

程序运行结果如下:

Input hours and minutes:5 35

Input hours and minutes:6 22

t1= 5 hours, 35 minutes

t2= 6 hours, 22 minutes

t3= t1 + t2 = 11 hours, 57 minutes

t4= t1 - t2 = 0 hours, -47 minutes

4、转换构造函数

C++提供显式类型转换,程序员在程序中将一种类型的数据转换为另一种指定类型的数据,其形式为:

       类型名(表达式)

如:    int(89.5)

其作用是将89.5转换为整型数89


对于用户自己声明的类型,编译系统并不知道怎样进行转换。

解决这个问题的关键是让编译系统知道怎样去进行这些转换,需要定义专门的函数来处理。

通常 把只有一个形参的构造函数 ,称为转换构造函数。 编译器可以利用它执行自动类型转换, 将一个其他类型的数据 转换成 一个 转换构造函数所在类 的对象

使用转换构造函数将一个指定类型的数据转换为类对象的方法如下:


(1)先声明一个类。

(2)在这个类中定义一个只有一个参数的构造函数,参数的类型是需要转换的类型,在函数体中指定转换的方法。

(3)在该类的作用域内可以用以下形式进行类型转换:

        类名(指定类型的数据)

就可以将指定类型的数据转换为此类的对象。

基于有理数类的包含转换构造函数和运算符重载的程序

#include <iostream.h>
#include <math.h>
class Rational            //声明有理数类
{ public:
   Rational();            //无参构造函数
   Rational(int x,int y); //有两个形参的构造函数
   Rational(int x);       //转换构造函数
   void Print();
//重载函数作为友元函数
friend Rational operator+(Rational a, Rational b); 
private:
    int num,den;
    void Optimi(); //有理数优化函数
};
//定义无参构造函数
Rational::Rational()
{  num=0;den=1;  }

//定义有两个形参的构造函数
Rational::Rational(int x,int y) 
{  num=x;den=y; }

//定义转换构造函数
Rational::Rational(int x)
{  num=x;den=1;  }
void Rational::Print()   //输出有理数
{  cout<<num;
   //当分子不为0且分母不为1时才显示"/分母
   if(num!=0&&den!=1) cout<<"/"<<den<<"\n";
   else cout<<"\n"; }
//定义作为友元函数的重载函数
Rational operator+(Rational a, Rational b)
{  Rational r;
   r.den=a.den*b.den;
   r.num=a.num*b.den+a.den*b.num;
   r.Optimi();
   return r;}
void rational::Optimi()  //定义有理数优化函数
{ … } 
int main()
{   Rational r1(3,5),r2,r3; 
    int n=3;
    r2=r1+n; 
    r2.Print();
    r3=n+r1; 
    r3.Print();
    return 0;
}

为了用构造函数完成类型转换,类内至少定义一个只带一个参数 ( 或其他参数都带有默认值 ) 的构造函数。当需要类型转换时,系统自动调用该构造函数,创建该类的一个临时对象,该对象由被转换的值初始化,从而实现了类型转换。
转换构造函数不仅可以将一个标准类型数据转换成类对象,也可以将另一个类的对象转换成转换构造函数所在的类的对象。

若有教师类Teacher,学生类Student ,可以定义下面的转换构造函数将Student 类转换为Teacher 类。

  Teacher(Student &s)

  { num=s.num; strcpy(name,s.name); sex=s.sex; }


5、类型转换效果
通过转换构造函数可以进行类型转换 ,但是它的转换功能受到限制。由于无法为基本类型定义构造函数,因此,不能利用构造函数把自定义类型的数据转换成基本类型的数据, 它只能从基本类型 ( int float ) 向自定义的类型转换 类型转换函数 则可以用来把源类类型转换成另一种目的类型。
类型转换函数的作用将一个类的对象转换成另一类型的数据
在类中, 类型转换函数 定义的一般格式为:

class类名{

        …

       operator <目的类型名>()

      {

         <函数体>

       }

}

关于类型转换函数,有以下几点注意事项:

1 类型转换函数只能定义为一个类的成员函数 而不能定义为类的友元函数或普通函数,因为转换的主体是本类的对象。
2 )类型转换函数与普通的成员函数一样,也可以在类定义体中声明函数原型,而将函数体定义在类外部。

关于类型转换函数,有以下几点注意事项:

3 )类型转换函数既没有参数,也不显式给出返回值类型。
4 )类型转换函数中必须有
    return 目的类型的数据 ; 的语句,
即必须送回目的类型数据作为函数的返回值。

operator double( )

{   return real; }

5 一个类可以定义多个类型转换函数 C++ 编译器将根据操作数的类型自动地选择一个合适的类型转换函数与之匹配。在可能出现二义性的情况下,应显式地使用类型转换函数进行类型转换
6 )通常把 类型转换函数 也称为类型转换运算符函数 ,由于它也是重载函数,因此 也称为类型转换运算符重载函数 ( 或称强制类型转换运算符重载函数 )
class rational{ //声明有理数类
public:
   rational();              //无参构造函数
   rational(int x,int y);   //有两个形参的构造函数
   rational(int x);        //转换构造函数
   operator int(){  return num;  } //类型转换函数
   void print();
//重载函数作为友元函数
friend rational operator+(rational a, rational b); private:
    int num,den;
    void optimi();
};
int main()
{  rational r1(3,5),r2,r3; 
   int n=3;
   r2=r1+n; 
   r2.print();
   r3=n+r1; 
   r3.print();
   return 0;}

转换构造函数和类型转换运算符函数有一个共同的功能:

当需要的时候,编译系统会自动调用这些函数,建立一个无名的临时对象(或临时变量)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值