第十一讲:运算符重载与重载函数

第十一讲:运算符重载与重载函数

    * 掌握:算符重载的概念、方法及规则。
    * 理解:算符重载作为类成员和友元函数。 
重点、难点
    * 算符重载的概念、法方及规则。

一、 什么是运算符重载

    重载(overloading)这个名词。所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋子新的含义,使之实现新的功能。因此,同一个函数名就可以用来代表不同功能的函数,也就是一名多用。

    运算符重载:系统对可对已存在的运算符进行重载,用户在不同的场合下使用它们时,作用是不同。

例如:
    1、实际上,我们已经在不知不觉之中使用了运算符重载。例如,大家都已习惯于用加法运算符“+”对整数、单精度数和双精度数进行加法运算,如5+8,5.8+3.67等,其实计算机对整数、单精度数和双精度数的加法操作过程是很不相同的,但由于已经对运算符“+”进行了重载,所以“+”就能适用于int,float,double类型的运算。
   2、“<<”是的位运算中的位移运算符(左移),但在输出操作中又是与流对象cout配合使用的流插入运算符;“>>”也是位移运算符(右移),但在输入操作中又是与流对象cin配合使用的流提取运算符。这就是利用了运算符重载(operator overloading)。如对“<<”和“>>”的重载处理是放在头文件stream中的。因此,如果要在程序中用"<<”和“>>"作流插入运算符和流提取运算符,必须在本文件模块中包含头文件stream(当然还应当包括"using namespace std";)。

问题提出:
   
用户能否根据自己的需要对已提供的运算符进行重载赋予它们新的含义,使之一名多用。譬如,能否用“+”号进行两个复数的相加。若有cl=(3十4i),c2=(5-lOi),在数学中可以直接用“+”号实现

例1 通过函数来实现复数相加(没有用运算符重载)
#include <iostream>
using namespace std;
class Complex    //定义Complex类
   { public:
      Complex(){real=0;imag=0;} //定义构造函数
      Complex(double r,double i){real=r;imag=i;}//构造函数重载
      Complex complex_add(Complex &c2);//声明复数相加的函数
      void display(); //声明输出函数
     private:

      double real; //实部
      double imag;}; //虚部

Complex Complex::complex_add(Complex &c2) //定义复数相加的函数
     { return Complex(real+c2.real,imag+c2.imag);}
            //两个复数的实部相加, 两个复数的虚部相加 
void Complex::display()       //定义输出函数
  { cout<<"("<<real<<"+"<<imag<<"i)"<<endl;}

int main()
 { Complex c1(3,4),c2(5,-10),c3; //定义3个复数对象
   c3=c1.complex_add(c2);      //调用复数相加函数
   cout<<"c1="; c1.display();   //输出cl的值
   cout<<"c2="; c2.display();   //输出c2的值
   cout<<"c1+c2="; c3.display(); //输出c3的值
   return 0; }


运行结果如下:
    cl=(3,4i)
    c2=(5,-lOi)
    cl+c2=(8,-6)

   结果无疑是正确的,但调用方式不直观、太烦琐,使人感到很不方便。人们自然会想:能否也和整数的加法运算一样,直接用加号“+”来实现复数运算呢?如c3=cl+c2;
   编译系统就会自动完成c1和c2两个复数相加的运算。如果能做到,就为对象的运算提供了很大的方便。这就需要对运算符“+”进行重载。

二、运算符重载的方法

   运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。,运算符重载是通过定义函数实现的,运算符重载实质上是函数的重载。

重载运算符的函数一般格式如下:
      函数类型 operator 运算符名称(形参表列)
             { 对运算符的重载处理 }

例如,想将“+”用于Complex类(复数)的加法运算,函数的原型可以是这样的:
    Complex operator+(Complex &cl,Complex &c2);

说明:
   
1、注意重载函数名是由operator和运算符组成。在上面的一般格式中,operator是关键字,是专门用于定义重载运算符的函数的,运算符名称就是提供给用户的预定义运算符。
   operator+就是函数名,意思是“对运算符+重载”。只要掌握这点,就可以发现,这类函数和其他函数在形式上没有什么区别。两个形参是Complex类对象的引用,要求实参为Complex类对象。
   2、在定义了重载运算符的函数后,函数operator+重载了运算符+。在执行复数相加的表达式cl+c2时(假设cl和c2都已被定义为Complex类对象),系统就会调用operator+函数,把cl和c2作为实参,与形参进行虚实结合。

例2 改写例1,重载运算符“+”,使之能用于两个复数相加。
可写出如下程序:
#include <iostream>
using namespace std;
class Complex
   { public:
      Complex(){real=0;imag=0;}
      Complex(double r,double i){real=r;imag=i;}
      Complex operator + (Complex &c2);//声明重载运算符+的函数
      void display();
     private:
      double real;
      double imag; };
Complex Complex::operator + (Complex &c2)//定义重载运算符+的函数
     { Complex c;
       c.real=real+c2.real;
       c.imag=imag+c2.imag;
       return c; }

void Complex::display()
  { cout<<"("<<real<<"+"<<imag<<"i)"<<endl; }

int main()
 { Complex c1(3,4),c2(5,-10),c3;
   c3=c1+c2; //运算符+用于复数运算
   cout<<"c1=";c1.display();
   cout<<"c2=";c2.display();
   cout<<"c1+c2=";c3.display();
   return 0; }

运行结果与例1相同:
  cl=(3,4i)
  c2=(5,-10i)
  c1+c2=(8,-6i)

请比较例1和例2,只有两处不同:
   1、在例2中以operator+函数取代了例1中的complex_add函数,而且只是函数名不同,函数体和函数返回值的类型都是相同的。
   2、在main函数中,以“c3=cl+c2;”取代了例1中的“c3=c1.complex_add(c2);”。在将运算符+重载为类的成员函数后,编译系统将程序中的表达式cl+c2解释为:c1.operator+(c2) //其中cl和c2是complex类的对象。
   3、上例中重载的运算符定义函数可可使用以下定义:
      Complex Complex::operator+(Complex &C2)
      { return Complex(real+c2.real,imag+c2.imag);}

   可以看到:两个程序的结构和执行过程基本上是相同的,作用相同,运行结果也相同。重载运算符是由相应的函数实现的。有人可能说,既然这样,何必对运算符重载呢? 我们要从用户的角度来看问题,虽然重载运算符所实现的功能完全可以用函数实现,但是使用运算符重载能使用户程序易于编写、阅读和维护。在实际工作中,类的声明和类的使 用往往是分离的。假如在声明Complex类时,对运算符+,-,*,/都进行了重载,那么 使用这个类的用户在编程时可以完全不考虑函数是怎么实现的,放心大胆地直接使用+,-,*,/进行复数的运算即可,显然十分方便。

思考:(上例中主函数中存在下列语句)
    1、3+5=如何操作?
    2、3.5+C1=如何操作?
    3、complex(3.5)+C1=如何操作?

   通过以上的例子,可以看到重载运算符允许用户自己定义新的类型。把运算符重载和类结合起来,可以在程序中定义出很有实用意义而使用方便的新的数据类型。运算符重载使具有强大的功能、更好的可扩充性和适应性,这是C++最吸引人的特点之一。

三、重载运算符的规则

(1)不允许用户自己定义新的运算符,只能对已有的运算符进行重载。

(2)允许重载的运算符
中绝大部分的运算符允许重载。具体规定见下表。

表4.1 允许重载的运算符
双目算术运算符: +(加),—(减),,(乘),/(除),%(取模)
关系运算符: ==(等于),!=(不等于),<(小于),>(大于),<=(小于等于),>=(大于等于)
逻辑运算符: ||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符: +(正),-(负),*(指针),&(取地址)
自增自减运算符: ++(自增),--(自减)
位运算符: |(按位或),&(按位与),~(按位取反),^(按位异或),<<(左移),>>(右移)
赋值运算符: =,+=,-=,*=,/=,%=,&=,|=,^=,<<=;>>=
空间申请与释放: new,delete,new[],delete[]
其他运算符: ()(函数调用),->(成员访问),->-*(成员指针访问),,(逗号),[](下标)

不能重载的运算符只有5个:
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)

   前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。

   (3)重载不能改变运算符运算对象(即操作数)的个数。
   (4)重载不能改变运算符的优先级别。
   (5)重载不能改变运算符的结合性。
   (6)重载运算符的函数不能有默认的参数,否则就改变了运算符参数个数,与前面第3点矛盾。
   (7)重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。
   (8)用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和“&”不必用户重载。

①赋值运算符(=)可以用于每一个类对象,可以利用它在同类对象之间相互赋值。
②地址运算符&也不必重载,它能返回类对象在内存中的起始地址。

   (9)从理论上说,可以将一个运算符重载为执行任意的操作,如可以将加法运算符重载为输出对象中的信息,将“,”运算符重载为“小于”运算。
   (10)运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数

四、运算符重载函数作为类成员函数和友元函数

   在本章例2程序中对运算符“+”进行了重载,使之能用于两个复数的相加。在该例中运算符重载函数operator+作为Complex类中的成员函数。

   cl+c2,编译系统把它解释为: c1.operator+(c2)

    例3 将运算符“+”重载为适用于复数加法,重载函数不作为成员函数,而放在类外,作为Complex类的友元函数。

#include <iostream>
using namespace std;
class Complex
   { public:
      Complex(){real=0;imag=0;}
      Complex(double r){real=r;imag=0;}
      Complex(double r,double i){real=r;imag=i;}
      friend Complex operator+ (Complex &c1,Complex &c2);//重载函数作为友元函数
      void display();
     private:
      double real;
      double imag; };

Complex operator+ (Complex &c1,Complex &c2) //定义作为友元函数的重载函数
     { return Complex(c1.real+c2.real, c1.imag+c2.imag);}

void Complex::display()
  { cout<<"("<<real<<"+"<<imag<<"i)"<<endl;}

int main()
 { Complex c1(3,4),c2(5,-10),c3;
   c3=c1+c2;
   cout<<"c1="; c1.display();
   cout<<"c2="; c2.display();
   cout<<"c1+c2="; c3.display();
   return 0; }

说明:

   1、将运算符函数不作为成员函数,而把它放在类外,在Complex类中声明它为友元函数。同时将运算符函数改为有两个参数。在将运算符“+”重载为非成员函数后,编译系统将程序中的表达式cl+c2解释为
operator+(cl,c2) 即执行cl+c2相当于调用以下函数:
Complex operator+(Complex&cl,Complex&c2)
{returllComplex(c1.real+c2.real,c1.1mag+c2.imag);}

    2、有的会提这样的问题:为什么把运算符函数作为友元函数呢?理由很简单,因为运算符函数要访问Complex类对象中的成员。如果运算符函数不是Complex类的友元函数,而是一个普通的函数,它是没有权利访问Complex类的私有成员的。

   3、运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数。现在分别讨论这3种情况。

    ① 只有在极少的情况下才使用既不是类的成员函数也不是友元函数的普通函数,原因是上面提到的,普通函数不能直接访问类的私有成员。当然如果一定要访问这些成员,也不是绝对没有办法,可以在类中定义公用的设置数据的set函数和读取数据的get函数,然后在重载函数中调用这些函数去访问类的私有成员。但这样做兜了一个圈子,编程不便,运行时调用函数的开销会降低效率。
    ② 如果将运算符重载函数作为成员函数,它可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数的参数。但必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,而且与运算符函数的类型相同。因为必须通过类的对象去调用该类的成员函数,而且只有运算符重载函数返回值与该对象同类型, 运算结果才有意义。
    如想将一个复数和一个整数相加,如c1+i,可以将运算符重载函数作为成员函数,如下面的形式:
    Complex Complex::operator+(int &i) //运算符重载函数作为Complex类的成员函数
    {return Complex(real+I,imag);}
注意在表达式中重载的运算符“+”左侧应为Complex类的对象,如:
c3=c2+i; 不能写成: c3=i+c2;//运算符“+”的左侧不是类对象,编译出错
    ③如果运算符左侧的操作数属于标准类型(如int)或是一个其他类(如在本例中是非Complex类)的对象,则运算符重载函数不能作为成员函数,只能作为非成员函数。如果函数需要访问类的私有成员,则必须声明为该类的友元函数。

规定:有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流插入“<<”和流提取运算符“>>”、类型转换运算符)。
    由于友元的使用会破坏类的封装,因此从原则上说,要尽量将运算符函数作为成员函数。但考虑到各方面的因素,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。在学习了本章第7节例9的讨论后,读者对此会有更深入的认识。

说明:有的编译系统(如Visual6.0)没有完全实现标准,它所提供不带后缀.h的头文件不支持把成员函数重载为友元函数。上面例4.3程序在GCC中能正常运行,而在Visual 6.O中会编译出错。但是Visual 所提供的老形式的带后缀.h的头文件可以支持此项功能,因此可以将程序头两行修改如下,即可顺利运行:
#include<iostream.h> 以后如遇到类似情况,亦可照此办理。

 

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值