Day 16 C++ 运算符重载

目录

定义

注意

举例

加号运算符重载(+)

成员函数重载

非成员函数重载

总结

左移运算符重载(<<)

成员函数重载

非成员函数重载

链式编程(Chaining)

递增运算符重载(++)

前置递增运算符(++obj)

后置递增运算符(obj++)

占位参数

赋值运算符重载(=)

关系运算符重载

函数调用运算符()重载

仿函数


定义

运算符重载是一种特性,对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型,operator 是一个关键字,用于表示运算符重载。运算符重载允许我们定义自定义类型的运算符行为,使其能够像内置类型一样使用。C++ 中的运算符包括算术运算符(例如 +-*/)、比较运算符(例如 ==!=><)、逻辑运算符(例如 &&||!)等等。

在C++中,可以通过成员函数或非成员函数实现运算符重载。成员函数重载运算符时,该函数将作为类的成员,并且至少有一个操作数是该类的对象。非成员函数重载运算符时,它们没有隐式的this指针参数,并且操作数可以是任意类型。

注意

对于内置的数据类型的表达式的的运算符是不可能改变的

不要滥用运算符重载

在成员函数中使用 this 指针是可选的,如果没有命名冲突的情况,可以省略 this->,直接使用成员变量和成员函数的名称即可。

举例

加号运算符重载(+)

可以实现两个自定义数据类型相加的运算

成员函数重载
#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int v) : value(v) {}

    MyClass operator+(const MyClass& other) const {
        return MyClass(value + other.value);
    }

    int getValue() const {
        return value;
    }
};

int main() {
    MyClass a(5);
    MyClass b(10);

    MyClass c = a + b;

    std::cout << "c = " << c.getValue() << std::endl;  // 输出:c = 15

    return 0;
}

在上面的示例中,我们定义了一个名为MyClass的类,并重载了加号运算符(+)作为成员函数。该成员函数接受一个参数,表示需要与当前对象相加的另一个对象。在函数体内,我们执行了加法运算并返回一个新的MyClass对象。

非成员函数重载
#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int v) : value(v) {}

    int getValue() const {
        return value;
    }

    friend MyClass operator+(const MyClass& obj1, const MyClass& obj2);
};

MyClass operator+(const MyClass& obj1, const MyClass& obj2) {
    return MyClass(obj1.value + obj2.value);
}

int main() {
    MyClass a(5);
    MyClass b(10);

    MyClass c = a + b;

    std::cout << "c = " << c.getValue() << std::endl;  // 输出:c = 15

    return 0;
}

在上面的示例中,我们定义了一个名为MyClass的类,并使用friend关键字声明了非成员函数operator+()。在函数体内,我们执行了加法运算并返回一个新的MyClass对象。

总结

我们可以使用 + 运算符来调用 MyClass 对象的成员函数 operator+。例如,a + b 实际上相当于调用了 a.operator+(b),其中 a是左操作数,b 是右操作数。

无论是成员函数重载还是非成员函数重载,都可以实现相同的功能。选择使用哪种方式主要取决于代码的结构和设计需求。重载加号运算符的结果是使得MyClass对象可以直接使用加号进行相加操作,从而提高代码的可读性和可维护性

左移运算符重载(<<)

左移运算符(<<)用于输出信息到流对象中。我们可以重载左移运算符,使自定义类型也能够通过左移运算符进行输出操作。

成员函数重载
#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int v) : value(v) {}

    int getValue() const {
        return value;
    }

    MyClass& operator<<(std::ostream& os) const {
        os << "Value: " << value;
        return *this;
    }
};

int main() {
    MyClass obj(10);

    obj << std::cout << std::endl;  // 输出:Value: 10

    return 0;
}

在这个示例中,我们在 MyClass 类中定义了一个成员函数 operator<< ,该函数接受一个类型为 std::ostream& 的输出流对象 os 作为参数,并返回一个 MyClass& 类型的引用。

在函数定义中,我们将要输出的信息以指定的格式写入到给定的流对象 os 中。在这个示例中,我们将 value 的值输出到流对象中。

然后,在 main 函数中,我们创建了一个 MyClass 对象 obj ,并通过调用 obj << std::cout 来输出对象的值到标准输出流。

这样,我们就可以使用成员函数方式重载左移运算符(<<)来输出 MyClass 对象。

非成员函数重载
#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int v) : value(v) {}

    int getValue() const {
        return value;
    }

    friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
};

std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
    os << "Value: " << obj.value;
    return os;
}

int main() {
    MyClass obj(10);

    std::cout << obj << std::endl;  // 输出:Value: 10

    return 0;
}

在上述示例中,我们将左移运算符(<<)重载为非成员函数。重载函数接受两个参数,第一个参数是要输出到的流对象(如std::cout),第二个参数是要输出的MyClass对象。在函数体内,我们使用流对象将信息输出到标准输出流,并返回流对象本身。

链式编程(Chaining)

是一种编程风格,它允许将多个操作或方法调用连接在一起,形成一个连续的调用链。每个操作都会在原始对象上产生一个修改或转换,并返回修改后的对象,以便可以继续对其进行下一个操作。例如:std::cout << obj << std::endl;

递增运算符重载(++)

通过重载递增运算符,可以定义自定义类在使用递增运算符时的行为。

前置递增运算符(++obj)

前置递增运算符返回递增后的对象引用。它会先递增对象的值,然后返回递增后的对象引用。

MyClass& operator++() {
    ++value;
    return *this;
}

使用前置递增运算符时,对象的值会在表达式求值之前递增,并且递增后的对象会作为结果直接使用。

后置递增运算符(obj++)

后置递增运算符返回递增前的对象副本。它会先创建一个临时副本,然后递增对象的值,最后返回临时副本。

MyClass operator++(int) {
    MyClass temp(*this);
    ++value;
    return temp;
}

使用后置递增运算符时,对象的值会在表达式求值后递增,返回的结果是递增前的对象副本

占位参数

对于后置递增运算符的重载,占位参数 (int) 的作用就是用来区分前置递增和后置递增。在实际使用时,并不需要传递实际的参数值给这个占位参数。占位参数 (int) 的目的是为了让编译器能够识别并调用正确版本的递增运算符函数。当编译器看到递增运算符被用在对象之后时,它知道应该调用后置递增运算符函数,并且这个函数的参数列表中应该有一个 int 类型的占位参数。因为在后置递增运算符的实现中,我们并不需要使用这个参数的实际值,所以传递任何具体的参数值都是多余的。只要保证参数列表中有一个类型为 int 的参数即可。通过这种方式,编译器可以根据调用方式准确地选择对应的递增运算符函数,而无需传递实际的值给占位参数。

赋值运算符重载(=)

赋值运算符(=)用于将一个对象的值或状态复制给另一个对象。通过重载赋值运算符,可以定义自定义类在使用赋值运算符时的行为,从而实现对象之间的成员变量的逐个赋值或深拷贝。

#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int v = 0) : value(v) {}

    int getValue() const {
        return value;
    }

    // 赋值运算符重载
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            value = other.value;
        }
        return *this;
    }
};

int main() {
    MyClass obj1(5);
    MyClass obj2;

    std::cout << "obj1 值: " << obj1.getValue() << std::endl;
    std::cout << "obj2 值: " << obj2.getValue() << std::endl;

    obj2 = obj1; // 使用赋值运算符将 obj1 的值赋给 obj2

    std::cout << "赋值后 obj2 值: " << obj2.getValue() << std::endl;

    return 0;
}

在上面的示例中,我们定义了一个 MyClass 类,并重载了赋值运算符(=)。赋值运算符接受一个类型相同的对象作为参数,并将其成员变量的值复制给当前对象。在重载函数内部,我们首先检查对象是否与自身相同,以防止自我赋值。然后,我们将参数对象的值复制给当前对象。在 main 函数中,我们创建了两个 MyClass 对象 obj1obj2,并展示了使用赋值运算符将 obj1 的值赋给 obj2 的过程和结果。

关系运算符重载

关系运算符(比如:==、!=、<、>、<=、>=)用于比较两个对象之间的关系,并返回一个布尔值(true或false)。通过重载关系运算符,你可以定义自定义类在进行比较操作时的行为,从而实现对象之间的自定义比较规则。

#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int v = 0) : value(v) {}

    int getValue() const {
        return value;
    }

    // 相等运算符(==)重载
    bool operator==(const MyClass& other) const {
        return value == other.value;
    }

    // 小于运算符(<)重载
    bool operator<(const MyClass& other) const {
        return value < other.value;
    }
};

int main() {
    MyClass obj1(5);
    MyClass obj2(10);

    if (obj1 == obj2) {
        std::cout << "obj1 和 obj2 相等" << std::endl;
    } else {
        std::cout << "obj1 和 obj2 不相等" << std::endl;
    }

    if (obj1 < obj2) {
        std::cout << "obj1 小于 obj2" << std::endl;
    } else {
        std::cout << "obj1 不小于 obj2" << std::endl;
    }

    return 0;
}

在上面的示例中,我们定义了一个 MyClass 类,并重载了相等运算符(==)和小于运算符(<)。

相等运算符接受一个类型相同的对象作为参数,并比较对象的成员变量是否相等。我们在重载函数中通过比较 value 成员变量的值来实现相等比较。

小于运算符同样接受一个类型相同的对象作为参数,并比较对象的成员变量的大小关系。我们在重载函数中通过比较 value 成员变量的值来实现小于比较。

函数调用运算符()重载

函数调用运算符(())可以用于将类对象像函数一样调用,从而实现类的对象实例的可调用行为。

通过重载函数调用运算符,你可以定义自定义类在使用函数调用运算符调用对象时的行为,从而使类对象能够像函数一样执行操作。

  • 函数调用运算符 () 也可以重载

  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数

  • 仿函数没有固定写法,非常灵活

#include <iostream>

class MyClass {
public:
    void operator()(int value) const {
        std::cout << "调用 MyClass 对象,传入参数: " << value << std::endl;
    }
};

int main() {
    MyClass obj;

    obj(10); // 使用函数调用运算符调用 MyClass 对象

    return 0;
}

在上面的示例中,我们定义了一个 MyClass 类,并重载了函数调用运算符(())。通过在类中定义一个名为 operator() 的函数成员,我们使得 MyClass 对象能够像函数一样被调用。在重载函数中,我们可以实现自定义的操作。在这个示例中,我们简单地打印了传入的参数值。在 main 函数中,我们创建了一个 MyClass 对象 obj然后使用函数调用运算符将该对象像函数一样调用,并传递一个整数参数。

仿函数

仿函数又称为函数对象,是一种能够行使函数功能的类,该类重载了operator()运算符,调用仿函数的时候实际上就是通过类对象调用重载后的operator操作符,重载operator()和重载普通的函数效果相同,当参数类型不同时会执行不同的代码逻辑。它可以像函数一样被调用。与普通函数不同,仿函数可以存储状态并具有行为。

#include <iostream>

class Adder {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    Adder add;

    int result = add(5, 3); // 使用仿函数调用

    std::cout << "结果: " << result << std::endl;

    return 0;
}

在上面的示例中,我们定义了一个名为 Adder 的类,并重载了函数调用运算符(())。通过在类中定义名为 operator() 的函数成员,我们将 Adder 对象变成了一个仿函数。在重载函数中,我们实现了将两个整数相加的操作。在 main 函数中,我们创建了一个 Adder 对象 add,然后使用该对象进行函数调用,传递两个整数参数。这样就实现了仿函数的行为,使得 add 对象像一个函数一样执行加法操作。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值