C++ 运算符重载

来源:互联网 发布:杭州软件测试培训班 编辑:程序博客网 时间:2024/06/05 22:27

C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性。

运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}

(1) 除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载。

(2) 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。

(3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。

(4) 用户自定义类的运算符一般都必须重载后方可使用,但两个例外,运算符“=”和“&”不必用户重载.

 

哪些运算符可以用作重载?几乎所有的运算符都可用作重载。具体包含:
算术运算符:=,-,*,/,%,++,--
位操作运算符:—,|,~,^,<<,>>;
逻辑运算符:!&&,||;
比较运算符:>,<,>=,<=,==,!=
赋值运算符:=,+=,- = ,*=,%=,\=^=,<<=,>>=;
其他运算符:[ ],()->, ',new,delete,new[],delete[],->* .

 

运算符有两种重载形式:成员函数形式和友元函数形式。这两种形式都可访问类中的私有成员。

实例:自定义一个int包裹类,包装int,并且支持+运算符和=运算符

 

 1.用成员函数来重载运算符

复制代码
class Integer {public:    Integer();    Integer(int value);    Integer operator+(int value);    void operator=(int value);private:    int m_value;};
复制代码

 

复制代码
//integer.cpp#include "integer.h"Integer::Integer() {    m_value = 0;}Integer::Integer(int value) {    m_value = value;}Integer Integer::operator+(int value) {    int tmpValue = m_value + value;    return Integer(tmpValue);}void Integer::operator=(int value) {     m_value = value;}
复制代码



使用示例:

 

Integer integer = Integer(10); Integer tmpInteger = 100; //重载=运算符tmpInteger = integer + 1; //重载+运算符

 

2.使用友元函数来重载运算符

  使用成员函数重载运算符,我们做到了实现 integer + 1,可是如果是 1 + integer呢,这个时候我们必须使用友元函数重载+运算符:

 

复制代码
//integer.hclass Integer {public:    Integer();    Integer(int value);    Integer operator+(int value);    void operator=(int value);    operator int() const;private:    int m_value;    friend Integer operator+(int value, Integer integer);   };//Integer operator +(Integer integer, int value);  //不能声明该函数,否则会和成员函数冲突 Integer operator+(int value, Integer integer);
复制代码

 

 

复制代码
//integer.cpp#include "integer.h"Integer::Integer() {    m_value = 0;}Integer::Integer(int value) {    m_value = value;}Integer Integer::operator+(int value) {    int tmpValue = m_value + value;    return Integer(tmpValue);}void Integer::operator=(int value) {     m_value = value;}Integer::operator int() const {    return m_value;}Integer operator+(int value, Integer integer) {    int tmpValue = integer.m_value + value;    return Integer(tmpValue);}
复制代码

使用示例:

 

Integer integer = Integer(10);  Integer tmpInteger = 100;   //重载=运算符tmpInteger = integer + 1;   //重载Integer成员函数+运算符tmpInteger = 1 + tmpInteger;//重载友元函数+运算符

 

转换运算符重载:

   从上述代码我们看到Integer integer = 1;(隐式调用构造函数),确实很方便,但是如果要实现 int i = Integer(100),实现自定义类型到内部类型的转换或者已有的类型,就需要转换运算符了。

   转换运算符的的形式如下:

   X::operator T()

   其中T是类型。

   代码示例如下:

   

复制代码
//integer.hclass Integer {public:    Integer();    Integer(int value);    Integer operator+(int value);    void operator=(int value);    operator int() const; //int 转换运算符 private:    int m_value;    friend Integer operator+(int value, Integer integer);   };//Integer operator +(Integer integer, int value);  //不能声明该函数,否则会和成员函数冲突 Integer operator+(int value, Integer integer);
复制代码

 

复制代码
//integer.cpp#include "integer.h"#include <stdio.h>#include <stdlib.h>Integer::Integer() {    m_value = 0;}Integer::Integer(int value) {    m_value = value;}Integer Integer::operator+(int value) {    int tmpValue = m_value + value;    return Integer(tmpValue);}void Integer::operator=(int value) {     m_value = value;}Integer::operator int() const {    return m_value;}Integer operator+(int value, Integer integer) {    int tmpValue = integer.m_value + value;    return Integer(tmpValue);}
复制代码

使用示例:

 

Integer integer = Integer(10);  Integer tmpInteger = 100;   //重载=运算符tmpInteger = integer + 1;   //重载Integer成员函数+运算符tmpInteger = 1 + tmpInteger;//重载友元函数+运算符int num = tmpInteger;       //重载int 转换运算符

 

 重载++运算符:

  前缀各后缀运算两种,为了区分这两种运算,将后缀运算符视为双目运算符,示例代码如下:

 

复制代码
//integer.hclass Integer {public:    Integer();    Integer(int value);    Integer operator+(int value);    void operator=(int value);    operator int() const; //int 转换运算符     Integer operator++(); //重载 ++Integer     Integer operator++(int value);//重载 Integer++ private:    int m_value;    friend Integer operator+(int value, Integer integer);   };//Integer operator +(Integer integer, int value);  //不能声明该函数,否则会和成员函数冲突 Integer operator+(int value, Integer integer);
复制代码

 

 

复制代码
//integer.cpp#include "integer.h"Integer::Integer() {    m_value = 0;}Integer::Integer(int value) {    m_value = value;}Integer Integer::operator+(int value) {    int tmpValue = m_value + value;    return Integer(tmpValue);}void Integer::operator=(int value) {     m_value = value;}Integer::operator int() const {    return m_value;}Integer Integer::operator++() {    Integer tmpInteger;    tmpInteger = ++m_value;    return tmpInteger;}Integer Integer::operator++(int value) {    Integer tmpInteger;    tmpInteger = m_value++;    return tmpInteger;}    Integer operator+(int value, Integer integer) {    int tmpValue = integer.m_value + value;    return Integer(tmpValue);}
复制代码

使用示例:

 

复制代码
    Integer integer = Integer(10);      Integer tmpInteger = 100;   //重载=运算符    tmpInteger = integer + 1;   //重载Integer成员函数+运算符    tmpInteger = 1 + tmpInteger;//重载友元函数+运算符    int num = tmpInteger;       //重载int 转换运算符    tmpInteger = integer++;     //重载 Integer++     tmpInteger = ++integer;     //重载 ++Integer
复制代码

 

两种重载形式的比较:
在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:
(1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
(2) 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。
(3) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。
(4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。
(5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。
(6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。
(7) 当需要重载运算符具有可交换性时,选择重载为友元函数。



可以看到,重载函数operator+访问了两个对象中的成员,一个是this指针指向的对象中的成员,一个是形参对象中的成员。如this->real+c2.real,this->real就是c1.real。上节中已说明,在将运算符函数重载为成员函数后,如果出现含该运算符的表达式,如c1+c2,编译系统把它解释为
   c1.operator+(c2)
即通过对象c1调用运算符重载函数,并以表达式中第二个参数(运算符右侧的类对象c2)作为函数实参。运算符重载函数的返回值是Complex类型,返回值是复数c1和c2之和(Complex(c1.real + c2.real,c1.imag+c2.imag))。

运算符重载函数除了可以作为类的成员函数外,还可以是非成员函数。可以将例10.2改写为例10.3。
例10.3 将运算符“+”重载为适用于复数加法,重载函数不作为成员函数,而放在类外,作为Complex类的友元函数。
#include <iostream>
using namespace std;
class Complex
{
   public:
   Complex( ){real=0;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( );
}

与例10.2相比较,只作了一处改动,将运算符函数不作为成员函数,而把它放在类外,在Complex类中声明它为友元函数。同时将运算符函数改为有两个参数。在将运算符“+”重载为非成员函数后,C++编译系统将程序中的表达式c1+c2解释为
   operator+(c1,c2)
即执行c1+c2相当于调用以下函数:
Complex operator + (Complex &c1,Complex &c2)
{return Complex(c1.real+c2.real, c1.imag+c2.imag);}
求出两个复数之和。运行结果同例10.2。

为什么把运算符函数作为友元函数呢?因为运算符函数要访问Complex类对象中的成员。如果运算符函数不是Complex类的友元函数,而是一个普通的函数,它是没有权利访问Complex类的私有成员的。

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

首先,只有在极少的情况下才使用既不是类的成员函数也不是友元函数的普通函数,原因是上面提到的,普通函数不能直接访问类的私有成员。

在剩下的两种方式中,什么时候应该用成员函数方式,什么时候应该用友元函数方式?二者有何区别呢?如果将运算符重载函数作为成员函数,它可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数的参数。但必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,而且与运算符函数的类型相同。因为必须通过类的对象去调用该类的成员函数,而且只有运算符重载函数返回值与该对象同类型,运算结果才有意义。在例10.2中,表达式c1+c2中第一个参数c1是Complex类对象,运算符函数返回值的类型也是Complex,这是正确的。如果c1不是Complex类,它就无法通过隐式this指针访问Complex类的成员了。如果函数返回值不是Complex类复数,显然这种运算是没有实际意义的。

如想将一个复数和一个整数相加,如c1+i,可以将运算符重载函数作为成员函数,如下面的形式:
Complex Complex∷operator+(int &i)  //运算符重载函数作为Complex类的成员函数
{return Complex(real+i,imag);}
注意在表达式中重载的运算符“+”左侧应为Complex类的对象,如
   c3=c2+i;
不能写成
   c3=i+c2;  //运算符“+”的左侧不是类对象,编译出错
如果出于某种考虑,要求在使用重载运算符时运算符左侧的操作数是整型量(如表达式i+c2,运算符左侧的操作数i是整数),这时是无法利用前面定义的重载运算符的,因为无法调用i.operator+函数。可想而知,如果运算符左侧的操作数属于C++标准类型(如int)或是一个其他类的对象,则运算符重载函数不能作为成员函数,只能作为非成员函数。如果函数需要访问类的私有成员,则必须声明为友元函数。可以在Complex类中声明:
   friend Complex operator+(int &i,Complex &c); //第一个参数可以不是类对象
在类外定义友元函数:
Complex operator+(int &i, Complex &c) //运算符重载函数不是成员函数
{return Complex(i+c.real,c.imag);}
将双目运算符重载为友元函数时,在函数的形参表列中必须有两个参数,不能省略,形参的顺序任意,不要求第一个参数必须为类对象。但在使用运算符的表达式中,要求运算符左侧的操作数与函数第一个参数对应,运算符右侧的操作数与函数的第二个参数对应。如
   c3=i+c2;  //正确,类型匹配
   c3=c2+i;  //错误,类型不匹配

请注意,数学上的交换律在此不适用。如果希望适用交换律,则应再重载一次运算符“+”。如
Complex operator+(Complex &c, int &i) //此时第一个参数为类对象
{return Complex(i+c.real,c.imag);}
这样,使用表达式i+c2和c2+i都合法,编译系统会根据表达式的形式选择调用与之匹配的运算符重载函数。可以将以上两个运算符重载函数都作为友元函数,也可以将一个运算符重载函数(运算符左侧为对象名的) 作为成员函数,另一个(运算符左侧不是对象名的)作为友元函数。但不可能将两个都作为成员函数,原因是显然的。

C++规定,有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流插入“<<”和流提取运算符“>>”、类型转换运算符)。

由于友元的使用会破坏类的封装,因此从原则上说,要尽量将运算符函数作为成员函数。但考虑到各方面的因素,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。在学习了本章第10.7节例10.9的讨论后,读者对此会有更深入的认识。

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

双目运算符(或称二元运算符)是C++中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧,如3+5,a=b,i<10等。在重载双目运算符时,不言而喻在函数中应该有两个参数。



/////////////////////////           前方高能   


[cpp] view plain copy
  1. 方法一  
[cpp] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. template <class T>  
  4. class Test  
  5. {  
  6. private:  
  7.    T num;  
  8. public:  
  9.  Test( T n)  
  10.  {  
  11.   num=n;  
  12.  }  
  13. friend ostream& operator<< <>(ostream &out,const Test<T> &);  
  14. };  
  15. template <class T>  
  16. ostream& operator<< <>(ostream &out,const Test<T> &obj)  
  17. {  
  18.  out<<obj.num;  
  19.  return out;  
  20. };  
  21. void main()  
  22. {  
  23.  Test<int> t(414);  
  24.  cout<<t;  
  25. }  


方法二

[cpp] view plain copy
  1. // 第二种格式  
  2.   
  3.   
  4. #include <iostream>  
  5.   
  6. using namespace std;  
  7.   
  8. template<class T>  
  9.   
  10. class Test  
  11.   
  12. {  
  13.   
  14. public:  
  15.   
  16.     template<class S>                   // 注意<class S>S  
  17.     friend ostream& operator << (ostream& out, const Test<S>& obj);  
  18.   
  19.   
  20.     Test(T n = 0) :num(n) {}  
  21.   
  22.   
  23.   
  24.     Test(const Test<T>& copy)  
  25.     {  
  26.         num = copy.num;  
  27.     }  
  28.   
  29.   
  30.   
  31. private:  
  32.   
  33.     T num;  
  34.   
  35. };  
  36.   
  37.   
  38. // 注意<class T>和<class S>都行  
  39.   
  40. template<class T>  
  41. ostream& operator << (ostream& out, const Test<T>& obj)  
  42. {  
  43.     out << obj.num << endl;  
  44.   
  45.     return out;  
  46. }  
  47.   
  48.   
  49. int main(){  
  50.     Test<int> t(2);  
  51.   
  52.     cout << t;  
  53.   
  54.     return 0;  
  55. }  



正确的写法就是代码段中的写法。<>也必不可少,其实<>有两

重意思:


一是,表明此友元函数是函数模板;


二是,此模板使用的模板类型参数为当前

模板类的类型参数class。


关于friend关键字:
·       虚函数必须是成员函数。如果f必须是虚函数,就让它成为c的成员函数。
       operator>>;和operator<<;决不能是成员函数。如果f是operator>>;或operator<<;,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
  ·只有非成员函数对最左边的参数进行类型转换。如果f需要对最左边的参数进行类型转换,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
  ·其它情况下都声明为成员函数。如果以上情况都不是,让f成为c的成员函数。
    然后在类体外对友元函数进行定义,定义的格式和普通函数相同,但可以通过对象作为参数直接访问对象的私有成员。
     1)必须在类的说明中说明友元函数,说明时以关键字friend开头,后跟友元函数的函数原型,友元函数的说明可以出现在类的任何地方,包括在private和public部分;
  2)注意友元函数不是类的成员函数,所以友元函数的实现和普通函数一样,在实现时不用"::"指示属于哪个类,只有成员函数才使用"::"作用域符号;
  3)友元函数不能直接访问类的成员,只能访问对象成员,
  4)友元函数可以访问对象的私有成员,但普通函数不行;
  5)调用友元函数时,在实际参数中需要指出要访问的对象,
  6)类与类之间的友元关系不能继承。

0 0
原创粉丝点击