C/C++ ———— 运算符重载

运算符重载

引言

对于内置数据类型,编译器知道如何进行运算,这里我们以两数之和为例:

int a =10;
int b =20;
int c =a+b;//c=30

上面的加法运算是毫无疑问可以运行的,但倘若如下呢:

class num
{
    public:
    	int m_a;
    	int m_b;
};

num n1;
n1.m_a=10;
n1.m_b=20;

num n2;
n2.m_a=15;
n2.m_b=25;

num n3;
n3=n1+n2;

这样编译器是无法解析并运行的,这里就运用到我们的运算符重载这个定义了

本篇文章将介绍几个重要的运算符重载以及他们的使用方法

1.加号运算符重载

引言中,其实我们已经初步抽象了解到了加号运算符重载,但不正确,接下来我们来学习一下如何正确使用它来达到我们想要的目标,

1)通过成员函数重载

关键函数:

num operator+(num & p)
{
    num temp;
    temp.m_a=this->m_a+p.m_a;
    temp.m_b=this->m_b+p.m_b;
    return temp;
}

我们只需在类内声明这一个函数,就可以达到我们想要的目标,完整代码如下:

#include<iostream>
#include<string>
using namespace std;
class num
{
public:
    int m_a;
    int m_b;
num operator+(num & p)
{
    num temp;
    temp.m_a=this->m_a+p.m_a;
    temp.m_b=this->m_b+p.m_b;
    return temp;
}
};

void test_01()
{
num n1;
n1.m_a=10;
n1.m_b=20;

num n2;
n2.m_a=15;
n2.m_b=25;

num n3;
n3=n1+n2;
cout <<"n3a:"<<n3.m_a<<"\n"<<"n3b:"<<n3.m_b<<endl;
}

int main()
{
    test_01();
    return 0;
}

本质上n3=n1+n2;调用其实就是

n3=n1.operator(n2);

结果如下:
在这里插入图片描述


2)通过全局函数重载


将函数放在外面,再传入一个对象参数

class num
{
public:
    int m_a;
    int m_b;

};
num operator+(num & p1,num&p2)
{
    num temp;
    temp.m_a=p1.m_a+p2.m_a;
    temp.m_b=p1.m_b+p2.m_b;
    return temp;
}

这里的调用就是

n3=operator(n1,n2);

2.左移运算符重载

在我们想达到下面代码想展示的效果时,如果按照这个写法肯定会出错,这是一个不规则输出,那么这一节我们就来讲解左移运算符重载

class num
{
public:
    int m_a;
    int m_b;
};
num n1;
n1.m_a=10;
n1.m_b=20;
cout<<n1;

1)通过全局函数重载

关键函数

ostream& operator<<(ostream &cout,num & p)
{   
    cout<<"n1_a="<<p.m_a<<"n1_b="<<p.m_b<<endl;
    return cout;
}

完整代码如下:

#include<iostream>
#include<string>
using namespace std;

class num
{
public:
    int m_a;
    int m_b;
    

};

ostream& operator<<(ostream &cout,num & p)
{   
    cout<<"n1_a="<<p.m_a<<"n1_b="<<p.m_b<<endl;
    return cout;
}

void test_01()
{
num n1;
n1.m_a=10;
n1.m_b=20;
cout<<n1<<endl;
}

int main()
{
    test_01();
    return 0;
}

结果如下:
在这里插入图片描述


是不是还有疑问?——“为什么这个没有成员函数重载?”

我们先看看代码

class num
{
public:
    int m_a;
    int m_b;
    void operator<<(ostream& cout)//抽象化
    {
        ...
    }
};

如果是这样,那么我们调用函数时需要

n1.operator<<(cout);
这样简化是      n1<<cout   看起来怪怪的,不规范
虽然有点绕,但是再多看看加号运算符重载可能会好理解一点

3.递增运算符重载

当我们想实现下面这个效果时,就需要运用到我们的递增运算符重载

class num
{
public:
    num()
    {
        m_a=10;
    }
    
private:
    int m_a;
};    
num n1;
cout<<++n1<<endl;//11
cout<<n1++<<endl;//11
cout<<n1<<endl;//12

1).重载前置++运算符

关键函数

num & operator++()
    {
        m_a++;
        return *this;
    }

这里还需要用到前一节的左移运算符重载,完整代码展示

#include<iostream>
#include<string>
using namespace std;

class num
{
    friend ostream& operator<<(ostream &cout,num & p);//友元
public:
    num()
    {
        m_a=10;
    }
    num & operator++()
    {
        ++m_a;
        return *this;
    }
private:
    int m_a;
};    

ostream& operator<<(ostream &cout,num & p)
{   
    cout<<p.m_a<<endl;
    return cout;
}

void test_01()
{
num n1;
cout<<++(++n1)<<endl;
cout<<n1;
}

int main()
{
    test_01();
    return 0;
}

输出如下:
在这里插入图片描述

这时候你是否会有一个疑问

为什么重载函数要返回引用?——在我们调用++后,如果不引用,返回的将会是一个新对象,以上面代码为例

//引用之后
cout<<++(++n1)//12
cout<<n1//12
    
//不引用
cout<<++(++n1)//12
cout<<n1//11

这是因为++n1之后返回的已经是一个新的对象,第二次++是对新对象的操作


2).重载后置++运算符

关键函数

num  operator++(int)//占位参数,用于区分前置和后置
    {
        num temp=*this;
        m_a++;
        return temp;
    }

这时候是不是肯定又有疑惑?——为什么这个又不用引用了啊!!

num& operator++() {
    m_a++;        // 先自增
    return *this; // 返回已经变的自己
}

如果是这样,再返回的时候已经++过了,达不到先输出后加的效果
接下来让我形象的来解释一下:

num operator++(int) {
    num temp = *this; // 🎭 给别人看的“原来的我”
    m_a++;            // 🤫 偷偷地自己+1
    return temp;      // 🪞 送出伪装的旧我
}

这段代码其实就是这个意思,我们用临时对象复制出一个还没变化的自己,实际上自己偷偷+1,最后给别人展示临时对象(也就是变化之前的自己)

完整代码如下:

#include<iostream>
#include<string>
using namespace std;

class num
{
    friend ostream& operator<<(ostream &cout,const num & p);
public:
    num()
    {
        m_a=10;
    }
    num  operator++(int)//占位参数,用于区分前置和后置
    {
        num temp=*this;
        m_a++;
        return temp;
    }
private:
    int m_a;
};    

ostream& operator<<(ostream &cout,const num & p)
{   
    cout<<p.m_a<<endl;
    return cout;
}

void test_01()
{
num n1;
cout<<n1++<<endl;
cout<<n1;
}

int main()
{
    test_01();
    return 0;
}

结果如下:

在这里插入图片描述


4.赋值运算符重载

当我们执行类似以下的赋值操作时:

class num {
public:
    int m_a;  //不用指针

    num(int value = 0) {
        m_a = value;  // 简单赋值
    }
};
num n1;
num n2;
n2 = n1;

此时赋值、拷贝都不会有任何问题;

编译器生成的默认赋值运算符和拷贝构造函数都没问题;

假如我们的构造函数中使用了 new 来动态分配内存:

class num {
public:
    int* m_a;
    num(int value) {
        m_a = new int(value);
    }
    ~num() {
        if (m_a != nullptr) {
            delete m_a;
            m_a = nullptr;
        }
    }
};

在上述类中,m_a 指向堆上的内存。当我们执行 n2 = n1; 时,n1.m_a 的地址被浅拷贝到了 n2.m_a,两个对象指向了同一块内存。

这样会带来严重问题:

  • n1n2 被销毁时(比如出了作用域),它们都会调用析构函数;
  • 析构函数中都会执行 delete m_a;,导致对同一块内存重复释放(double free)
  • 此外,如果一个对象修改了 *m_a 的值,也会影响另一个对象;
  • 这会造成 悬挂指针(dangling pointer)重复释放 的问题。

接下来就介绍本小节的赋值运算符重载

关键函数

num& operator=(const num&p)
    {
        if(m_a!=NULL)
        {
            delete m_a;
            m_a=NULL;    
        }
        m_a=new int (*p.m_a);
        return *this;
    }

完整代码

#include<iostream>
#include<string>
using namespace std;

class num
{
public:
    int * m_a;
    num(int value)
    {

       m_a=new int(value);

    }
    ~num()
    {
        if(m_a!=NULL)
        {
            delete m_a;
            m_a=NULL;
        }
    }

    num& operator=(const num&p)
    {
        if(m_a!=NULL)
        {
            delete m_a;
            m_a=NULL;    
        }
        m_a=new int (*p.m_a);
        return *this;
    }

};    

void test_01()
{
    num n1(10);
    num n2(20);
    num n3(32);
    n3=n2=n1;
    cout<<*n2.m_a<<endl;

}

int main()
{
    test_01();
    return 0;
}

输出结果如下:

在这里插入图片描述


5.关系函数重载

关键函数

bool operator==(const person &p)
    {
        if(p.m_age==this->m_age && p.m_name==this->m_name)
        {
            return true;
        }
        return false;

代码比较简单我直接展示完整函数

#include<iostream>
#include<string>
using namespace std;

class person
{
public:
    person(string name ,int age)
    {
        m_name=name;
        m_age=age;
    }
    bool operator==(const person &p)
    {
        if(p.m_age==this->m_age && p.m_name==this->m_name)
        {
            return true;
        }
        return false;
    }
string m_name;
int m_age;

};  

void test_01()
{
    person p1("张三",18);
    person p2("李四",19);
    if(p1==p2)
    {
        cout<<"equal"<<endl;
    }
    else
        cout<<"not equal"<<endl;
}

int main()
{
    test_01();
    return 0;
}

结果如下:
在这里插入图片描述


6.函数调用运算符重载

关键函数

void operator()(string test)
    {
        cout<<test<<endl;
    }

完整代码:

#include<iostream>
#include<string>
using namespace std;

class Myprint
{
public:
    void operator()(string test)
    {
        cout<<test<<endl;
    }
};

void test_01()
{
    Myprint p1;
    p1("我勒个骚刚");
}

int main()
{
    test_01();
    return 0;
}

直接函数也可以完成同样的效果

void test_02(string text)
{
    cout<<text<<endl;
}
test_02("woleig");

这么一看是不是函数重载使用起来非常像函数调用呢,所以我们称其位仿函数,它是非常灵活的,没有固定的写法,比如下面的代码,我们实现一个加法

class Myprint
{
public:
    int operator()(int a,int b)
    {
        return a+b;
    }
};
void test_01()
{
    Myprint p1;
    int num=p1(5,8);
    cout<<num<<endl;
    cout<<Myprint()(33,22)<<endl;//匿名对象调用
}

调用之后输出结果就为12和55


那么我们就先介绍到这里,感谢你的阅读,希望这篇文章对你有帮助😊

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值