运算符重载用实例来讲解更加方便,这次我们同时引入自建头文件的编译,比如,我们自定义了一个mySpan类,我们想让mySpan之间可以叠加,以达到现实中两个向量相加的效果:
//MyClasses.h
#pragma once
#include
#include
class mySpan
{
public:
mySpan()
:x{0},y{0}
{}
mySpan(double X,double Y)
:x{X},y{Y}
{}
~mySpan()
{
std::cout << std::format("mySpan Destoryed\n");
}
mySpan operator+(const mySpan& InSpan); //先在此处声明重载
private:
double x;
double y;
};
//MyClasses.cpp
#include "MyClasses.h"
mySpan mySpan::operator+(const mySpan& InSpan) //在此处定义重载
{
return mySpan{this->x+InSpan.x,this->y+InSpan.y};
}
//源.cpp
#include "MyClasses.h"
int main()
{
mySpan span1(1, 1), span2(2, 2);
mySpan span3 = span1 + span2;
}
现在通过编译后我们便能得到(3, 3)的span3向量了,我们看向函数重载的定义,mySpan mySpan::operator+(const mySpan& InSpan),在 operator前加一个mySpan的限定符是必要的,这使得函数实际上成为mySpan类的成员,以便于我们访问权限为 private的 x和 y。基本的函数重载的结构就是这样子了:
需要返回的类型 operator需要重载的符号(作为类成员重载时此处参数只能有一个)
我们知道mySpan指的是一个向量,我们知道它的表示方法是 (x, y),但很可惜编译器不知道,所以我们需要重载 <<运算符来实现向量的输出,但是这里我们就犯难了:<<的类型在C++中是 std::ostream,但是类内的运算符重载只能传入一个参数。我们一般用友元来实现:
//MyClsses.h
Class mySpan
{
public:
mySpan()
:x{0},y{0}
{}
mySpan(double X,double Y)
:x{X},y{Y}
{}
~mySpan()
{
std::cout << std::format("mySpan Destoryed\n");
}
mySpan operator+(const mySpan& InSpan);
friend std::ostream& operator<<(std::ostream& stream, const mySpan& inspan); //<<的类型是std::ostream,在类内通过添加friend关键词来声明<<的运算符重载是类的友元,让运算符重载获得类内 private元素的权限
private:
double x;
double y;
};
//MyClasses.cpp
std::ostream& operator<<(std::ostream& stream, const mySpan& inspan) //此处并不需要添加mySpan::限定符,因为<<的重载是全局的
{
stream << "(" << inspan.x << "," << inspan.y << ")" << std::endl;
return stream;
}
//源.cpp
#include "MyClasses.h"
int main()
{
mySpan span1(1, 1), span2(2, 2);
mySpan span3 = span1 + span2;
std::cout<<"span3 = " << span3;
输出:
span3 = (3,3)
//MyClasses.cpp
const mySpan mySpan::operator++() //后++是指先自增再输出
{
x++;
y++;
return *this;
}
const mySpan mySpan::operator++(int) //前++是指先输出再自增,添加参数int(只能是int)告诉编译器这是前++
{
mySpan formerSpan = *this; //保存自增前的量,因为前++输出的是自增前的值
x++;
y++;
return formerSpan;
}
注:如果不在返回值前添加const即默认允许出现类似于span1++++的语法,一般来说是要避免的,所以建议在返回值前添加const
限制:
·不能发明新的运算符,如?、===或<>。
·不能修改现有运算符的操作数个数、相关性或优先级,也不能改变运算符的操作数的计算顺序。
·一般来说,不能重写内置的运算符,并且重载运算符的签名必须涉及至少一种类类型。我们并不能修改现有运算符操作基本类型或数组类型的方式,例如不能让整数的加法实行乘法操作。
1.绝不重载逻辑运算符&&或||,如果想为自己的类对象使用运算符重载,则应选择重载&与|,原因是&&与||会发生短路计算,为了避免不必要的bug发生,采用不会进行短路计算的&与|进行重载(同样的原因,因为计算操作数的顺序没有被强制要求,故在C++17之前也不建议重载','(逗号)运算符)。
2.一般约定比较运算符的返回值为bool类型。
3.C++20提供了“太空飞船运算符”<=>,返回值为std::strong_ordering::less、std::strong_ordering::greater、std::strong_ordering::equal以及std::strong_ordering::equivalent(当然还有std::partial::ordering与std::weak_ordering,后面单独出章节介绍)
4.标准库的模块在std::rel_ops名称空间中提供了一组运算符模板,使用T的<和==运算符,为任何类型T定义了<=、>、>=和!=运算符。不过,这种方案有缺陷,不应该再使用。
5.运算符重载大多数时候应该实现为成员函数。只有当不能使用成员函数。或者希望对第一个操作数进行隐式转换时,才能使用非成员函数。
在C++20之前的版本中,写这类型的运算符重载很难以复用和维护,想象一下我们写了一个10维向量,我们在比较两个向量时只能写十个==比较。好消息是C++20提供了默认的比较运算符重载,它于构造函数以及析构函数相似,但是需要我们主动要求它生成,语法如下:
bool operator==(const mySpan& InSpan) const = default; //此处是在类内声明
这样我们就可以使用C++20提供的默认==运算符重载了,它的好处在于当我们改变需要比较的参数时不需要手动去更改代码,编译器会主动帮我们维护(C++20是相当聪明哒)。
所以说,如果我们这么写…
auto operator<=>(const mySpan& InSpan) const = default; //她温,我哭
注:每当默认生成<=>时,编译器也将添加默认生成的==运算符。因此,如果所有比较运算符的默认行为符合要求,就只需要默认生成一个运算符函数:<=>。这太容易了。