c++重载操作符

支持重载操作符是c++的一个特性,本文分两个部分介绍

  • 重载操作符简介和使用——适用新手
  • 重载操作符的原理和sao操作——适用装杯选手

1 简介 & 使用

c++支持重载的操作符见本文最后的 重载操作符一览表。这些操作符按照操作数个数不同分为单目操作符、双目操作符、多目操作符,其中大多数为双目运算符,少部分为单目运算符或者多目运算符。

用法

重载操作符实际上就是重新定义操作符的行为函数,重载操作符时需要用到关键字operator。大多数操作符有两种重载方式

  • 类成员函数重载
  • 全局函数重载

形式如下

返回值 operator符号 (形参列表) { 函数体 }

例如

// 成员函数重载Point类的"+"操作符和"~"操作符 —— 定义在类内部
struct Point {
    Point operator+(const Point& oth) const { ... }
    Point operator~() const { ... }
};

// 全局函数重载Point类的"+"操作符和"~"操作符 —— 定义在类外部
Point operator+(const Point& self, const Point& oth) { ... }
Point operator~(const Point& self) { ... }

一些限制

在不熟悉的情况下重载操作符看起来似乎很难掌握,但实际上它和普通函数类似,只是存在一些特殊限制

1)基本数据类型只能通过全局函数重载操作符。因为基本数据类型是语言内置的,无法修改其定义。

2)对于自定义类型,所有支持重载的操作符都可以通过类成员函数重载;除个别操作符外,大多数操作符都可以通过全局函数重载(具体见文末的表格)。

3)单目、双目、多目操作符的形参个数分别如下,形参类型和返回值类型一般没有特殊要求

单目操作符

双目操作符

多目操作符

类成员函数

0

1

0个或多个

全局函数

1

2

-

以上内容足够日常使用重载操作符。但有些同学会觉得重载操作符很难理解和正确使用,下面一起理解一下重载操作符的本质,理解本质之后再回头来看就会发现重载操作符原来很简单。

2 重载操作符的本质

重载操作符本质上是特殊的函数

在c++中,函数具有以下形式

返回值 函数名 (形参列表) { 函数体 }

函数由四个部分组成:返回值、函数名、形参列表、函数体。重载操作符本质上也是函数,只是在 函数名形参列表 两部分具有特殊性,另外 调用方式 也很特殊。

函数名

重载操作符的函数名是由operator关键字和操作符符号组成的,例如 operator+、operator!等等。

形参列表

形参列表的参数数量是固定的,具体见下面的表格

单目操作符

双目操作符

操作符-特殊

全局函数

1 (self)

2 (self, 任意类型)

-

类成员函数

0

1 (任意类型)

0个或多个

一个没用的小知识

细心的同学应该已经发现了一个现象:对于同一个操作符而言,全局函数重载总是比类成员函数重载多一个参数,而且这个多出来的参数有两个特点:

  • 一定是全局函数的第一个形参。
  • 类型一定是重载操作符的目标类型。

例如:

成员函数重载: Point operator+(const Point& oth) const { ... }

全局函数重载: Point operator+(const Point& self, const Point& oth) { ... }

这里隐藏了一个成员函数的秘密:在c++中,成员函数形参列表的第一个参数是this指针,只不过写法上忽略了。大家感兴趣的话可以研究下成员函数的汇编码,其中的奥秘就一目了然了。熟悉python语法的同学可能很容易理解,python的类成员函数必须把第一个参数写成self,这样才能在函数体内访问成员变量。

调用方式

操作符的使用和函数调用有直观上的差别,如何使用操作符大家应该都很熟悉,这里就不举例子了。值得一提的是操作符的使用本质上还是函数调用。举个例子,用全局函数重载"+"操作符

struct Point {
    Point(int x, int y) : x(x), y(y) { }
    int x;
    int y;
};

// 重载Point的“+”操作符
Point operator+(const Point& self, const Point& oth) { return Point(self.x + oth.x, self.y + oth.y); }

int main() {
    Point p1(20, 60);
    Point p2(2, 5);
    Point pAdd = p1 + p2;    // 使用Point的“+”操作符
}

上面这段代码的汇编代码如下(为了方便大家能抓住重点,对汇编码做了精简)

Point::Point(int, int) [base object constructor]:
        ...
        ret
operator+(Point const&, Point const&):
        ...
        ret
main:
        ...
        mov     rsi, rdx
        mov     rdi, rax
        call    operator+(Point const&, Point const&)
        ...
        ret

可以看到,编译器把Point pAdd = p1 + p2;这句c++代码编译成了call operator+(Point const&, Point const&),也就是调用函数operator+(Point const&, Point const&)

看到这里,大家脑子里会不会闪过一个大胆的想法——在c++代码中直接调用函数operator+(Point const&, Point const&)会怎么样?就像...

...
    Point pAdd = operator+(p1, p2);    // Point pAdd = p1 + p2;
...

然后我们会发现代码竟然可以 正!常!运!行!而且对应的汇编代码也一!模!一!样!所以,操作符也可以通过函数掉调用的方式使用

前面研究的是全局函数重载的行为,那么成员函数重载呢?我们一起来看看

struct Point {
    Point(int x, int y) : x(x), y(y) {}
    // 重载Point的“+”操作符
    Point operator+(const Point& oth) const { return Point(x + oth.x, y + oth.y); }
    int x;
    int y;
};

int main() {
    Point p1(20, 60);
    Point p2(2, 5);
    Point pAdd = p1 + p2;    // 使用Point的“+”操作符
}

对应的汇编代码

Point::Point(int, int) [base object constructor]:
        ...
        ret
Point::operator+(Point const&) const:
        ...
        ret
main:
        ...
        mov     rsi, rdx
        mov     rdi, rax
        call    Point::operator+(Point const&) const
        ...
        ret

编译器把Point pAdd = p1 + p2;这句c++代码编译成了call Point::operator+(Point const&) const。不难发现,全局函数重载和成员函数重载对应的汇编代码的operator+符号不一样:

  • 形参列表不同。全局函数有两个形参,成员函数有一个形参。
  • 域前缀不同。全局函数没有域前缀,类成员函数汇编后携带了域信息Point

成员函数重载操作符也可以像函数调用一样使用,只不过需要遵守成员函数的调用规则

...
    Point pAdd = p1.operator+(p2);    // Point pAdd = p1 + p2;
...

下面做个简单对比

全局函数重载

类成员函数重载

c++代码

Point operator+(const Point& self, const Point& oth) { ... }
struct Point {
    Point operator+(const Point& oth) const { ... }
};

汇编代码

operator+(Point const&, Point const&)

Point::operator+(Point const&) const

函数式调用

Point pAdd = operator+(p1, p2);

Point pAdd = p1.operator+(p2);

以上就是重载操作符相对于普通函数的特殊所在,除了这几个特殊点其他方面没有任何不同。在使用的时候我们可以对返回值形参类型函数体为!所!欲!为!

严重警告

  • 尽管规则允许我们直接调用重载操作符,但尽量不要这样做,因为这样做的话c++的NB程度会锐减,而且一定会有人在看代码的时候骂你。
  • 尽管规则上可以为所欲为,还是建议各位保持冷静,玩玩儿可以千万不要在项目中写出太超越道德上限的代码。像这样
struct Cat { ... };
struct Dog { ... };

Dog operator+(const Cat& self, const Cat& oth) {
    ...
    return Dog();
}

一只猫加另一只猫,得到一条狗?

重载操作符一览表

类别操作符类型

成员函数

重载

全局函数

重载

算术操作符

+(正号)、-(负号)、++、--单目⭕️⭕️

+、-、*、/、%

双目⭕️⭕️

关系操作符

==、!=、>、<、>=、<=

双目⭕️⭕️

逻辑操作符

!单目⭕️⭕️

&&、||

双目⭕️⭕️

位操作符

~单目⭕️⭕️

&、|、^、<<、>>

双目⭕️⭕️

赋值操作符

=

双目⭕️

复合赋值操作符

+=、-=、*=、/=、%=、&=、|=、^=、>>=、<<=

双目⭕️⭕️

下标操作符

[]

双目⭕️

函数调用操作符

()

多目⭕️

成员访问操作符

->

单目⭕️

指针操作符

*(解引用)、&(取地址)

单目⭕️⭕️

逗号操作符

,

双目⭕️⭕️
类型转换操作符operator <Type>()单目⭕️
内存操作符new、delete、new[]、delete[]-⭕️⭕️
  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值