首页 > 编程语言 >C++核心编程运算符的重载

C++核心编程运算符的重载

时间:2024-06-21 21:00:15浏览次数:29  
标签:函数 int value 运算符 C++ 重载 MyClass

C++核心编程运算符的重载

文章目录

运算符重载在C++等编程语言中是一种重要的特性,它允许程序员为已存在的运算符提供额外的或定制的行为,尤其是针对用户自定义类型(如类和结构体)。以下是几个关键原因,解释了为什么需要运算符重载:

  1. 提升代码的自然度和可读性:通过运算符重载,可以让自定义类型像内置类型一样使用熟悉的运算符。例如,如果你定义了一个复数类,重载加号运算符(+)可以让复数相加就像普通整数或浮点数相加一样自然,这使得代码更易于阅读和理解。

  2. 增强表达能力:运算符重载能够以紧凑的形式表达复杂的操作,避免了使用长而晦涩的函数名称。这样可以让代码更加简洁,减少编程时的认知负担。

  3. 保持一致性:对于用户自定义类型,如果不能重载运算符,那么在处理这些类型时,可能需要引入全新的方法或函数来完成类似内置类型的运算,这会破坏语言使用的一致性体验。

  4. 支持泛型编程:运算符重载是实现模板和泛型算法的关键,它允许算法以统一的方式处理多种类型,只要这些类型支持相应的运算符。

  5. 提高效率:在某些情况下,通过精心设计的运算符重载,可以避免不必要的对象复制,减少临时对象的创建,从而提升程序的运行效率。

  6. 适应面向对象编程:在面向对象编程中,类和对象经常需要进行比较、组合等操作,运算符重载使得这些操作可以直接利用运算符,而不是通过复杂的函数调用来实现,更加符合面向对象的设计理念。

运算符重载是实现代码高效、清晰、一致的重要机制,特别是在处理自定义类型时,它极大地增强了语言的表达能力和灵活性。

1.“+”运算符的重载

在C++中,加号运算符(+)的重载允许你自定义当加号应用于自定义类型时的行为。你可以通过两种方式重载加号运算符:作为成员函数或作为友元函数(全局函数)。下面是两种方式的概览和示例:

1.1 作为成员函数重载

当作为成员函数重载时,加号运算符接受一个参数,这个参数是你要与当前对象相加的对象。返回值通常是该操作的结果,通常是一个新对象或者引用。

class MyClass {
public:
    // 成员函数重载加号运算符
    MyClass operator+(const MyClass& other) {
        MyClass result;
        // 执行相加操作,例如:
        result.value = this->value + other.value;
        return result;
    }

private:
    int value;
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    MyClass sum = obj1 + obj2; // 使用重载的加号运算符
    // ...
}

示例2:

//
// Created by 86189 on 2024/6/10.
//
#include "iostream"
using namespace std;

class  Complex {
public:
    int real;
    int imag;
    Complex operator+(Complex &c2) const{
        Complex c3{};
       c3.real = this->real + c2.real;
       c3.imag = this->imag + c2.imag;
        return c3;
    }
};

int main() {
    Complex c1{}, c2{} , c3{};
    c1.real = 10;
    c1.imag = 20;
    c2.real = 5;
    c2.imag = 10;
    c3 = c1 + c2;
    cout << c3.real << endl;
    cout << c3.imag << endl;
    return 0;
}
1.2 作为全局函数重载

作为全局函数(友元函数)重载时,加号运算符接受两个参数,这两个参数都是要相加的对象。这种方式的好处是可以访问第一个对象的私有或保护成员。

class MyClass {
public:
    int value;

    // 声明为友元函数,可以在类外部定义
    friend MyClass operator+(const MyClass& lhs, const MyClass& rhs);
};

// 全局函数实现加号运算符重载
MyClass operator+(const MyClass& lhs, const MyClass& rhs) {
    MyClass result;
    result.value = lhs.value + rhs.value;
    return result;
}

int main() {
    MyClass obj1{10};
    MyClass obj2{20};
    MyClass sum = obj1 + obj2; // 同样使用重载的加号运算符
    // ...
}

示例2:

//
// Created by 86189 on 2024/6/10.
//
#include <iostream>
using namespace std;

class A {
public:
    int a;
    int b;
};
A operator+(A &a, A &b) {
    A c{};
    c.a = a.a + b.a;
    c.b = a.b + b.b;
    return c;
}

int main() {
    A a1{}, a2{}, a3{};
    a1.a = 1;
    a1.b = 2;
    a2.a = 3;
    a2.b = 4;
    a3 = a1 + a2;
    cout << a3.a << " " << a3.b << endl;
    return 0;
}

无论哪种方式,重载的加号运算符都应当遵循运算符的一般语义和预期行为,确保操作的直观性和代码的可读性。同时,需要注意的是,当涉及自增(++)、自减(--)、赋值(=)等运算符时,还需要考虑前置与后置版本以及可能需要的拷贝构造或移动构造等细节。

2."<<"运算符重载

在C++中,重载运算符是一种特殊函数,它用于给已有运算符提供自定义的行为,特别是针对用户自定义类型。左移运算符(<<)通常与输入/输出流(如std::cout)一起使用来进行数据的输出。但当你想要自定义类型也能支持这样的输出操作时,就需要重载这个运算符。

2.1为什么需要重载左移运算符

当你定义了一个新的类或结构体,并希望像基本数据类型那样方便地将其实例打印出来时,就需要重载<<运算符。这在调试、日志记录和用户界面显示等方面非常有用。

2.2如何重载左移运算符

以下是一个简单的例子,展示了如何为一个自定义的类MyClass重载左移运算符:

#include <iostream>

class MyClass {
public:
    int value;

    // 构造函数
    MyClass(int v) : value(v) {}

    // 左移运算符重载函数
    friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
};

// 实现左移运算符重载函数
std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
    os << "MyClass object with value: " << obj.value;
    return os;
}

int main() {
    MyClass obj(10);
    std::cout << obj << std::endl;  // 调用重载后的<<运算符输出obj的内容
    return 0;
}

在这个例子中,我们定义了一个MyClass,它有一个成员变量value。然后,我们声明了一个友元函数来重载<<运算符,该函数接受一个输出流对象(std::ostream&)和一个MyClass的常引用作为参数。在函数内部,我们将MyClass对象的value成员插入到输出流中。通过返回os,我们可以链式调用这个运算符。

2.3注意事项
  • 友元函数:在这里,我们将运算符重载函数声明为类的友元,以便它可以直接访问类的私有和保护成员。
  • 返回值:重载函数应该返回输出流对象的引用,这样就可以支持连续输出(例如 std::cout << obj1 << obj2;)。
  • const引用:传递对象作为const引用是为了避免复制,提高效率,并允许对const对象进行操作。

通过这种方式,你可以使自定义类型更自然、更方便地融入C++的标准I/O机制中。

重载的一般方式:

//
// Created by 86189 on 2024/6/11.
//
#include "iostream"
using namespace std;

class MyClass {
public:
    int a;
    int b;
    MyClass(int a, int b) {
        this->a = a;
        this->b = b;
    }
};
ostream &operator<<(ostream &out, MyClass &myClass) {
    out << myClass.a << " " << myClass.b;
    return out;
}

int main() {
    MyClass myClass(1, 2);
    cout << myClass;
    return 0;
}

一般情况下的全局函数重载,但这种方式只能访问公有的成员属性和方法(函数)。

3."++"运算符重载
3.1 前置递增运算符重载

前置递增运算符直接对对象进行修改并返回修改后的对象本身。通常,它的声明和定义如下:

class MyClass {
public:
    // 前置递增运算符重载
    MyClass& operator++() {
        // 在这里实现自增逻辑
        // 例如,如果MyClass代表一个数值,可以简单地增加其值
        ++value; // 假设value是类的一个成员变量
        return *this; // 返回当前对象的引用
    }
private:
    int value;
};
3.2后置递增运算符重载

后置递增运算符需要创建一个临时对象来保存自增前的状态,然后原对象再进行自增操作。C++通过接受一个额外的无关参数(通常是int类型的占位符)来区分前置和后置版本:

class MyClass {
public:
    // 后置递增运算符重载
    MyClass operator++(int) { // int参数是后置递增的标志
        MyClass temp(*this); // 创建临时对象保存当前状态
        ++(*this); // 调用前置递增运算符实现自增
        return temp; // 返回自增前的对象状态
    }
private:
    int value;
};
3.3注意事项
  • 确保递增运算符合乎预期,特别是对于复合数据类型或有特殊逻辑的对象。
  • 由于后置递增需要复制当前对象状态,因此在性能敏感的应用中应谨慎使用。
  • 保持运算符重载的一致性,即前置和后置版本在效果上应等价(除了返回类型和是否立即修改对象外)。

通过上述方式,就可以自定义类对象在使用递增运算符时的行为,使其更符合类的设计意图和使用场景。

递增运算符重载的实际应用:

#include <iostream>
using namespace std;

class Complex {
public:
    int value;
    explicit Complex(int value) {
        this->value = value;
    }
    Complex& operator++() {
        ++this->value;
        return *this;
    }
};

class Complex2 {
public:
    int value;
    explicit Complex2(int value) {
        this->value = value;
    }
    Complex2 operator++(int) {
        Complex2 temp(*this);
        this->value++;
        return temp;
    }
};
ostream &operator<<(ostream &out, const Complex c) {
    out << c.value;
    return out;
}
ostream &operator<<(ostream &out, const Complex2 c) {
    out << c.value;
    return out;
}
int main() {
    Complex c(1);
    cout << ++c << endl;
    cout << c << endl;
    Complex2 c2(1);
    cout << c2++ << endl;
    cout << c2 << endl;
    return 0;
}
4.赋值运算符重载

在C++中,赋值运算符(=, assignment operator)可以被重载,以自定义类对象之间赋值的行为。当你想要控制一个类实例如何将值赋给另一个类实例时,重载赋值运算符变得尤为重要。下面是一个简单的示例,展示了如何重载赋值运算符:

#include <iostream>

class MyClass {
public:
    // 成员变量
    int value;

    // 默认构造函数
    MyClass(int v = 0) : value(v) {}

    // 赋值运算符重载
    MyClass& operator=(const MyClass& other) {
        // 防止自我赋值
        if (this != &other) {
            // 实现赋值逻辑
            value = other.value;
        }
        // 返回当前对象的引用,支持连续赋值 (a = b = c)
        return *this;
    }
};

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

    std::cout << "Before assignment: obj1.value = " << obj1.value << ", obj2.value = " << obj2.value << std::endl;

    // 使用重载的赋值运算符
    obj2 = obj1;

    std::cout << "After assignment: obj1.value = " << obj1.value << ", obj2.value = " << obj2.value << std::endl;

    return 0;
}
4.1解释
  • 成员变量MyClass中有一个成员变量value
  • 默认构造函数:提供了默认构造方式,可以初始化value为0或指定的值。
  • 赋值运算符重载:定义了operator=函数,它接受一个对同类对象的引用other作为参数。
    • 自我赋值检查:通过比较this(当前对象地址)和&other(传入对象地址)来判断是否是自我赋值,这是为了避免在自我赋值情况下出现不必要的操作或错误。
    • 赋值逻辑:如果不是自我赋值,就将other.value的值赋予当前对象的value
    • 返回值:重载的赋值运算符通常返回一个对当前对象(*this)的引用,这样可以支持连续赋值操作。
4.2注意事项
  • 深拷贝与浅拷贝:当类中有指针或动态分配的资源时,必须小心处理深拷贝(复制资源的内容)与浅拷贝(只复制指针)的问题,以避免悬挂指针或资源泄露。
  • 复制与交换:有时使用“复制并交换”(copy-and-swap)技术来实现赋值运算符,这是一种既安全又高效的方法。
  • 三/五法则:如果重载了赋值运算符,一般也建议重载拷贝构造函数、析构函数、移动构造函数和移动赋值运算符,以保持类的完整性和资源管理的一致性。这被称为C++的“三/五法则”。
5.关系运算符的重载
5.1解释

在C++中,你可以重载关系运算符(如==, !=, <, >, <=, >=)来定义它们在自定义类型上的行为。重载关系运算符可以帮助你更直观地比较自定义类的实例。下面是一个简单的例子,展示了如何为一个简单的Point类重载==<运算符:

#include <iostream>

class Point {
public:
    int x, y;

    // 构造函数
    Point(int x = 0, int y = 0) : x(x), y(y) {}

    // 重载等于运算符
    bool operator==(const Point& other) const {
        return (x == other.x) && (y == other.y);
    }

    // 重载小于运算符
    bool operator<(const Point& other) const {
        if (y == other.y) {
            return x < other.x;
        } else {
            return y < other.y;
        }
    }
};

// 为了支持cout输出,重载<<运算符
std::ostream& operator<<(std::ostream& os, const Point& p) {
    os << "(" << p.x << ", " << p.y << ")";
    return os;
}

int main() {
    Point p1(1, 2);
    Point p2(1, 2);
    Point p3(2, 1);

    std::cout << "p1 == p2: " << (p1 == p2) << std::endl; // 应输出1,表示真
    std::cout << "p1 < p3: " << (p1 < p3) << std::endl;   // 应输出0,表示假,因为我们定义了先按y比较,后按x比较

    return 0;
}
5.2关键点
  • 重载函数:重载的关系运算符通常被声明为类的成员函数(对于非成员函数,需要定义为友元函数以访问私有成员),并且通常被声明为const,因为它们不应该修改对象的状态。
  • 返回类型:重载的关系运算符应该返回bool类型,表示比较的结果。
  • 逻辑一致性:当你重载一个关系运算符时,应确保所有相关运算符(如<>)的逻辑一致,以避免违反关系运算符的传递性、对称性等属性。
  • 友元函数:对于非成员函数形式的关系运算符重载(如==),通常将其声明为类的友元函数,以便直接访问类的私有和保护成员。

重载关系运算符可以让你的自定义类型更加自然地融入C++的标准表达式语法中,提高代码的可读性和易用性。

6.函数调用运算符重载

在C++中,直接重载圆括号()操作符通常是用来实现类的实例作为函数的调用,这种机制常用于仿函数(functor)、智能指针或者任何希望像函数一样被调用的类。重载圆括号操作符是通过在类中定义一个名为operator()的成员函数来实现的。

6.1示例

下面是一个简单的示例,展示了如何定义一个类来重载圆括号操作符,使其实例可以像函数一样被调用:

#include <iostream>

class Functor {
public:
    // 重载圆括号操作符
    int operator()(int x, int y) {
        return x + y; // 这个操作符现在像一个求和函数
    }
};

int main() {
    Functor adder; // 创建类的实例

    // 使用类实例像函数一样调用
    int result = adder(10, 20); 
    std::cout << "Result: " << result << std::endl; // 输出: Result: 30

    return 0;
}

在这个例子中,Functor类通过重载operator(),使得创建的adder对象可以像函数那样被调用,传入两个整数参数,并返回它们的和。

以及使用匿名对象调用:

#include <iostream>
using namespace std;

class myClass{
public:
    myClass(int a, int b) {
        this->a = a;
        this->b = b;
    }

    myClass(int a) {
        this->a = a;
        this->b = 0;
    }

    myClass() {
        this->a = 0;
        this->b = 0;
    }
    void operator()(const string& s)
    {
        cout << s << endl;
    }
    int operator()(int i, int j)
    {
        return i + j;
    }
private:
    int a;
    int b;
   };

int main() {
    myClass myClass1(1, 2);
    myClass1("hello");
    cout << myClass()(1, 2) << endl; //匿名对象调用
    return 0;
}
6.2注意事项
  • 返回类型operator()可以有任意合法的返回类型,根据实际需求定义。
  • 参数列表:圆括号内的参数列表也可以根据需要自由定义,就像普通函数的参数列表一样。
  • 多态性:重载的operator()可以实现多态行为,使得类的实例能够以统一的方式处理不同类型的输入。

通过重载圆括号操作符,C++提供了强大的灵活性,允许用户定义能够像函数一样被调用的对象,这对于函数对象(functors)、函数适配器、策略模式等设计模式非常有用。

标签:函数,int,value,运算符,C++,重载,MyClass
From: https://blog.csdn.net/weixin_73497355/article/details/139869699

相关文章

  • 2022年大作业参考报告-使用C++语言开发小学生成绩管理系统、中学生成绩管理系统、大学
    背景:目录第一章需求分析   21.1   问题描述   26.1   功能需求   26.2   开发环境   26.3   开发过程   2第二章概要设计   32.1   总体设计   32.2   类的定义   32.3   接口设计   52.4  ......
  • opencv入门-小白的学习笔记c++(1)
    注:以下是根据链接https://blog.csdn.net/Cream_Cicilian/article/details/105427752的小白学习过程。1加载、修改、保存图像1.1加载图像1.1.1加载图像cv::imread用于从文件中读取图像数据并将其存储到一个cv::Mat对象中,其中第一个参数表示图像文件名称第二个参数,表......
  • 0基础学C++ | 第03天 | 基础知识 |算术运算符 | 赋值运算符 | 比较运算符 | 逻辑运算
    前言前面已经讲了,数据类型以及求数据类型所占的空间0基础学C++|第02天|基础知识|sizeof关键字|浮点型|字符型|转义字符|字符串|布尔类型|数据的输入-CSDN博客,现在讲运算符算术运算符 作用:用于处理四则运算#include<iostream>usingnamespacestd;in......
  • String(C++)
    文章目录前言文档介绍经典题目讲解HJ1字符串最后一个单词的长度模拟实现框架构造函数析构函数迭代器c_str()赋值size()capacity()reserveempty()[]访问front/backpush_backappendoperator+=insert一个字符insert一个字符串eraseswapfind一个字符find一个字符串substr(......
  • Windows C++ 应用软件开发从入门到精通详解
    目录1、引言2、IDE开发环境介绍2.1、VisualStudio 2.2、QTCreator3、Windows平台实用小工具介绍3.1、代码编辑器VSCode3.2、代码查看编辑器SourceInsight3.3、文本编辑器Notepad++3.4、文件搜索工具Everything4、C++语言特性4.1、熟悉泛型编程4.2、了解......
  • 【C++】priority_queue的模拟实现与仿函数
    文章目录1.优先级队列的介绍与使用1.1介绍1.2使用2.模拟实现2.1push2.2pop2.3top、empty、size2.4迭代区间构造3.仿函数1.优先级队列的介绍与使用1.1介绍优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。......
  • 校招常见七大排序C++版(适合新人,通俗易懂)
    作者:求一个demo版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处内容通俗易懂,没有废话,文章最后是面试常问内容是否稳定最优、最坏、平均时间复杂度最优、最坏、平均空间复杂度冒泡排序是O(n)、O(n^2)、O(n^2)0、O(n)、O(1)选择排序否O(n^2)、O(n^2)......
  • C++矩阵库:Eigen 3.4.90 中文使用文档 (一)
    写在前面:我在学习Eigen库时,没找到好的中文文档,因此萌发了汉化Eigen官网文档的想法。其中一些翻译可能不是特别准确,欢迎批评指正。感兴趣的同学可以跳转到官网查看原文:Eigen:MainPagehttps://eigen.tuxfamily.org/dox/index.html       Eigen库,是一个开源的C......
  • C/C++ 缓冲区溢出问题总结
    缓冲区溢出(BufferOverflow)是一种常见的安全漏洞,它发生在当程序试图将更多的数据放入一个固定大小的内存区域(即缓冲区)时,超过了该区域所能容纳的数据量。这可能导致未定义的行为,包括数据损坏、程序崩溃,甚至更糟糕的是,攻击者可以利用这种漏洞执行恶意代码。一、缓冲区溢出概述缓冲......
  • C++ 面向对象高级开发 4、参数传递与返回值
    consructor构造函数:被放在private区ctors放在private区classA{public:staticA&getInsance();    setup(){...};private:A();    A(constA&rhs);};A&A::getInstance(){staticAa;    returna;}A::getInsance().s......