[C++] 运算符重载

1.为何需要重载运算符

自定义的类,不支持常用的操作符号,如果需要使用一些操作符,则该类必须对相应的操作符进行重载。

如自定义类Person:

#include 
class Person
{
    private:
        int m_age;
        std::string m_name;
    public:
        Person(int age, std::string name);
};

Person p(12,"zhangsan");

那么当要通过cout输出p时,可以这样做:

std::cout << p << std::endl;

然而,编译器发现p不支持<<运算符,从而报错:

no match foroperator<<(operand types are ‘std::ostream {aka std::basic_ostream<char>}and ‘Person’)

如果需要实现该功能,则需要Person类对<<进行重载。

运算符重载也是多态的一种形式,如*可以表示指针,可以表示解除引用操作符,可以表示乘法运算,c++编译器将根据使用场景来决定采用哪种操作。

2.重载运算符形式

可以有两种方式实现运算符重载:

  • 1.成员函数重载;
  • 2.非成员函数——友元函数重载

3.重载规则

运算符重载格式如下:

operatorop(argument-list);

operator关键字表示重载运算符,op表示要重载的运算符;

argument-list表示参数;

在运算符表示法中,左侧的对象表示调用对象,因此将作为隐式参数传入operatorop()函数中,右侧的对象则显式地作为形参被传入。

4.重载限制

  • 1.重载运算符至少有一个操作数是定义类型;

此限制针对友元函数的重载,如果是成员函数重载,则操作符左边对象被作为调用对象传入。

  • 2.重载运算符时不能违背运算符原来的含义;
  • 3.不能创建新的运算符;
  • 4.不能重载如下运算符:
sizeof 运算符
. 成员运算符
.* 成员指针运算符
:: 作用域解析符
?:条件运算符
typeid  RTTI运算符
const_cast 强制类型转换运算符
dynamic_cast  强制类型转换运算符
reinterpret_cast 强制类型转换运算符
static_cast 强制类型转换运算符
  • 5.如下运算符不能使用成员函数重载,只能使用友元函数来重载:
= 赋值运算符(重载解决浅拷贝问题)
() 函数调用运算符
[] 下标运算符
-> 指针访问类运算符

5.成员函数重载示例

5.1.重载+运算符

//Time.h
#ifndef _TIME_H_
#define _TIME_H_

class Time 
{
        private:
                int hour;
                int minute;
        public:
                Time();
                ~Time();
                Time(int h,int m); 
                Time addTime(const Time &t1) const;
                Time operator+(const Time &t) const;
                int getHour() const;
                int getMinute() const;
      
};
#endif
//Time.cpp
#include 
#include "Time.h"

Time::Time(){
        hour = 0;
        minute = 0;
}   
    
Time::~Time(){
        //blank;
}   
Time::Time(int h,int m) {
        hour = h;
        minute = m;
}   

//之所以返回Time对象,是为了支持链式操作
Time Time::addTime(const Time & t) const {
        Time temp;
        temp.minute = (minute + t.minute) % 60;
        temp.hour = hour + t.hour + (minute + t.minute) / 60;
        return temp;
}

//重载+运算符
Time Time::operator+(const Time &t) const {
        Time temp;
        temp.minute = (minute + t.minute) % 60;
        temp.hour = hour + t.hour + (minute + t.minute) / 60;
        return temp;
}
        
int Time::getHour() const {
        return hour; 
}       
        
int Time::getMinute() const {
        return minute;
}
//main文件中
#include 
#include "Time.h"

int main()
{
        using namespace std;
    
        Time t1;//call Time()
        Time t2(2,25);//call Time(int,int)
        Time t3(4,45);
    
        cout << "t1:" << t1.getHour() << "h" << t1.getMinute() << "m" << endl;
        cout << "t2:" << t2.getHour() << "h" << t2.getMinute() << "m" << endl;
        cout << "t3:" << t3.getHour() << "h" << t3.getMinute() << "m" << endl;
     
        Time t4 = t2.addTime(t3);//call addTime(Time)
        cout << "t4:" << t4.getHour() << "h" << t4.getMinute() << "m" << endl;
     
        Time t5 = t4 + t2;//call operator+(Time)
        cout << "t5:" << t5.getHour() << "h" << t5.getMinute() << "m" << endl;
        return 0;
}
/*
运行结果:
@ubuntu:~/workspace/C++/chapter11$ g++ Time.h Time.cpp client.cpp -o time
@ubuntu:~/workspace/C++/chapter11$ ./time 
t1:0h0m
t2:2h25m
t3:4h45m
t4:7h10m
t5:9h35m
*/

在重载+运算符时,有一个细节:返回值为Time,这主要是由于两个因素:

  • 1.可以进行链式操作;
  • 2.为何是Time而非Time &n呢?

在重载操作符时,大部分情况下返回一个引用比返回一个对象更合适,但是这里之所以返回Time对象,是因为该对象是在方法内部实例化的,也就是说该对象的作用域为当前方法,当方法执行完毕,对象就被析构了,如果返回一个引用,那么这个引用将会指向一个不存在的对象。而返回对象则不同,编译器会在删除该对象之前调用其拷贝构造函数得到一个新的匿名对象,并将该匿名对象赋给接受它的对象。

5.2.重载*运算符

//重载*运算符
Time Time::operator*(int i) const {
        Time temp;
        temp.hour = this->hour * i +  (this->minute * i) / 60; 
        temp.minute = (this->minute * i) % 60;
        return temp;
}

以上两个示例是通过成员函数对运算符进行了重载,那么可以通过成员函数对<<运算符进行重载呢?如通过重载<<运算符,就可以直接使用如下代码输出了:

std::cout << t << std::endl;

如果使用成员函数进行重载,根据运算符表示法,运算符左边为调用对象,右边为参数,而左边的对象是std::cout,所以,这种方案是不可行的。除非使用如下方式调用:

t << std::cout;

但这种方式却不符合c++的编程习惯。

对于二元运算符的重载,使用友元函数就可实现。下面就看看看,如何通过友元函数对运算符进行重载。

6.使用友元函数的重载

6.1.什么是友元函数?

c++中对类的访问通过访问修饰符进行控制,类对象不能能访问private修饰的变量和函数。有时候这种限制过于严格,因此,c++提供了另一种形式的访问权限——友元。

通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。可以将友元理解为“类的好朋友”。简单来说,一个类的友元可以访问其私有属性和方法。

6.2.创建友元函数

  • Step1.创建友元函数时,首先在类中定义友元函数的原型,定义时必须加关键字friend:
friend void func();
  • Step2.在类的外部实现函数定义,在实现函数定义时,由于不是成员函数,因此不需要使用作用域解析符,同时不需要friend:
void func()
{
    ......
}

6.3.友元函数使用场景

当需要为类重载二元运算符时,就需要使用友元函数来实现。

6.4.友元函数重载示例

还是以前面的Time类为例,来看看如何使用友元函数进行重载。

6.4.1.重载+运算符

//Time2.h
class Time
{
        private:
                int hour;
                int minute;
        public:
                Time();
                Time(int hour,int minute);
                int getHour();
                int getMinute();
        public:
                friend Time operator+(Time &t1,Time &t2);//使用友元函数重载+
};
//Time2.cpp
#include "Time2.h"

Time::Time(){
        hour = 0;
        minute = 0;
}

Time::Time(int hour,int minute) {
        this->hour = hour;
        this->minute = minute;
}

int Time::getHour(){
        return hour;
}

int Time::getMinute() {
        return minute;
}
//友元函数不属于类,故不需要Time::限定
Time operator+(Time &t1,Time &t2) {
        Time temp;
        temp.hour = t1.hour + t2.hour + (t1.minute+ t2.minute) / 60; 
        temp.minute = (t1.minute + t2.minute) % 60; 
        return temp;
}
#include 
#include "Time2.h"
int main()
{
        using namespace std;
    
        Time t1;//call Time()
        Time t2(2,25);//call Time(int,int)
        Time t3(4,45);
    
        cout << "t1:" << t1.getHour() << "h" << t1.getMinute() << "m" << endl;
        cout << "t2:" << t2.getHour() << "h" << t2.getMinute() << "m" << endl;
        cout << "t3:" << t3.getHour() << "h" << t3.getMinute() << "m" << endl;
    
        Time t4 = t2 + t3;//call operator+() 
        cout << "t4:" << t4.getHour() << "h" << t4.getMinute() << "m" << endl;
            
        return 0;
}

6.4.2.重载*运算符

//Time2.h
class Time
{
        //.....
        public:
                friend Time operator*(Time &t1, int i);
};
//Time2.cpp
Time operator*(Time &t,int i) {
        Time time;
        time.hour = t.hour * i + (t.minute * i) / 60;
        time.minute = (t.minute * i) % 60;
        return time;
}

6.4.3.重载<<运算符

//Time2.h
#include 
class Time
{
    //......
    public:
        friend std::ostream & operator<<(std::ostream &os,const Time & t);
};
//Time2.cpp
std::ostream & operator<<(std::ostream &os,const Time &t) {
    os << t.hour << "h" << t.minute << "m" << std::endl;
    return os;
}
//main()函数文件中:
int main()
{
    Time t(5,28);
    std::cout << t << endl;
    return 0;
}

返回ostream&是为了支持链式调用。

7.成员函数重载和友元函数重载区别

通过在Time类中对同一个操作符使用两种方式重载后,可以发现这两种方式的特点:

  • 1.成员函数适用于重载一元操作符,友元函数适用于重载二元操作符;
  • 2.对于成员函数来说,一个操作数通过this指针隐式传递,另一个操作书则作为函数参数显式地传递,对于友缘函数来说,两个操作数都作为参数来传递。

不管使用何种方式,只能选择一种,如果对同一个操作符分别使用了两种方式都进行了重载,则将出现二义性错误。

你可能感兴趣的:(C++)