运算符重载

所谓运算符重载,就是将已有的运算符重新进行定义,赋予新的功能。

一、加号运算符重载

对于内置数据类型,编译器知道如何去运算。比如两个int类型的数据,它们两个是可以直接用加号运算符运算的。

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

但是如果我们想要实现自定义运算符的加法运算,直接使用“+”就不行了,这就要使用到运算符重载技术了。

一般情况下,如果我们想要实现两个自定义数据类型的相加,首先考虑的是构造出一个函数:

class Person
{
public:
    int m_A;
    int m_B;
};

Person personAdd(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;
}
  • 成员函数实现运算符重载
    C++为我们提供了一个关键字,operator 可以实现函数重载,语法和构造一个函数很像:
class Person
{
public:
    int m_A;
    int m_B;

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

int main(void)
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 20;
    Person p2;
    p2.m_A = 11;
    p2.m_B = 21;

    Person p3 = p1 + p2; //与p1.operator+(p2)等价
    cout << "p3.m_A = " << p3.m_A << endl; //21
    cout << "p3.m_B = " << p3.m_B << endl; //41

    Person p4 = p1.operator+(p2);
    cout << "p4.m_A = " << p4.m_A << endl; //21
    cout << "p4.m_B = " << p4.m_B << endl; //41

    return 0;
}
  • 全局函数实现运算符重载
    同样地,也可以使用全局函数实现重载,并且运算符重载也可发生函数重载。接下来的例子我们除了要用全局函数达到两个自定义类型“相加”的目的,还要实现自定义数据类型和内置数据类型(比如int)的相加。
class Person
{
public:
    int m_A;
    int m_B;
};

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 operator+(Person &p, int num)
{
    Person temp;
    temp.m_A = p.m_A + num;
    temp.m_B = p.m_B + num;
    return temp;
}

int main(void)
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 20;
    Person p2;
    p2.m_A = 11;
    p2.m_B = 21;

    Person p3 = p1 + p2;                   //与operator+(p1, p2)等价
    cout << "p3.m_A = " << p3.m_A << endl; //21
    cout << "p3.m_B = " << p3.m_B << endl; //41

    Person p4 = operator+(p1, p2);
    cout << "p4.m_A = " << p4.m_A << endl; //21
    cout << "p4.m_B = " << p4.m_B << endl; //41

    //运算符重载 函数重载
    Person p5 = p1 + 10;
    cout << "p5.m_A = " << p5.m_A << endl; //20
    cout << "p5.m_B = " << p5.m_B << endl; //30
    return 0;
}

对于以上例子,运算符重载本可以理解为一个函数名为“operator+”的函数在调用时候的简化版写法。例如:p1 + p2 就是p1.operator+(p2)的简化版本;也是operator+(p1+p2)的简化版本

二 、左移运算符的重载

所谓左移运算符,就是我们常见的cout后面的“<<”。我们现在要使用运算符重载实现cout输出自定义数据类型。但在这之前,我们先来重新认识一下cout。cout是一个对象,是使用标准输出流ostream创建出来的对象,并且这个对象只有一个。

有了以上例子我们很容易想到用成员函数实现它:

class Person
{
public:
    int m_A;
    int m_B;

    void operator<<(ostream &cout) 
    {
        cout << this->m_A << this->m_B << endl;
    }
};

但是这个成员函数的调用方法是p.operator<<(cout),简化后就变成了p<<cout,不是我们想要的cout<<p;所以要想实现,只能用全局函数。

class Person
{
public:
    int m_A;
    int m_B;
};

ostream& operator<<(ostream &cout, Person &p) //返回osteram的引用值,就是返回自身,符合链式编程的思想
{
    cout << p.m_A << " " << p.m_B << endl; //10 20
    return cout;
}

int main(void)
{
    Person p;
    p.m_A = 10;
    p.m_B = 20;
    cout << p << "hello world" << endl; //之后还可以继续追加<<输出字符串
    return 0;
}

一般情况下,会将成员属性设置为私有,但是可以利用友元技术,也就是将实现运算符重载的函数设置为友元,来实现访问私有属性的目的。

三、递增运算符的重载

所谓递增运算符,就是“++”,就是让一个变量自增一的操作,分为前置递增和后置递增。其实这两种递增的区别也很好记,前置递增就是“先+1,再运算”,后置递增是“先运算,后+1”。但是它们的最终结果都是使得原值增一。

int a = 10;
cout << a++<< endl; // 先输出a的值10,再+1
cout << a << endl;  //+1后变成了11;

int b = 10;
cout << ++b << endl; //先+1,再输出+1后的值11
cout << b << endl //11

下面我们来自定义一个“int类型”,并实现对它的自增操作。先来介绍一下思路,用成员函数来实现对++运算符的重载。但由于我们要对一个自定义的“int”类型进行操作,所以要先对<<运算符进行重载,使之可以输出我们自定义的数据类型。

重载分为前置++和后置++两个操作,所以还会发生函数重载,用来区分前置和后置。

  • 前置++
  1. 实现自增操作,再将自增后的值返回;
  2. 返回值为自身的引用值,因为要实现++(++a)的操作。
  • 后置++
  1. 先用局部变量记录当前的值,再将值自增一;
  2. 返回该局部变量。

对于“++”这种一元运算符,有前置和后置两种运算规则,所以在运算符重载的时候,会发生函数重载。可以用占位参数(int)来区分前值和后置:前置重载没有参数,后置重载的参数列表有一个(int)占位参数。

class MyInterger
{
    //重载左移运算符设置为友元,可以输出私有属性
    friend ostream &operator<<(ostream &cout, MyInterger _int);

public:
    MyInterger()
    {
        m_Num = 0;
    }

    //重载前置++运算符
    MyInterger& operator++() //返回引用值是为了可以实现 ++(++a)的操作
    {
        m_Num++;
        return *this;
    }

    //重载后置++运算符
    MyInterger operator++(int) //int为占位参数,可以实现函数重载,区分前置++还是后置++
    {
        //先记录当时的结果
        MyInterger temp = *this;
        //后递增
        m_Num++;
        //最后将记录作为结果返回
        return temp;
    }

private:
    int m_Num;
};

//重载左移运算符
ostream &operator<<(ostream &cout, MyInterger _int) //返回osteram的引用值,就是返回自身
{
    cout << _int.m_Num;
    return cout;
}

void test01()
{
    MyInterger _int;
    cout << _int++ << endl;
}

void test02()
{
    MyInterger _int;
    cout << ++_int << endl;  //1
    cout << ++(++_int) << endl; //3 
}
  • 发现与总结
  1. 只有前置运算符才有类似++(++a)的操作,而a++没有。也就是说“++a”是一个变量,而“a++”是一个常量,常量是不可以修改的。
  2. 这个案例在写的时候又出现了一个我无法解释的问题:为什么重载左移运算符函数的参数列表使用引用的方式传递一个对象,会导致在输出该对象的时候出现错误,而改为值传递就正常了呢?
ostream &operator<<(ostream &cout, MyInterger &_int);//引用传递,报错
void test01()
{
    MyInterger _int;
    cout << _int++ << endl;
}
*************************
ostream &operator<<(ostream &cout, MyInterger _int)//值传递,不报错
void test01()
{
    MyInterger _int;
    cout << _int++ << endl;
}

四、赋值运算符的重载

之前说过C++编译器至少给一个类添加四个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符operator=,对属性进行值拷贝

如果对象创建在堆区,赋值操作会造成深浅拷贝的问题。如以下例子所示,对象的属性开辟到堆区,用指针进行维护,并在析构函数中进行释放。但是进行值拷贝的时候,本质上是把指针复制了一份给另一个对象,这就导致了两个指针指向了同一块内存空间。第一个对象执行到析构函数的时候会释放堆区空间,但是第二个对象也会执行一次析构函数,再次释放空间,这就会导致内存重复释放的错误。

class Person
{
public:
    Person(int age)
    {
        m_age = new int(age); //在堆区开辟
    }
    int *m_age;
    ~Person()
    {
        if (m_age != NULL)
        {
            delete m_age;
            m_age = NULL;
        }
    }
};

void test01()
{
    Person p1(18);
    cout << "p1的年龄为:" << *p1.m_age << endl;
    Person p2(20);
    cout << "p2的年龄为:" << *p2.m_age << endl;
    p2 = p1; //赋值操作
}

int main(void)
{
    test01();
    return 0;
}

出现以上错误的原因主要是内部重载的=运算符实现的仅仅是值拷贝,我们可以对它进行重载,深拷贝。在类内部添加对应重载函数。 我们先来分析一下这个函数该怎么写。首先,最终的形式是 p2 = p1; 那么还原回成员函数的形式应该是p2.operator=(p1),所有要有一个Person类型的参数;其次,要判断传入的参数是否有原始的属性。如果有,要进行释放。最后,要实现p3 = p2 = p1这样的操作,要保证返回值是自己的引用。最终的成品如下:

    //=运算符重载
    Person& operator=(Person &p)
    {
        //应该先判断是否有属性在堆区,如果有先释放干净
        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)
    {
        m_Age = age;
        m_Name = name;
    }
    string m_Name;
    int m_Age;

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

void test01()
{
    Person p1("Tom", 18);
    Person p2("Tom", 18);
    if(p1 == p2)
    {
        cout << "p1 = p2!" << endl;
    }
    else
    {
        cout << "p1 != p2" << endl;
    }
}

六、函数调用运算符重载

这里要重载的函数调用运算符就是“()”,没有它,函数是调用不起来的。由于重载之后的使用方式非常像函数的调用,所以称之为仿函数。

class MyPrint
{
public:
    //()运算符重载
    void operator()(string test)
    {
        cout << test << endl;
    }
};

void test01()
{
    MyPrint mp;
    mp("hello world"); //和正常的函数调用非常像 称为仿函数
}
  • 仿函数是没有固定的写法的,很灵活,再来看一个例子。
class MyAdd
{
public:
    //()运算符重载
   int operator()(int num1, int num2)
    {
        return num1 + num2;
    }
};

void test01()
{
    MyAdd m_a;
    cout << m_a(10, 20) << endl; //和正常的函数调用非常像 称为仿函数
    //匿名函数对象
    cout<< MyAdd()(100,200) << endl;
}

其它的运算符重载都要考虑返回值、参数顺序、返回值。但是“()”这种运算符本身就很特殊,它的参数是写在内部的,所以就造成了这种灵活的写法。

这个例子里面还有一个要注意的地方,匿名函数对象。MyAdd()这条语句就是创建了一个匿名对象,这条语句执行完对象自动是释放的。而 MyAdd()(100,200)的意思就是创建一个匿名对象,并用它调用重载的()运算符。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 技术交流QQ群:1027579432,欢迎你的加入! 1.Cpp中的重载运算符和重载函数 C++允许在同一作用域中...
    CurryCoder阅读 597评论 0赞 1
  • 基本上我们进行运算符重载时有两种形式,类内的运算符重载和顶层函数位置的运算符重载。 操作符重载指的是将C++提供的...
    飞扬code阅读 1,816评论 0赞 4
  • 运算符重载 1 运算符重载基本概念 运算符重载,就是对已有的运算符重新进行定义,赋予[http://baike.b...
    蒋斌文阅读 647评论 0赞 0
  • C++运算符重载-下篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 1,576评论 0赞 49
  • 对于内置数据类型,编译器知道如何做运算,编译器不知道如何让两个类进行运算 如果向让自定义数据类型 进行+法运算,...
    紫诺不离阅读 213评论 0赞 0

友情链接更多精彩内容