C++ 中的运算符重载及其使用

在之前的文章中,我们简单介绍过运算符重载。运算符重载其实也算是一种函数重载,只是该函数可以用下边的两种形式实现调用:

operator x(argument);
... x ...;

上边的形式中,x 表示运算符。其中第二种形式严格来说不太对,应该你不知道运算符的类型,因此只表示一种形式。

运算符重载

定义

datatype operator x(argument)
{
    statement;
}

规则

  • 不能定义新的运算符
  • 只能对已有的 C++ 运算符进行重载
  • C++ 允许能够重载的运算符
+-*/%^&
|~!=<>+=
-=*=/=%=^=&=|=
<<>>>>=<<===!=<=
>=&&||++--,->*
->()[]newdeletenew []delete []
  • C++ 不允许重载的运算符
..*::?:sizeof
  •  只能重载为成员函数的运算符
=[]()->->*
  • 重载不能改变运算符操作数的个数
  • 重载不能改变运算符的优先级
  • 重载不能改变运算符的结合性
  • 重载不能含有默认参数
  • 重载的参数至少有一个是自定义类或枚举类型
  • 不需要重载的运算符(=,&)

自定义类中的 = 默认调用 = 重载,因为会为类生成默认的 = 重载,因此无需重载,当然如果有必要的话也可以自定义 = 重载。

而 & 返回的一直是类对象的地址,也不必重载。

  • 重载不应失去符号原有的意义

实例

成员 or 友元

#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    const POINT operator+(const POINT &obj)
    {
        cout<<"POINT & operator+(const POINT &obj)"<<endl;
        return POINT(x + obj.x,y + obj.y);
    }
    friend const POINT operator+(const POINT &obj1,const POINT & obj2);
private:
    int x;
    int y;
};

const POINT operator+(const POINT &obj1,const POINT & obj2)
{
    cout<<"friend POINT & operator+(const POINT &obj1,const POINT & obj2)"<<endl;
    return POINT(obj1.x + obj2.x, obj1.y + obj2.y);
}

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);

    p1.display();
    p2.display();

    POINT p3 = p1+p2;
    POINT p4 = operator+(p1,p2);

    p1.display();
    p2.display();
    p3.display();
    p4.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 30,The y is 40
POINT & operator+(const POINT &obj)
friend POINT & operator+(const POINT &obj1,const POINT & obj2)
The x is 10,The y is 20
The x is 30,The y is 40
The x is 40,The y is 60
The x is 40,The y is 60

上边的结果中:

  • 同时存在成员函数和友元函数的形式,两者都是要实现 POINT 元素的相加,但是调用时有所差别:
    • 成员函数是作为类对象的方法使用的,因此 p1 + p2 的形式更接近于 p1.operator(p2) 的形式
    • 而 operator=(p1,p2) 的形式不是通过对象调用的,会直接调用全局函数中的 + 重载
  • 成员函数需要返回 const 类型,因为返回值还要进行 = 重载,而 = 重载的形参是 const 的引用,非 const 不能传给 const 的引用,因此返回值需要加 const
  • 因为成员函数和友元函数都是采用了临时对象作为返回值,因此只能返回对象,而不能返回对象的引用。因为引用作为别名,是不能够在对象被析构后返回的。
  • 友元函数类外定义,不需要加 friend 关键字

双目运算符重载

// global style
datatype operator x(arg1,arg2);
// class inner style
datatype operator x(arg2);

全局条件下,因为不存在调用该函数的对象,而双目运算符需要两个参数,因此需要两个参数 arg1 和 arg2,同样全局函数定义时只能通过友元实现。

类内定义时,重载的中 this 指针默认为调用该函数的对象,因此只需要一个参数 arg2。

#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    const POINT & operator+=(const POINT &obj)
    {
        x = x + obj.x;
        y = y + obj.y;

        return *this;
    }
private:
    int x;
    int y;
};

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);

    p1.display();
    p2.display();

    p1 += p2;
    p1.display();
    p2.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 30,The y is 40
The x is 40,The y is 60
The x is 30,The y is 40

 此时 += 重载可以返回引用,因为是已经存在的对象。

此时尽量使用引用,因为返回引用和返回对象的实质不太一样,一个是返回了已创建对象的别名,一个是返回临时对象,再次拷贝构造的结果。

单目运算符重载

// global style
datatype operator x(arg);
// class inner style
datatype operator x();

与双目运算符重载处的解释差不多。

#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    const POINT operator-() const
    {
        return POINT(-x, -y);
    }

private:
    int x;
    int y;
};

int main()
{
    POINT p1(10,20);
    p1.display();

    p1 = -p1;
    p1.display();

    p1 = -(-p1);
    p1.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is -10,The y is -20
The x is -10,The y is -20

上边的程序实现了 -(负号) 的重载,重载函数中含有两个 const:

const POINT operator-() const;

前一个 const 修饰返回值,表明返回的是 const POINT,后一个 const 修饰函数,const 类对象只能访问 const 方法。前一个 const 不能使下边的赋值形式成立:

-p1 = P2;

后一个 const 使返回的 const 对象能够继续调用该方法,并使下边的形式成立:

p1 = -(-p1);
#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    POINT & operator++()
    {
        x++;
        y++;
        return *this;
    }

private:
    int x;
    int y;
};

int main()
{
    POINT p1(10,20);
    p1.display();

    ++p1;
    p1.display();

    ++++p1;
    p1.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 11,The y is 21
The x is 13,The y is 23

上边的程序实现了 ++(前) 的重载,返回非 const 引用使下边的形式能够实现:

++++p1;

因为返回的是自己对象的引用,并且该引用没有用 const 修饰,因此可以对结果再次运算,并能够保证 p1 也被更改。

#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    const POINT operator++(int)
    {
        POINT tmp(*this);
        x++;
        y++;
        return tmp;
    }

private:
    int x;
    int y;
};

int main()
{
    POINT p1(10,20);
    p1.display();

    p1++;
    p1.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 11,The y is 21

上边的程序实现了 ++(后) 的重载,函数形参中使用了 int,但是实际却并没有实参传递,这种使用方式叫做哑元,是 C++ 内部用来区分 i++ 和 ++i 的方式。

返回值为 POINT 类型是因为返回的是函数栈中创建的临时对象,因此不能采用引用的形式,也是为了实现 p1++ 的先执行再 ++ 的过程。

而返回值用 const 修饰则是为了屏蔽掉下边的形式:

p1++++;

因为返回的是临时对象,因此对临时对象再执行后 ++ 操作是没有意义的。

#include <iostream>

using std::cout;
using std::endl;
using std::ostream;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    friend ostream & operator<<(ostream &out, const POINT &obj);
private:
    int x;
    int y;
};

ostream & operator<<(ostream &out, const POINT &obj)
{
    out<<"The x is "<<obj.x<<","<<"The y is "<<obj.y<<endl;
    return out;
}

int main()
{
    POINT p1(10,20);
    p1.display();

    cout<<p1<<endl;
    return 0;
}

 结果为:

The x is 10,The y is 20
The x is 10,The y is 20

上边的程序中实现了流输出运算符的重载,因为日常书写的流输出的形式总是:

cout<<var<<endl;

因此,该运算符只能重载为该类的友元,因为成员函数的第一个参数默认的是该类对象的指针,同样为了实现串联输出,需要返回引用。

同样,从该运算符的重载过程中也可以得知:

  • 如果运算符是多目(多为双目)的话,运算符的左右操作数不一定是相同类型的对象,这就涉及该重载函数的定位问题,将之定义为谁的成员,谁的友元
  • 通常情况下,由于存在 this 指针的问题,运算符被声明为哪个类的成员,通常取决于该函数的调用对象(一般为左操作数)
  • 通常情况下,运算符被声明为哪个类的友元,通常取决于该函数的参数类型(一般为右操作数)

流输入运算符的重载也可以仿照此逻辑书写。

自定义类型转换

能够实现从源类型到目标类型的转化。

转换构造函数

定义

class targetclass
{
    targetclass(const sourceclass &sourceclassvar)
    {
        statement;
    }
}

性质

  • 转换构造函数从本质上来说仍旧是构造函数
  • 且只含有一个参数
  • 如果同时含有多个参数,只能成为构造函数,而不是转换函数
#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

private:
    int x;
    int y;
    friend class POINT3;
};

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}

    POINT3(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
        this->z = 0;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
    }

private:
    int x;
    int y;
    int z;
};

int main()
{
    POINT p1(10,20);
    p1.display();

    POINT3 p2(p1);
    p2.display();

    POINT3 p3 = p1;
    p3.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 10,The y is 20,The z is 0
The x is 10,The y is 20,The z is 0

上边的结果实现了从 POINT 到 POINT3 的转换,这就是转换构造函数的作用,当然也有友元的帮忙。

explicit

explicit 关键字主要用来修饰构造函数,强调该构造函数必须使用显示转换的方式进行转换。从这句描述来看,可知对于转换构造函数应该是有用的。

#include <iostream>

using std::cout;
using std::endl;
using std::ostream;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

private:
    int x;
    int y;
    friend class POINT3;
};

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}

    explicit POINT3(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
        this->z = 0;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
    }

private:
    int x;
    int y;
    int z;
};

int main()
{
    POINT p1(10,20);
    p1.display();

    POINT3 p2(p1);
    p2.display();

    POINT3 p3 = static_cast<POINT3>(p1);
    p3.display();

    return 0;
}

此时将转换构造函数用关键字 explicit 声明,就只能使用下边的形式进行转换了。

POINT3 p3 = static_cast<POINT3>(p1);

类型转换操作符函数

定义

class sourceclass
{
    operator targetclass()
    {
        statement;
    }
}

性质

  • 转换函数为类方法
  • 转换函数无参数,无返回
  • 该转换函数函数体一般为目标类的构造函数
  • 该转换函数也可以用 explicit 关键字修饰
#include <iostream>

using std::cout;
using std::endl;

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
    }

private:
    int x;
    int y;
    int z;
};

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    operator POINT3()
    {
        return POINT3(x,y,0);
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

private:
    int x;
    int y;
};

int main()
{
    POINT p1(10,20);
    p1.display();

    POINT3 p2(p1);
    p2.display();

    POINT3 p3 = p1;
    p3.display();

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 10,The y is 20,The z is 0
The x is 10,The y is 20,The z is 0

异同点

相同之处

  • 都能够进行自定义类型的转换
  • 该本类中使用这两种转换函数时,都需要提前定义(至少是声明)源类

不同之处

  • 转换构造函数是将源类,转化为该类
  • 类型转换操作符函数则是将该类转换为目标类

高级应用

仿函数

定义

class classname
{
    datatype operator()(argument)
    {
        statement;
    }
}

作用

仿函数能够使类像函数一样使用,其实是重载了 (),使之具有了类似函数的行为:

#include <iostream>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    void operator()()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

private:
    int x;
    int y;
};

int main()
{
    POINT p1(10,20);
    p1();

    return 0;
}

结果为:

The x is 10,The y is 20

上边的结果显示,借由 () 重载能够实现之前 display 成员函数的功能。当然也可以对仿函数进行重载。

new/delete

定义

void *operator new(size_t);
void operator delete(void *);
void *operator new[](size_t);
void operator delete[](void *);

作用

同样为了不改变运算符的基本含义,也是用来申请内存和释放内存的,只是可以在函数体内实现一些定制的功能,一般用不到。

只是此时的 size_t 参数不是由用户手动给出的,而是编译器自动计算传递的。

解引用

和 new/delete 一样,感觉一般用不到。形式为:

classname & operator*()
{
    statement;
}

classname & operator->()
{
    statement;
}

智能指针

这一部分不扩展来说明,同样智能指针存在好几种类型,这里只说明 auto_ptr。

RAII

资源获取即初始化(Resource Acquisition Is Initialization,RAII),是 C++ 的管理资源,避免泄露的方法。

auto_ptr

使用 auto_ptr 可以使资源能够自动的被释放。对于使用 new 申请的内存来说很有用。

#include <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::auto_ptr;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y)
    {
        cout<<"POINT(int x = 0,int y = 0):x(x),y(y)"<<endl;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    ~POINT()
    {
        cout<<"~POINT()"<<endl;
    }

private:
    int x;
    int y;
};

void func()
{
    auto_ptr<POINT> p(new POINT);
    p->display();
}

int main()
{
    func();

    return 0;
}

 结果为:

POINT(int x = 0,int y = 0):x(x),y(y)
The x is 0,The y is 0
~POINT()

从上边的结果可以看出,使用 auto_ptr 可以完成申请内存的自动释放。

其实智能指针的实现也是借用了运算符重载的概念,这里不做详细说明,以后再记。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值