• c++运算符重载


    运算符重载

    为SpreadsheetCell实现加法

    请看下面代码

    class SpreadsheetCell
    {
    public:
        SpreadsheetCell(){};
        SpreadsheetCell(int value) : m_val{value} {}
        int getValue() const
        {
            return m_val;
        }
    
    private:
        int m_val;
    };
    
    int main()
    {
        int a{1}, b{2};
        a + b;
        SpreadsheetCell s1{1}, s2{2};
        s1 + s2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述代码创建了一个电子表格单元格类,在main()函数内又创建了两个SpreadsheetCell对象,并且将两个对象相加。对于第21行a+b两个基本类型变量相加肯定是可以编译通过的,但是第23行两个对象相加编译就会报错。报错信息如下:

     error: no match foroperator+(operand types are ‘SpreadsheetCell’ and ‘SpreadsheetCell’)
    
    • 1

    在c++中没有对自定义类型进行运算符'+'运算的实现,所以需要手动实现自定义类型的相加。

    1.首次尝试:add方法

    增加一个add函数,该函数实现SpreadsheetCell对象相加

    class SpreadsheetCell
    {
    public:
        SpreadsheetCell(){};
        SpreadsheetCell(int value) : m_val{value} {}
        int getValue() const
        {
            return m_val;
        }
        SpreadsheetCell add(const SpreadsheetCell &cell) const;
    
    private:
        int m_val;
    };
    
    SpreadsheetCell SpreadsheetCell::add(const SpreadsheetCell &cell) const
    {
        return SpreadsheetCell{getValue() + cell.getValue()};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这种方法的确可以实现自定义的SpreadsheetCell类型相加,但是有点笨拙。

    2.第二次尝试:将operator+作为方法重载

    '+'号对两个单元格相加比较方便,就像对基本类型的相加一样。

    c++中允许编写自己的加号版本,以正确的处理类,称为加运算符。为此可以编写一个名为operator+的方法,如下:

    class SpreadsheetCell
    {
    public:
        SpreadsheetCell(){};
        SpreadsheetCell(int value) : m_val{value} {}
        int getValue() const
        {
            return m_val;
        }
        SpreadsheetCell operator+(const SpreadsheetCell &cell) const;
    
    private:
        int m_val;
    };
    
    // 允许在operator和'+'之间存在空格
    SpreadsheetCell SpreadsheetCell::operator+(const SpreadsheetCell &cell) const
    {
        return SpreadsheetCell{getValue() + cell.getValue()};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    对于operator+这样的写法可能有点奇怪,不用过多担心,这就是一个名称,就像add函数一样。当c++编译器分析一个程序,遇到运算符(例如:+、-、=或<<)时,就会查找名为operator+operator-operator=或者operator<<,且具有适当参数的函数或者方法。

    SpreadsheetCell s1{1}, s2{2};
    
    SpreadsheetCell s3{s1 + s2};
    // 等同于
    SpreadsheetCell s3{s1.operator+(s2)};
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意,用作operator+参数的对象类型并不一定要与编写operator+的类相同。同时可以指定operator+的返回类型。

    隐式转换
    class SpreadsheetCell
    {
    public:
        SpreadsheetCell(){};
        SpreadsheetCell(double value) : m_val{value} {}
        SpreadsheetCell(string_view value)
            : SpreadsheetCell{stringToDouble(value)} {}
    
        double getValue() const
        {
            return m_val;
        }
    
        SpreadsheetCell operator+(const SpreadsheetCell &cell) const;
    
        string doubleToString(double value) const;
        double stringToDouble(string_view value) const;
    
    private:
        double m_val{0};
    };
    string SpreadsheetCell::doubleToString(double value) const
    {
        return to_string(value);
    }
    double SpreadsheetCell::stringToDouble(string_view value) const
    {
        double number{0};
        from_chars(value.data(), value.data() + value.size(), number);
        return number;
    }
    
    // 允许在operator和'+'之间存在空格
    SpreadsheetCell SpreadsheetCell::operator+(const SpreadsheetCell &cell) const
    {
        return SpreadsheetCell{getValue() + cell.getValue()};
    }
    
    int main()
    {
        SpreadsheetCell s1{1}, s4{0}, s5{0};
        string str{"124"};
        s4 = s1 + string_view(str);
        cout << "s4.value = " << s4.getValue() << endl;
        s5 = s1 + 5.6;
        cout << "s5.value = " << s5.getValue() << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    执行结果:

    s3.value = 3
    s4.value = 125
    s5.value = 6.6
    
    • 1
    • 2
    • 3

    上述代码中,涉及到数值装换函数from_chars,不会的可以点击此处学习

    当编译器看到SpreadsheetCell试图与double类型相加时,发现了用double值作为参数的SpreadsheetCell构造函数,就会生成一个临时的SpreadsheetCell对象,传递给operator+。与此类似,string_view也是同样操作。

    由于必须创建临时对象,隐式使用构造函数的效率不高。为避免与double值相加时隐式的使用构造函数,可以编写第二个operator+。如下:

    SpreadsheetCell SpreadsheetCell::operator+(const double rhs) const
    {
        return SpreadsheetCell{getValue() + rhs};
    }
    
    • 1
    • 2
    • 3
    • 4

    第三次尝试:全局operator+

    隐式转换允许使用operator+方法将SpreadsheetCell对象与intdouble值相加。然而,这个运算符不具有互换性。

    s4 = s1 + 5;    
    s5 = s1 + 5.6;
    
    s4 = 5 + s1;    // error
    s5 = 5.6 + s1;  // error
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当对象在左边时,隐式装换正常运行,但是在右边就无法运行。不符合加法的运算规律。问题在于必须在SpreadsheetCell对象上调用operator+方法,对象必须在operator+的左边。这是c++语言定义的方式,因此,使用operator+方法无法让上面的代码运行。

    然而,如果用不局限于某个特定对象的全局operator+函数替换类内的operator+方法,上面的代码即可运行。示例:

    SpreadsheetCell operator+(const SpreadsheetCell &lhs, const SpreadsheetCell &rhs)
    {
        return SpreadsheetCell{lhs.getValue() + rhs.getValue()};
    }
    
    • 1
    • 2
    • 3
    • 4

    假如我编写出下面这样的代码,编译器会如何应对呢?

    SpreadsheetCell s1;
    s1 = 1.1 + 5.5;
    
    • 1
    • 2

    首先,这个代码是肯定可以运行的,但是并没有调用前面operator+。这段代码将普通的double型数值1.1与5.5相加,得到了下面展示的中间语句:

    s1 = 6.6;
    
    • 1

    为了让赋值操作继续,运算符右边应该是SpreadsheetCell对象。编译器找到非explicit(防止隐式转换)的由用户定义的double值作为参数的构造函数,然后将这个构造函数隐式的将double值转换为一个临时SpreadsheetCell对象,最后调用赋值运算符

    注意:在c++中不能更改运算符的优先级,也不能发明新的运算符号,不允许更改运算符的实参个数。

    重载算数运算符

    上述已经对自定义类型实现了+的操作,所以对减法、乘法、除法都类似的操作。还可以重载%。

    对于operator/而言,唯一棘手之处是记着检查除数是否为0。如果检测到除数为0,该实现将抛出异常。

    SpreadsheetCell operator/(const SpreadsheetCell &lhs, const SpreadsheetCell &rhs)
    {
        if (rhs.getValue() == 0)
        {
            throw invalid_argument{"Divide by zero"};
        }
        return SpreadsheetCell{lhs.getValue() / rhs.getValue()};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    c++并没有真正要求在operator*中实现乘法,在operator/中实现除法。可在operator/中实现乘法,在operator+中实现除法,以此类推。然而这样做会让人非常迷惑,也没有理由这么去做。在实现中应该尽量使用常用的运算符含义。

    除基本算术运算符外,C++还提供了简写运算符,例如+=和-=。这些运算符与基本算数运算符不同,它们会改变运算符左边的对象,而不是创建一个新的对象。此外还有一个微妙的差别,它们生成的结果是对被修改对象的引用,这一点与赋值运算符类似。

    简写算数运算符的左边总要有一个对象,因此应该将其作为方法,而不是全局函数。

    class SpreadsheetCell
    {
    public:
        SpreadsheetCell &operator+=(const SpreadsheetCell &rhs);
        SpreadsheetCell &operator-=(const SpreadsheetCell &rhs);
        SpreadsheetCell &operator*=(const SpreadsheetCell &rhs);
        SpreadsheetCell &operator/=(const SpreadsheetCell &rhs);
    
        double getValue() const
        {
            return m_val;
        }
    private:
        double m_val{0};
    };
    
    SpreadsheetCell &SpreadsheetCell::operator+=(const SpreadsheetCell &rhs)
    {
        this->m_val += rhs.getValue();
        return *this;
    }
    
    // -=、*=、/=类似操作。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    如果代码中既有某个运算符的普通版本,又有简写版本(如+=),建议基于简写版本实现普通版本,以避免代码重复。例如:

    SpreadsheetCell operator+(const SpreadsheetCell &lhs, const SpreadsheetCell &rhs)
    {
        auto result{lhs};
        result += rhs; // 调用+=版本;
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重载比较运算符

    比较运算符(例如>、<、<=、>=、==和!=)是另一组对类有用的运算符。c++20标准为这些操作带来了很多变化,并添加了三向比较运算符,也成为宇宙飞船运算符,<=>

    与基本算数运算符一样,6个c++20之前的比较运算符应该是全局函数,这样就可以在运算符的左侧或者右侧参数上使用隐式转换。比较运算符都返回bool类型,当然,也可以更改返回类型,但是不建议这样做。

    下面是比较运算符的声明,必须用>、<、<=、>=、==和!=替换< op >,得到6个函数

    bool operator<op>(const SpreadsheetCell &lhs, const SpreadsheetCell &rhs);
    
    • 1

    下面是函数operator==的定义,其他类似:

    bool operator==(const SpreadsheetCell &lhs, const SpreadsheetCell &rhs)
    {
    	return (lhs.getValue() == rhs.getValue());
    }
    
    • 1
    • 2
    • 3
    • 4

    注意:大多数时候,最好不要对浮点数执行相等或者不相等的操作,应该用ε测试(epsilon test)。

    当类中的数据成员较多时,比较每个数据成员可能比较痛苦。然而当实现了==<之后,可以根据这两个运算符编写其他比较运算符。例如,下面的operator>=定义中使用了operator<

    bool operator>=(const SpreadsheetCell &lhs, const SpreadsheetCell &rhs)
    {
    	return !(lhs < rhs);
    }
    
    • 1
    • 2
    • 3
    • 4

    如果你需要支持所有的比较运算符,需要写很多的代码。

    在c++20中,简化了向类中添加对比较运算符的支持,首先,对于c++20,实际上建议将operator==实现为类的成员函数,而不是全局函数,还要注意,添加[[nodiscard]]属性是一个不错的操作,这样操作符的结果就不能被忽略。

    在c++20中,单个的operator==重载就可以使下面的代码生效

    if (myCell == 10) {cout << "myCell == 10" << endl;}
    if (10 == myCell) {cout << "10 == myCell" << endl;}
    
    • 1
    • 2

    上面的10 == myCell的表达式由c++20编译器重写为myCell == 10,可以为其调用operator==成员函数。此外, 通过实现operator==,c++20会自动添加对!=的支持。

    接下来,要实现对全套的比较运算符的支持,在c++20中,只需要实现一个额外的重载运算符,operator<=>,一旦类有了运算符==<=>的重载,c++20就会自动为所有的6个比较运算符提供支持,对于spreadsheetCell类,运算符<=> 如下:

    [[nodiscard]] std::partial_ordering operator<=>(const SpreadsheetCell& rhs) const;
    
    • 1

    注意:c++20编译器不会根据<=>重写或者!=,这样做是为了避免性能问题,因为operator的显示实现通常比<=>更加高效!

    重载返回的类型partial_ordering是c++20引入的,是三向比较的结果类型。

    <=>实现如下:

    partial_ordering SpreadsheetCell::operator<=>(const SpreadsheetCell &rhs) const
    {
        return getValue() <=> rhs.getValue();
    }
    
    • 1
    • 2
    • 3
    • 4

    注意编译的时候,如果你的编译器不支持c++20,要加上-std=c++20的选项。

    如果用SpreadsheetCell类与double类型比较,则会和前面一样,会产生隐式转换。如果希望避免隐式转换带来对性能上的影响,可以为double提供特定的重载。对于现在,有了c++20,不需要有太多的工作,只需要提供两个额外的重载运算符作为方法。

    [[nodiscard]] bool operator==(double rhs) const;
    [[nodiscard]] std::partial_ordering operator<=>(double rhs) const;
    
    • 1
    • 2

    实现如下:

    bool SpreadsheetCell::operator==(double rhs) const
    {
    	return getValue() == rhs;
    }
    
    std::partial_ordering SpreadsheetCell::operator<=>(double rhs) const
    {
    	return getValue() <=> rhs;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    编译器生成的比较运算符

    注意:SpreadsheetCelloperator==<=>的实现,它们只是简单的比较所有的数据成员。在这种情况下,可以进一步的减少需要编写的代码行数,因为c++20可以为我们编写这些代码。例如,拷贝构造函数可以显示的设置为默认。operator==<=>也可以默认。在这种情况下,编译器会为你编写他们,并通过依次比较每个数据成员来实现他们。此外,如果只是显示的使用默认operator<=>,编译器会自动包含一个默认的operator==。因此,对于SpreadsheetCell类,如果没有显示的double版本的operator==<=>,只需要编写以下单行代码,即可添加对所有的6个比较运算符的完全支持。

    [[nodiscard]]std::partial_ordering operator<=>(const SpreadsheetCell&)const = default;
    
    • 1

    如果用户显示的添加了double版本的<=>,则编译器不在自动生成operator==(const SpreadsheetCell &),因此必须将其显示默认,如下所示:

    class SpreadsheetCell {
    public:
        [[nodiscard]] auto operator<=>(const SpreadsheetCell &rhs) const = default;
        [[nodiscard]] bool operator==(const SpreadsheetCell &rhs) const = default;
    
        [[nodiscard]] std::partial_ordering operator<=>(double rhs) const;
        [[nodiscard]] bool operator==(double rhs) const;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    建议将显示的将operator<=>设置为默认,通过让编译器为你编写,它将与新添加或者修改的数据成员保持同步。

    注意:只有当operator==<=>使用定义操作的类类型的const引用作为参数的时候,才可能显示的将operator==<=>设置为默认。例如下列的操作将不起作用:

    [[nodiscard]] auto operator<=>(double) const = default;  // dont work!
    
    • 1
  • 相关阅读:
    Java 压缩PDF文档
    gin-admin-react踩坑
    [附源码]Python计算机毕业设计SSM基于H5乡镇疫情防控系统(程序+LW)
    Python机器视觉--OpenCV入门--视频的加载,录制
    骗子查询系统源码
    java家用电器springboot家电销售网站管理系统
    ogrinfo不是内部或者外部命令
    正则验证用户名和跨域postmessage
    [附源码]Python计算机毕业设计Django房产中介管理系统
    vite的使用
  • 原文地址:https://blog.csdn.net/weixin_48617416/article/details/130907111