C++学习之:运算符重载

1.什么叫运算符重载

 运算符重载就是写一个函数解释某某个运算符在某个类中的含义,这个函数可以是所重载这个类的成员函数或全局函数。使得编译器在遇见这个运算符时能自动找到重载的这个函数。函数名应体现出与某个被重载的运算符的联系。
  C++不是所有的运算符都能重载。
C++规定重载函数名为:

operator@

其中@为要重载的运算符。如要重载“+”运算符,该重载函数名为operator+。要重载运算符“=”,函数名为operator=。
注意一下几点:
1.运算符的重载不能改变运算符的运算对象数。重载函数的形式参数个数(包括成员函数的隐式指针this)与运算符的运算对象数相同 。
2.运算符重载可以重载成成员函数也可以重载成全局函数实现。重载成全局函数时,最好把此函数设为友员函数。
3.如果重载函数作为类的成员函数,它的形式参数个数比运算符的运算对象数少1。这是因为成员函数有一个隐含的参数this。在C++中,把隐含参数this作为运算符的第一个参数。当把一个一元运算符重载成成员函数时,该函数没有形式参数,运算对象是当前调用函数的对象,即this指针指向的对象。把一个二元运算符重载成成员函数时,该函数只有一个形式参数,左运算数是当前对象,右运算符是形式参数。
4.当把一个一元运算符重载成成员函数时,该函数没有形式参数。
5.把一个二元运算符重载成成员函数时,该函数只有一个形式参数,就是右操作数,当前对象是左操作数,右运算数是形式参数。

实例:为Rational类增加+”和“*”以及“==”比较的重载函数。

//定义类
class Rational {
private:
    int num;
    int den;
    void ReductFraction();
public:
    Rational(int n = 0, int d = 1) { num = n; den = d;}
     Rational operator+(const Rational &r1) const; 
     Rational operator*(const Rational &r1) const; 
    bool operator==(const Rational &r1) const; 
    void display() {  cout << num << '/' << den;  }
} 
//重载+运算符
Rational Rational::operator+(const Rational &r1) const
{    Rational tmp;
      tmp.num = num * r1.den + r1.num * den;
      tmp.den = den * r1.den;
      tmp.ReductFraction();
      return tmp;
}
//重载*运算符
Rational Rational::operator*(const Rational &r1) const
{    Rational tmp;
      tmp.num = num * r1.num;
      tmp.den = den * r1.den;
      tmp.ReductFraction();
      return tmp;
} 
//重载==运算符
bool Rational::operator==(const Rational &r1) const
{    return num == r1.num && den == r1.den;}

===========重载成全局函数====

class Rational {
     friend Rational operator+(const Rational &r1, const Rational &r2); 
     friend Rational operator*(const Rational &r1 , const Rational &r2); 
     friend bool operator==(const Rational &r1 , const Rational &r2); 
  private:  
    int num;
    int den;
    void ReductFraction();
  public:
    Rational(int n = 0, int d = 1) { num = n; den = d;}
    void display() { cout  <<  num  <<  '/‘  <<  den;}
};
//函数实现
Rational operator+(const Rational &r1, const Rational &r2)
{  Rational tmp;
   tmp.num = r1.num * r2.den + r2.num * r1.den;
   tmp.den = r1.den * r2.den;
   tmp.ReductFraction();   return tmp;
}

Rational operator*(const Rational &r1, const Rational &r2)
{  Rational tmp;
   tmp.num = r1.num * r2.num;
   tmp.den = r1.den * r2.den;
   tmp.ReductFraction();   return tmp;  
} 
...

由于重载函数主要是对对象的数据成员进行操作,而在一般的类定义中,数据成员都被定义为私有的。所以当运算符被重载成全局函数时,通常把此全局函数(重载函数)声明为类的友元函数,便于访问类的私有数据成员。
注意:
1.大多数运算符都可以重载成成员函数或全局函数。
2.赋值(=)、下标([])函数调用(())和成员访问(->)必须重载成成员函数
3.具有赋值意义的运算符,如复合的赋值运算符以及++和–,不一定非要定义为成员函数,但最好定义为成员函数。
4.具有两个运算对象的运算符最好重载为全局函数,这样可以使得应用更加灵活。如果把加运算定义成全局函数,r是有理数类的对象,则2+r是一个合法的表达式。

1.赋值运算符的重载

对任一类,如果用户没有自定义赋值运算符函数,那么系统为其生成一个缺省的赋值运算符函数,在对应的数据成员间赋值。一般情况下,这个缺省的赋值运算符重载函数能满足用户的需求。但是,当类含有类型为指针的数据成员时,可能会带来一些麻烦。 比如内存泄漏,无法正常析构等。
比如对DoubleArray类对象执行array1 = array2的问题:

会引起内存泄漏
使这两个数组的元素存放于同一块空间中
当这两个对象析构时,先析构的对象会释放存储数组元素的空间。而当后一个对象析构时,无法释放存放数组元素的空间

赋值运算符“=”的原型
(赋值运算符只能重载成成员函数):

X  &X::operator=(const X &source)
     {
       // 赋值过程
     }

 一旦创建了对象x1,  x2, 可以用 x1 = x2赋值。

DoubleArray类的赋值运算符重载函数

DoubleArray &DoubleArray::operator=
                           (const DoubleArray &right)
{   if (this == &right) return *this; //防止自己拷贝自己
    delete [ ] storage;               //先归还空间
    low = right.low;
    high = right.high;
    storage = new double[high - low + 1]; //根据数组大小重新申请空间
    for (int i=0; i <= high - low; ++i) 
          storage[i] = right.storage[i]; //复制数组元素    
    return *this;                       //注意返回的是this指针中存放的数据
} 

2.下标运算符重载

可以通过重载下标运算符([ ])来实现,像普通的数组那样通过下标运算操作DoubleArray类的对象,这样可以使DoubleArray类更像一个功能内置的数组。
下标运算符是二元运算符,第一个运算数是数组名即当前对象,第二个运算数是下标值。运算结果是对应数组元素的引用。
c++规定下标运算符必须重载成成员函数
实例:

//DoubleArray类的[ ]重载
double & DoubleArray::operator[](int index)
{   if (index < low || index > high)  {
         cout << "下标越界"; 
         exit(-1);
     }
     return storage[index - low];
}
// DoubleArray类的使用
//定义:
DoubleArray array(20, 30);
//数组输入:
for ( i=20;  i<=30;  ++i ) {
 cout << "请输入第" << i << "个元素:";
 cin >> array[i];
  }
//数组输出:
for ( i=20;  i<=30; ++i)
cout << array[i] << '\t';

3.函数调用运算符

函数调用运算符()是一个二元运算符。它的第一个运算对象是函数名,第二个参数是形式参数表。运算的结果是函数的返回值。
一个类重载了函数调用运算符,就可以把这个类的对象当做函数来使用。
c++规定函数调用运算符必须重载成成员函数。
函数调用运算符重载函数的原型为

函数的返回值   operator() (形式参数表);

4.++和–运算符的重载

++、- -:是一元操作符,这两个操作符可以是前缀,也可以是后缀。而且前缀和后缀的含义是有区别的。所以,必须有两个重载函数。

前缀:一元操作符。
后缀:二元操作符。
成员函数重载
++ob重载为:ob.operator++( )
ob– 重载为:ob.operator–( int )
友元函数重载
++ob重载为:operator++( X &ob )
ob–重载为:operator–( X &ob, int )
调用时,参数int一般传递给值0。

实例:设计一个会报警的计数器类。该计数器从0开始计数,当到达预先设定好的报警值时,计数器会发出报警消息,计数器的值不再增加。

//类的定义
class Counter {
    int value;           //计数器的值
    int alarm;           //报警值
public:
    Counter(int a) { value = 0; alarm = a;}
    Counter & operator++( ); //前缀的++重载
    Counter operator++( int ); //后缀的++重载
    void print() { cout << value << endl; }
}; 

//类的实现

Counter & Counter::operator++() //函数返回值是引用
{   if (value == alarm) cout << "已超过报警值\n";
     else  ++value;

     return *this;
}

Counter Counter::operator++(int x)//函数返回值是值类型
{   Counter tmp = *this;  
     if (value == alarm) cout << "已超过报警值\n";
     else  ++value;

     return tmp;  
} 


//类的使用
int main()
{ Counter cnt(3); //定义一个Counter类的对象,报警值为3
   cnt.print();        //显示对象的当前值,此时输出为0
   ++cnt;
  cnt.print();         // 输出为1
  (++cnt).print(); // 输出2
  (cnt++).print(); //  输出的是2
  cnt.print();        //输出值为3
  return 0;
}

5.输入输出运算符重载

借助于流插入运算符(>>)和流提取运算符(<<)输入和输出用户自定义类的对象。
输入输出运算符必须被重载成全局函数
<<是二元运算符。如
cout << x
两个运算符是cout和x,返回的是第一个对象的引用,由于第一个参数是输入输出流对象而不是本类的一个对象,只能重载为友元函数

ostream & operator<<(ostream &  os,
                                  const ClassType &obj)
{ os << 要输出的内容;
  return os;
} 

6.自定义类型转换函数

内置类型之所以能互相转换是因为系统预先制定了转换的规则,并写好了完成转换的程序。类类型与其它类类型或内置类型之间如何转换,编译器预先无法知道。类的设计者必须定义转换的方法。

参数设计

对于任何函数的参数,如果仅需要从参数中读,而不改变它,一般用const引用来传递。
只有会修改左值参数的运算符,如赋值运算符,左值参数不是常量,所以用地址传递

返回值的类型设计

运算符的结果产生一个新值,就需要产生一个作为返回值的新对象
对于逻辑运算符,人们希望至少得到一个int或bool的返回值
所有的赋值运算符(如,=,+=等)均改变左值,应该能够返回一个刚刚改变了的左值的非常量引用

猜你喜欢

转载自blog.csdn.net/wu_qz/article/details/80494494