cpp自学 day17(运算符重载)

 运算符重载

概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

一、加号运算符重载

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

  • 通过成员函数重载+号

Person operator+ (Person &p)
{
    Person temp;
    temp.m_A = this->m_A + p.m_A;
    temp.m_B = this->m_B + p.m_B;
    return temp;
}

本质 : Person p3 = p1.operator+(p2);

简化为 : Person p3 = p1 + p2;
  • 通过全局函数重载+号

Person operator+ ( Person &p1 , Person &p2)
{
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}

本质 : Person p3 = operator+ (p1,p2)

简化为 : person p3 = p1 + p2;

二、左移运算符重载

作用:可以输出自定义的数据类型

  • 全局函数重载左移运算符

(只能能利用全局函数重载左移运算符)

 void operator<<(ostream &cout , Person &p) 
{                                          本质 operator<<(cout , p) 简化 cout << p
    cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
}

void test01()
{
    Person p;
    p.m_A = 10;
    p.m_B = 10;

    cout << p;
}

上例中,cout属于ostream类型(输出流),由于对象只能有一个,故采用引用的方式传递

ostream & operator<<(ostream &cout , Person &p) 
{                                     //本质 operator<<(cout , p) 简化 cout << p << endl
    cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
    return cout;   //★
}

void test01()
{
    Person p;
    p.m_A = 10;
    p.m_B = 10;

    cout << p <<endl;
}

cout << p <<endl  这个语句属于于链式编程思想,只有返回值是cout才能够继续在后面追加输入,故我们需要将全局函数返回值为cout的类型,即ostream。

相当与调用函数 operator(cout,p),如果没有返回值 operator( ,endl)第二个调用函数少一个对象cout

  • 为什么无法用成员函数重载左移运算符(<<

无法实现cout在左侧

假设我们有一个Person类,我们想要重载<<运算符来打印出人的名字和年龄。

尝试使用成员函数来重载<<运算符

#include <iostream>
using namespace std;

class Person {
public:
    string name;
    int age;

    // 尝试使用成员函数重载 <<
    void operator<<(ostream& os) {
        os << "Name: " << name << ", Age: " << age;
    }
};

int main() {
    Person p;
    p.name = "Alice";
    p.age = 30;

    // 这里会报错,因为成员函数无法这样调用
    cout << p; 
    return 0;
}

在上面的代码中,我们尝试通过成员函数重载<<运算符。但是,当我们尝试调用cout << p;时,编译器会报错。这是因为成员函数需要通过对象来调用,而cout << p;这样的调用方式实际上是在寻找一个全局函数,而不是成员函数。


三、递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据      

准备知识

前置加

int a = 10;

cout << ++a <<endl; // 11
cout << a <<endl; // 11

后置加

int b = 10;

cout << b++ <<endl; // 10
cout << b <<endl; // 11

目标

MyInteger myint;
(相当于int  a ;)

cout << myint <<endl; // 0

cout << ++myint << endl; // 1
cout << myint++ <<endl; // 1
cout << myint <<endl; // 2

实现

class MyInteger {
    friend ostream& operator<<(ostream& out, MyInteger myint);

public:
    MyInteger() {
        m_Num = 0;
    }

    // 前置++
    MyInteger& operator++() {
        // 先++
        m_Num++;
        // 再返回
        return *this;
    }

    // 后置++
    MyInteger operator++(int) {
        // 先返回
        MyInteger temp = *this; 

    // 记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++

        m_Num++;
        return temp;
    }
private:
    int m_Num;
};

ostream& operator<<(ostream& out, MyInteger myint) {
    out << myint.m_Num;
    return out;
}

// 前置++ 先++ 再返回
void test01() {
    MyInteger myInt;
    cout << ++myInt << endl;
    cout << myInt << endl;
}

// 后置++ 先返回 再++
void test02() {
    MyInteger myInt;
    cout << myInt++ << endl;
    cout << myInt << endl;
}

为什么前置用了引用而后置没有用?

前置递增运算符使用引用是为了支持链式调用,而后置递增运算符返回副本是为了保留递增操作之前的对象状态。


四、赋值运算符重载

C++编译器至少给一个类添加4个函数

1. 默认构造函数(无参,函数体为空)

2. 默认析构函数(无参,函数体为空)

3. 默认拷贝构造函数,对属性进行值拷贝

4. 赋值运算符 operator=,对属性进行值拷贝

前三点在之前的帖子已经细讲过,此处我们着重看第四点

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题(析构函数重复释放)

由于编译器默认的赋值运算符“=”是浅拷贝,若我们类中有属性指向堆区,在赋值操作时会出现深浅拷贝的问题,此时我们就需要重新创造一种赋值方式,即赋值运算符重载

Person& operator=(Person &p)
{
    / 编译器是提供浅拷贝
    / m_Age = p.m_Age;

    / 应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
    if (m_Age != NULL)
    {
        delete m_Age;
        m_Age = NULL;
    }
    // 深拷贝
    m_Age = new int(*p.m_Age);

    // 返回对象本身
    return *this;
}


五、关系运算符重载

作用:重载关系运算符,可让两个自定义类型对象进行对比操作       

class Person
{
public:
    / 构造函数,初始化姓名和年龄
    Person(string name, int age)
    {
        this->m_Name = name;
        this->m_Age = age;
    };

    / 重载等于运算符
    bool operator==(Person & p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return true;
        }
        else
            return false;
    }
    
    / 重载不等于运算符
    bool operator!=(Person & p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

private:
    string m_Name; // 姓名
    int m_Age;     // 年龄
};

此处比较运算符被定义为成员函数,全局函数(也称为非成员函数)通常用于重载那些不能作为成员函数实现的运算符


六、函数调用运算符重载

  • 函数调用运算符"()"也可以重载  [重载小括号]
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活 (可以在小括号里传string也可以传int)

class Adder {
public:
    / 重载函数调用运算符
    int operator()(int a, int b) {
        return a + b; / 返回两个数的和
    }
};

int main() {
    Adder add; / 创建Adder对象
    int result = add(3, 4); / 使用重载的()运算符调用对象,执行加法
    cout << "Result: " << result << endl; / 输出结果
    return 0;
}

函数调用运算符重载在后续STL用的比较多,现在仅供了解


运算符重载总结

何时使用全局函数重载运算符以及何时使用成员函数重载运算符

运算符类型全局函数重载成员函数重载备注
一元运算符operator++(前置和后置)和operator!
二元运算符operator+operator-,当操作数之一不是类类型时使用全局函数
输出运算符operator<< 用于输出流
输入运算符operator>> 用于输入流
下标运算符operator[] 用于访问数组或容器元素
函数调用运算符operator() 用于类实例的函数调用
赋值运算符operator= 通常作为成员函数实现
比较运算符operator==operator!=,通常作为成员函数实现,除非需要比较类类型与非类类型
类型转换运算符operator int,用于类实例到基本数据类型的转换

代码案例

一、加号运算符重载

// 成员函数重载(推荐在操作数类型固定时使用)
Person operator+(const Person& p) const {  // 添加const修饰
    Person temp;
    temp.m_A = this->m_A + p.m_A;
    temp.m_B = this->m_B + p.m_B;
    return temp;
}
// 本质调用:Person p3 = p1.operator+(p2)

// 全局函数重载(支持隐式类型转换)
Person operator+(const Person& p1, const Person& p2) {
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}
// 注意:若访问私有成员需声明为友元

二、左移运算符重载

// 全局函数重载(必须返回ostream&以支持链式调用)
ostream& operator<<(ostream& cout, const Person& p) {  // 参数添加const
    cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
    return cout;  // 返回引用以实现链式调用
}

// 若访问私有成员需在类内声明:
friend ostream& operator<<(ostream& cout, const Person& p);

// 错误示例:成员函数重载会导致调用方向相反
void operator<<(ostream& os) { ... }  // 实际调用方式为:p << cout

三、递增运算符重载

// 前置++(返回引用)
MyInteger& operator++() {
    m_Num++;
    return *this;
}

// 后置++(int占位符区分,返回const防止连续后置++)
const MyInteger operator++(int) {  // 添加const修饰
    MyInteger temp = *this;
    m_Num++;
    return temp;  // 返回旧值副本
}

四、赋值运算符重载

Person& operator=(const Person& p) {  // 参数应为const
    // 1. 防止自赋值
    if (this == &p) return *this;
    
    // 2. 释放原有资源
    if (m_Age != nullptr) {  // 建议使用nullptr
        delete m_Age;
        m_Age = nullptr;
    }
    
    // 3. 深拷贝
    m_Age = new int(*p.m_Age);
    
    return *this;
}

// 注意:需同时实现拷贝构造函数
Person(const Person& p) : m_Age(new int(*p.m_Age)) {}

五、关系运算符重载

bool operator==(const Person& p) const {  // 添加const
    return (m_Name == p.m_Name) && (m_Age == p.m_Age);
}

bool operator!=(const Person& p) const {
    return !(*this == p);  // 复用==运算符
}

六、函数调用运算符重载

class Adder {
public:
    // 可携带状态的仿函数
    int operator()(int a, int b) const {  // const版本
        return a + b + offset; 
    }
private:
    int offset = 5;  // 示例成员变量
};

// 应用场景:STL算法中的自定义行为
sort(vec.begin(), vec.end(), MyComparator());

活动发起人@小虚竹 想对你说:

这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!

提醒:在发布作品前,请将不需要的内容删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值