C++运算符重载

转载自:https://blog.csdn.net/lishuzhai/article/details/50781753

为什么要对运算符进行重载:

C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。

C++运算符重载的实质:

运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:

operator p(argument-list)//operator 后面的'p'为要重载的运算符符号。

即:

<返回类型说明符> operator <运算符符号>(<参数表>)
{
     <函数体>
}

运算符重载的规则:

(1)为了防止用户对标准类型进行运算符重载,C++规定重载后的运算符的操作对象必须至少有一个是用户定义的类型

这是什么意思呢?

比如说现在有两个数:int number1,int number2,

那么number1+number2 求的是两个数的和,

但是如果你重载以后让着两个数相加为他们的乘积,这肯定是不合乎逻辑的。

可能重载以后会有二义性,导致程序不知道该执行哪一个(是自带的的还是重载后的函数)

(2)使用运算符不能违法运算符原来的句法规则。如不能将% 重载为一个操作数,

例如:
int index;

%index;这种是不被允许的。

(3)不能修改运算符原先的优先级。

(4)不能创建一个新的运算符,例如不能定义operator** (···)来表示求幂

(5)不能进行重载的运算符:成员运算符,作用域运算符,条件运算符,sizeof运算符,typeid(一个RTTI运算符),const_cast、dynamic_cast、reinterpret_cast、static_cast强制类型转换运算符

(6)大多数运算符可以通过成员函数和非成员函数进行重载但是下面这四种运算符只能通过成函数进行重载:

= 赋值运算符,()函数调用运算符,[ ]下标运算符,->通过指针访问类成员的运算符。

(7)除了上述的规则,其实我们还应该注意在重载运算符的时候遵守一些明智的规则:例如:不要将+运算符重载为交换两个对象的值。

重载运算符的两种形式:

重载为类的成员函数||重载为类的非成员函数。

重载为类的非成员函数的时候:

通常我们都将其声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数。但是非友元又不是类的成员函数是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。

下面我统一讲解一个经典的例子来说明一下这两者的区别、特点、要重载时可能出现的问题

GO:

现在我们有一个时间类,我们要进行的任务是对时间类的加减乘。或许你会觉得很简单,简单就对了,因为这更容易让你深入的理解这二者

MyTime.h文件:
#pragma once
#ifndef MYTIME_H_
#define MYTIME_H_
class CMyTime
{
private:
    int m_hours;
    int m_minutes;
public:
    CMyTime();
    CMyTime(int h, int m = 0);
    void AddHr(int h);                         //小时更改
    void AddMin(int m);                        //分钟更改
    void Reset(int h = 0, int m = 0);          //重新设置时间
    CMyTime operator+(const CMyTime &t) const; //重载加法
    CMyTime operator-(const CMyTime &t) const; //重载减法
    CMyTime operator*(double n) const;         //重载乘法
    void Show() const;
    ~CMyTime();
};
#endif
MyTIme.cpp文件:
#include "stdafx.h"
#include "MyTime.h"
#include <iostream>
 
CMyTime::CMyTime()
{
    m_hours = 0;
    m_minutes = 0;
}
 
 
CMyTime::CMyTime(int h, int m)
{
    m_hours = h;
    m_minutes = m;
}
 
 
CMyTime::~CMyTime()
{
}

// 小时更改
void CMyTime::AddHr(int h)                                            
{
    m_hours += h;
}
 
// 分钟更改 
void CMyTime::AddMin(int m)                                             
{
    // 修正一下 原blog没有考虑进位
    // m_minutes = m;
    m_hours = (m_minutes + m) / 60;
    m_minutes = (m_minutes + m) % 60;
}
 
// 重新设置时间 
void CMyTime::Reset(int h, int m)                                       
{
    m_hours = h;
    m_minutes = m;
}
 
// 重载加法运算符函数
CMyTime CMyTime::operator+(const CMyTime &t) const              
{
    CMyTime sum;

    sum.m_minutes = t.m_minutes + m_minutes;
    sum.m_hours = t.m_hours + m_hours + sum.m_minutes / 60;
    sum.m_minutes %= 60;

    return sum;
}
 
// 重载为减法运算符函数,这里限制了t要比本身的时间小
CMyTime CMyTime::operator-(const CMyTime &t) const         
{
    CMyTime diff;

    int tot1, tot2;
    tot1 = t.m_minutes + 60 * t.m_hours;
    tot2 = m_minutes + 60 * m_hours;
    diff.m_minutes = (tot2 - tot1) % 60;
    diff.m_hours = (tot2 - tot1) / 60;

    return diff;
}
 
// 重载为乘法运算符函数
CMyTime CMyTime::operator*(double n) const                 
{
    CMyTime result;
    
    long totalMinutes = m_hours * 60 * n + m_minutes *n;
    result.m_minutes = totalMinutes % 60;
    result.m_hours = totalMinutes / 60;

    return result;
}
 
 
void CMyTime::Show() const
{
    std::cout << m_hours << " hours "
                << m_minutes << " minutes\n";
}

 

主函数:
 
// Study11-02.cpp : 定义控制台应用程序的入口点。
//
 
 
#include "stdafx.h"
#include <iostream>
#include "MyTime.h"
using namespace std;

int main(int argc, _TCHAR* argv[])
{
    CMyTime weeding(4, 35);
    CMyTime waxing(2, 47);
    CMyTime total;
    CMyTime diff;
    CMyTime adjusted;
 
    cout << "weeding Time = ";
    weeding.Show();
    cout << endl;
 
     cout << "waxing Time = ";
    waxing.Show();
    cout << endl;
 
    cout << "total work Time = ";            //(1)
    total = weeding + waxing;
    total.Show();
    cout << endl;
 
    diff = weeding - waxing;
    cout << "weeding Time - waxing Time = "; //(2)
    diff.Show();
    cout << endl;
 
 
    adjusted = total * 1.5;                  //(3)
    cout << "adjusted work Time = ";
    adjusted.Show();
    cout << endl;
 
    return 0;
}

在主函数中,我们创建了weeding 和waxing两个对象,进行了操作。那么现在问题来了。

我们看(3)标记处,现在adjusted = total * 1.5;  是可以运行的,那么adjusted = 1.5 * total可以运行吗?

答案当然是不可以,我们还原下此语句:adjusted = total.operator*(1.5),换成1.5在乘号前面当然是不可以的。因为1.5不是对象。

(在(1)处,total = weeding + waxing; 这句话其实使用了编译器默认的赋值运算符重载函数,当然你也可以自己写,特别是深拷贝的情况,见下一篇博文)

 

这个时候有两种解决方式:

一:告诉每个人只能按照adjusted = total *1.5;  这种方式来,这种方式看起来当然是欠缺的。那么重头戏来了:

二:非成员函数,声明为 CMyTime operator *(double m,const CMyTime &t);

这样等价于:A = operator *(1.5,B)等价于:A = 1.5 * B;出于性能考虑,我们将其声明为friend 即友元函数(前面已经解释过为什么要声明为友元了):

friend CMyTime operator*(double m,const CMyTime &t);

接下来我们这样做:在上面的MyTime.h文件的public中加入:friend CMyTime operator*(double m, const CMyTime &t){ return t*m; }  (因为这个函数操作很简单,可以直接为内联函数);然后就可以运行 A  = 1.5 * B语句了。

 

接下来我们为了更好的了解重载运算符,来进行<<运算符的重载:

现在我们想让 cout<<adjusted; 这句话能直接执行输出,(显然这种输出方式如果不重载<<运算符是没有办法执行的。因为cout根本不知道输出adjusted的什么东西),对于<<的重载我们有两种版本:

第一种声明为成员函数

照葫芦画瓢:CMyTime operator<<(ostream &os); 那么这种声明方式会造成什么结果呢?

答案是:输出会变成:adjusted<< cout;或许看起来很不好,但这确实是正确的。因为这等价于:adjusted.operator<<(cout);

第二种版本声明为友元函数:(也是更好的版本):

MyTIme.h 声明:

friend void operator<<(ostream &os,const CMyTime &t);    

MyTime.cpp实现:  

void operator<<(ostream &os,const CMyTime &t){

    os << t.hours << t.minutes

};

这样就可以执行cout << adjusted 这条语句了。

操作符重载为成员函数时,比重载为友元操作符重载函数少一个参数(左操作数默认为this指针),编译器优先在成员函数中寻找操作符重载函数。

 

但是这样会存在一个问题:

我们没有办法执行cout << adjusted <<waxing;这条语句。因为从左往右读(cout << adjusted)<< waxing ,返回类型是void 而我们需要的是waxing的左边是一个ostream对象。我们只需要这样修改:

friend ostream& operator<<(ostream &os,const CMyTIme &t); 实现的时候:return os;就行了。

(程序可以运行,更改也很少,可以试一试的呦);

 

那么最重要的问题来了,我们什么时候声明为成员函数,什么时候声明为非成员函数呢?

首先,我们要明白这句话:对于成员函数来说,一个操作数通过this指针隐式的传递,(即本身),另一个操作数作为函数的参数显示的传递;对于友元函数(非成员函数)两个操作数都是通过参数来传递的。

(1)一般来说,单目运算符重载为类的成员函数,双目运算符重载为类的友元函数(咳咳,一般情况下)

(2)双目运算符不能将 =  ()[] -> 重载为类的友元函数。

(3)如果运算符的第一次操作数要求为隐式转换则必须为友元函数。

(4)当最左边的要求为类对象,而右边的是一个内置类型,则要为友元函数。

 

 

最重要的注意点:

T1 = T2 + T3;

可以为T1 = T2.operator+(T3);

也可以为T1  = operator+(T2,T3);

但是这两种方式不能同时声明定义,因为这会出现二义性。造成程序不知道该执行那个函数。在进行运算符重载的时候千万要注意造成二义性的情况。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值