大话C++语言:运算符重载

大话C++语言:运算符重载

编码文章call10242024-12-13 11:57:3928A+A-

1 基本概念

C++运算符重载(operator overloading)是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。通过运算符重载,可以自定义运算符的行为,使其在作用于用户自定义的数据类型时具有特定的意义。

运算符重载只是一种语法上的方便,它并不是创建新的运算符,而是对已有的运算符进行重新定义。重载的运算符仍然保持原有的优先级和结合性,不能改变这些属性。

本质上,运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。运算符重载的语法是使用operator关键字,后跟要重载的运算符符号。重载的运算符函数可以作为类的成员函数或非成员函数进行定义。作为成员函数时,第一个参数隐式地是*this指针,表示当前对象。作为非成员函数时,需要显式地指定所有参数。

运算符重载的目的是为了满足自定义类型的运算需求,使得这些类型能够以更直观、易读和符合语法规则的方式进行运算。通过合理地重载运算符,可以提高代码的可读性和可维护性,同时增强代码的表达能力。然而,在使用运算符重载时需要注意避免过度使用,保持逻辑上的合理性和一致性,以避免引入歧义或性能问题。


2 可重载的运算符

几乎 C++ 中所有的运算符都可以重载,但运算符重载的使用时相当受限制的。特别是不能使用 C++ 中当前没有意义的运算符(例如用**求幂)不能改变运算符优先级,不能改变运算符的参数个数。这样的限制有意义,否则,所有这些行为产生的运算符只会混淆而不是澄清寓语意。 C++中可以重载的运算符类型包括:

运算符类型

例子

描述

算术运算符

+, -, *, /, %, ++, --

用于执行算术运算的运算符。

关系运算符

==, !=, <, >, <=, >=

用于比较两个值的关系的运算符。

逻辑运算符

&&, ||,!

用于逻辑运算

位运算符

&|^~<<>>

用于位运算

赋值运算符

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

用于赋值运算

成员访问运算符

->, ->*

用于访问类的成员或成员指针的运算符。

下标运算符

[]

用于访问数组或容器元素的运算符。

函数调用运算符

()

用于调用函数或方法的运算符。

类型转换运算符

static_cast, dynamic_cast, reinterpret_cast, const_cast

用于执行类型转换的运算符。这些不是直接重载的运算符,但可以在类

然而,并不是所有的运算符都可以被重载。例如,.(成员访问)、.*(成员指针访问)、::(域解析)、sizeof(长度)、?:(条件)等运算符是不能被重载的。

在重载运算符时,还需要注意以下几点:

  • 重载的运算符应保持原有的语义和优先级,不能改变这些属性。
  • 重载的运算符函数可以作为类的成员函数或非成员函数进行定义。作为成员函数时,第一个参数隐式地是*this指针;作为非成员函数时,需要显式地指定所有参数。
  • 重载运算符的目的是为了增强代码的可读性和表达能力,而不是为了创建新的运算符。因此,在使用运算符重载时应谨慎,避免过度使用或滥用。


3 自增/自减(++/--)运算符重载

在C++中,可以通过重载运算符来定义自定义类型上的++--行为。重载需要区分前置和后置的自增(++)和自减(--)运算符。其中,前置版本(如++c)先执行自增/自减操作,然后返回结果。后置版本(如c++)先返回当前值,然后再执行自增/自减操作。

3.1 前置自增/自减

前置版本(如++c)先执行自增/自减操作,然后返回结果。前置版本的语法格式如下:

class ClassName 
{
    // ... 类的其他成员 ...

public:
    // 前置自增运算符重载
    ClassName& operator++() 
    {
        // 自增逻辑
        return *this;
    }

    // 前置自减运算符重载
    ClassName& operator--() 
    {
        // 自减逻辑
        return *this;
    }
};

注意,前置版本返回的是引用对象。

3.2 后置自增/自减

后置版本(如c++)先返回当前值,然后再执行自增/自减操作。后置版本的语法格式如下:

class ClassName 
{
    // ... 类的其他成员 ...

public:
    // 后置自增运算符重载
    ClassName operator++(int) 
    {
        ClassName temp = *this; // 保存当前对象的状态
        // 自增逻辑
        return temp; // 返回自增前的对象状态
    }

    // 后置自减运算符重载
    ClassName operator--(int) 
    {
        ClassName temp = *this; // 保存当前对象的状态
        // 自减逻辑
        return temp; // 返回自减前的对象状态
    }
};

注意,后置版本返回的是对象副本。

3.3 综合案例

以下是一个综合案例,在本案例中,我们创建一个表示复数的类Complex,并为它重载前置和后置的自增自减运算符。

#include <iostream>

class Complex 
{
public:
    // 构造函数
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) 
    {
    }

    // 获取实部
    double GetReal() const 
    {
        return real;
    }

    // 获取虚部
    double GetImag() const 
    {
        return imag;
    }

    // 设置实部
    void SetReal(double r) 
    {
        real = r;
    }

    // 设置虚部
    void SetImag(double i) 
    {
        imag = i;
    }

    // 输出复数
    void Display() const 
    {
        std::cout << "(" << real << ", " << imag << ")" << std::endl;
    }

    // 前置自增运算符重载
    Complex& operator++() 
    {
        ++real; // 实部自增
        ++imag; // 虚部自增
        return *this;
    }

    // 后置自增运算符重载
    Complex operator++(int) 
    {
        Complex temp = *this; // 保存当前对象状态
        ++real;               // 实部自增
        ++imag;               // 虚部自增
        return temp;          // 返回自增前的对象状态
    }

    // 前置自减运算符重载
    Complex& operator--() 
    {
        --real; // 实部自减
        --imag; // 虚部自减
        return *this;
    }

    // 后置自减运算符重载
    Complex operator--(int) 
    {
        Complex temp = *this; // 保存当前对象状态
        --real;               // 实部自减
        --imag;               // 虚部自减
        return temp;          // 返回自减前的对象状态
    }
    
private:
    double real; // 实部
    double imag; // 虚部    
};

int main() 
{
    Complex complexNum(1, 2); // 创建一个复数对象 c1,实部为1,虚部为2

    // 使用前置自增运算符
    ++complexNum;
    std::cout << "前置自增后,该该复数:";
    complexNum.Display(); // 输出: (2, 3)

    // 使用后置自增运算符
    complexNum++;
    std::cout << "后置自增后,该该复数:";
    complexNum.Display(); // 输出: (3, 4)

    // 使用前置自减运算符
    --complexNum;
    std::cout << "前置自减后,该该复数:";
    complexNum.Display(); // 输出: (2, 3)

    // 使用后置自减运算符
    complexNum--;
    std::cout << "后置自减后,该该复数:";
    complexNum.Display(); // 输出: (1, 2)

    return 0;
}

3.4 自增/自减注意事项

在C++中,重载自增(++)和自减(--)运算符时,需要注意以下事项:

  • 返回值类型
    • 前置版本(如 ++operator)应该返回对象的引用(ClassName&),以便支持链式操作。
    • 后置版本(如 operator++)应该返回对象的一个副本(ClassName),因为后置版本需要在修改对象之前返回其原始值。
  • 参数
    • 后置版本通常带有一个未使用的整型参数(通常命名为dummy),这主要是为了与前置版本区分开。这个参数在函数调用时被忽略。
  • 不改变运算符优先级
    • 重载运算符不会改变运算符的优先级。这意味着重载后的运算符应该遵循原有的优先级规则。
  • 保持原有语义
    • 重载后的运算符应该尽量保持原有语义。例如,重载++运算符应该模拟原有加一的语义。


4 等于和不等于(==/!=)运算符重载

在C++中,==!=运算符也可以被重载,以便为自定义类型提供相等性比较的逻辑,其语法格式如下:

class ClassName 
{
    // ... 类的其他成员 ...

public:
    // 重载 == 运算符作为成员函数
    bool operator==(const ClassName& other) const 
    {
        // 实现比较逻辑并返回结果
    }

    // 重载 != 运算符作为成员函数(可选,但通常推荐)
    bool operator!=(const ClassName& other) const 
    {
        // 通常返回 !(this == other) 的结果
        return !(*this == other);
    }

    // ... 类的其他成员 ...
};

注意,当重载这些运算符时,参数应该是常量引用(const ClassName&),这样可以避免不必要的对象复制,并且允许比较临时对象和常量对象。同时,这些函数通常也被声明为const,因为它们不修改调用它们的对象的状态。

#include <iostream>

class Complex 
{
public:
    // 构造函数
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) 
    {
    }

    // 获取实部
    double GetReal() const 
    {
        return real;
    }

    // 获取虚部
    double GetImag() const 
    {
        return imag;
    }

    // 设置实部
    void SetReal(double r)
    {
        real = r;
    }

    // 设置虚部
    void SetImag(double i)
    {
        imag = i;
    }

    // 输出复数
    void Display() const 
    {
        std::cout << "(" << real << ", " << imag << ")" << std::endl;
    }

    // 重载 == 运算符
    bool operator==(const Complex& other) const 
    {
        return (real == other.real) && (imag == other.imag);
    }

    // 重载 != 运算符
    bool operator!=(const Complex& other) const 
    {
        return !(*this == other);
    }
    
private:
    double real; // 实部
    double imag; // 虚部    
};

int main() 
{
    Complex c1(1, 2); // 创建一个复数对象 c1,实部为1,虚部为2
    Complex c2(1, 2); // 创建另一个复数对象 c2,与 c1 相等
    Complex c3(3, 4); // 创建另一个复数对象 c3,与 c1 不相等

    // 使用重载的 == 运算符
    if (c1 == c2) 
    {
        std::cout << "c1和c2相同" << std::endl;
    } else {
        std::cout << "c1和c2不相同" << std::endl;
    }

    // 使用重载的 != 运算符
    if (c1 != c3)
    {
        std::cout << "c1和c3不相同" << std::endl;
    } 
    else
    {
        std::cout << "c1和c3相同" << std::endl;
    }

    return 0;
}

注意

  • 返回值类型==!=运算符应该返回布尔值(bool),表示比较的结果(相等或不相等)。
  • 对称性==运算符应该满足对称性,即a == bb == a应该产生相同的结果。!=运算符则是==的否定,即a != b当且仅当a == bfalse时返回true
  • 传递性:如果a == bb == c都为true,那么a == c也应该为true。同样地,如果a != bb != c都为true,那么a != c也应该为true
  • 一致性:如果重载了==运算符,通常也应该重载!=运算符,以保持一致性。


5 函数调用符号()重载

在C++中,operator() 被称为函数调用运算符或调用操作符。当它被重载时,允许类的对象像函数那样被调用。这通常用于创建可调用对象,这些对象可以模拟函数的行为。重载 operator() 可以使对象看起来像是可调用的函数,并允许你以更直观和面向对象的方式封装某些操作。重载operator()的语法格式如下:

class ClassName 
{
public:
    // 重载 operator()
    ReturnType operator()(ParameterList) 
    {
        // 函数体,实现所需功能
        // ReturnType 是你希望 operator() 返回的类型
        // ParameterList 是 operator() 所接受的参数列表
    }
};

其中,

  • ClassName 是类名
  • ReturnTypeoperator() 返回的类型
  • ParameterListoperator() 所接受的参数列表
#include <iostream>

class Complex 
{
private:
    double real; // 实部
    double imag; // 虚部

public:
    // 构造函数
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) 
    {
    }

    // 获取实部
    double GetReal() const 
    {
        return real;
    }

    // 获取虚部
    double GetImag() const 
    {
        return imag;
    }

    // 设置实部
    void SetReal(double r) 
    {
        real = r;
    }

    // 设置虚部
    void SetImag(double i) 
    {
        imag = i;
    }

    // 输出复数
    void Display() const 
    {
        std::cout << "(" << real << ", " << imag << ")" << std::endl;
    }

    // 重载 operator(),执行复数加法
    Complex operator()(const Complex& other) const 
    {
        return Complex(real + other.real, imag + other.imag);
    }
};

int main() 
{
    // 创建两个 Complex 对象
    Complex c1(1, 2); // (1, 2)
    Complex c2(3, 4); // (3, 4)

    // 使用重载的 operator() 进行复数加法
    // 调用 c1 的 operator(),参数为 c2
    Complex result = c1(c2); 

    // 输出结果
    result.Display();

    return 0;
}

注意,虽然 operator() 被重载了以模拟函数调用的外观,但它仍然是一个类的成员函数,并且遵循成员函数的调用规则。



---E N D---

喜欢的记得关注哦!

您的支持是我们前进的动力!

职创未来|专注IT与新能源领域中高端人才培养

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4