zl程序教程

您现在的位置是:首页 >  其他

当前栏目

C++面向对象编程之七:重载操作符(++,--,[],->,())

2023-04-18 14:11:19 时间

本文是上一篇博文:C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=)的延续,需要比较完整的了解C++面向对象编程:重载操作符,可以先浏览C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=),浏览过的博友忽略这段话即可。

接着上一篇博文,假设我们有一个MyInt类,实现的功能跟内置的类型int一样。那么下面我们为这个MyInt类实现一下相关的操作符重载吧

重载操作符++,--

1.重载为成员函数还是非成员函数?

对于++,--操作符而言,C++没有规定重载为成员函数还是非成员函数,但++,--操作会改变非共有成员变量的属性,如果重载为非成员函数,需要设置该非成员函数为类的友元,这破坏了类的封装性,所以对于++,--操作符而言,更建议重载为类的成员函数

2.形参

++操作有前++和后++,--操作有前--和后--,为了区分它们,C++规定将后缀式操作符函数额外增加一个无用的int类型形参,这个额外无用的int类型形参,存在的唯一目的是,只是作为一个标记,将前缀操作符和后缀操作符区分开。当使用后缀式操作符(例如:后++,后--等)的时候,编译器将0传递给这个形参,作为这个形参的实参。

3.返回值类型

a.对于前缀操作符,例如:

int a = 1;
cout << "++++a = " << ++++a << endl; //打印结果:++++a = 3
cout << "a = " << a << endl; //打印结果:a = 3

所以,我们设计MyInt类时,前++,前--的返回值类型为MyInt&,因为假如返回值类型为MyInt,第3行的打印结果:a = 2,跟内置的类型不一致了。

b.对于后缀操作符,例如:

int b = 1;
cout << "b++" << b++ << endl; //打印结果:b = 1
cout << "b = " << b << endl; //打印结果:b =2
//cout << "b++++" << b++++ << endl; //错误:C++是不允许后缀操作符链式操作的

所以,我们设计MyInt类时,后++,后--的返回值类型为const MyInt,因为假如返回值类型为MyInt&,第2行的打印结果:b = 2,跟内置类型不一致了。

#include <iostream>
using namespace std;

class MyInt
{
    friend ostream& operator<<(ostream &cout, const MyInt &a);
    friend istream& operator>>(istream &cin, MyInt &a);
    public:
    MyInt():m_num(0)
    {

    }
    MyInt(const int num):m_num(num)
    {

    }

    MyInt& operator=(const MyInt &b)
    {
        m_num = b.m_num;
        return *this;
    }

    MyInt& operator++()
    {
        ++m_num;
        return *this;
    }
    const MyInt operator++(int)
    {
        MyInt temp = *this;
        m_num++;
        return temp;
    }

    MyInt& operator--()
    {
        --m_num;
        return *this;
    }
    const MyInt operator--(int)
    {
        MyInt temp = *this;
        m_num--;
        return temp;
    }

    private:
    int m_num;
};

ostream& operator<<(ostream &cout, const MyInt &a)
{
    cout << a.m_num;
    return cout;
}

istream& operator>>(istream &cin, MyInt &a)
{
    cin >> a.m_num;
    return cin;
}

int main(int argc, char *argv[])
{
    MyInt a = 1;
    cout << "a = " << a << endl;
    cout << "++++a = " << ++++a << endl;
    cout << "a = " << a << endl;

    MyInt b = 1;
    cout << "b = " << b << endl;
    cout << "b++ = " << b++ << endl;
    cout << "b = " << b << endl;
    //cout << "b++++" << b++++ << endl; //错误:后缀操作符不允许链式操作

    MyInt c = 1;
    cout << "c = " << c << endl;
    cout << "----c = " << ----c << endl;
    cout << "c = " << c << endl;

    MyInt d = 1;
    cout << "d = " << d << endl;
    cout << "d-- = " << d-- << endl;
    cout << "d = " << d << endl;
    //cout << "d---- = " << d---- << endl; //错误:后缀操作符不允许链式操作

    return 0;
}

重载下标操作符[]

假设我们要设计一个IntArray类,并为这个类重载下标操作符[]

1.重载为成员函数还是非成员函数?

C++规定,重载下标操作符[]必须为类的成员函数。这是为什么呢?对于重载操作符为全局函数和重载操作符为类的成员函数,形参的个数看上去是有区别的,一般重载操作符为类的成员函数,其形参个数看起来比重载操作符为全局函数的形参个数少1个,实际上,重载操作符为类的成员函数,这个成员函数有一个隐含的this指针形参,限定为操作符的第一个操作数,所以对于编译器而言。形参个数并没有少1个的。而重载操作符为全局函数,这个全局函数的第一个形参则为操作符的第一个操作数。所以将重载下标操作符为类的成员函数,那么第一个形参是隐含的this指针,第二个形参是下标索引。是跟内置类型,例如:int arr[10] = {0};cout << arr[9] << endl; //第一个形参的实参为arr,第二个形参的实参为下标9,这样的命名规范是一致的。但如果C++没有规定重载下标操作符[]必须为类的成员函数,那么我们要设计一个IntArray类,并为这个类重载下标操作符[],我们完全可以为这个IntArray类重载下标操作符[]为全局函数,定义成这个全局函数的第一个形参为下标索引,第二个形参为类名的引用,即:IntArray& operator[](const int index, IntArray&);函数体内我们同样可以实现功能,我们用函数调用法:IntArray arr[10] = {0}; cout << operator(9, arr) << endl; 这样我们同样可以取出数组中对应下标的值,看起来也没有错。但简化的调用版本为:9[arr];这就是错误的了,所以C++规定,重载下标操作符[]必须为类的成员函数

2.形参

因为重载下标操作符[]必须为类的成员函数,第一个形参是隐含形参,即为成员函数内隐含的this指针,第二个形参是下标索引,定义为:const int index

3.返回值类型

//情况1
int arr1[3] = {1, 2, 3}; //左值
int a = arr1[0]; //右值
arr1[2] = 5;

//情况二
const int arr2[3] = {10, 20, 30}; //常量数组
cout << arr2[0] << endl; //可以通过下标正常访问常量数组
arr2[2] = 5; //错误:常量对象的值是不允许被修改的

a.为了适应第一种情况,重载下标操作符可以做左值,也可以做右值的特性。所以返回值类型为:int&,并且应该将该重载函数重载为普通成员函数,因为重载下标操作符做左值时,是允许修改值的,例如:arr[2] = 5

b.为了适应第二种情况,对于常量数组对象,常量对象只能调用常函数,并且常函数是不能修改成员变量的属性的,所以应该将重载函数重载为常成员函数。并且常量对象的值是不允许被修改的,所以返回值类型为:const int&

综上所述:我们重载下标操作符[]时,应该重载两个版本,如下:

int& operator[](const int index); //适用于重载下标操作符做左值或右值时

const int& operator[](const int index) const; //适用于常量对象使用下标操作符读对应下标的数据时

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

class IntArray
{
    public:
    IntArray(const int length)
    {
        assert(length > 0);

        m_length = length;
        mp_arr = new int[length]();
    }
    IntArray(const IntArray &intArr)
    {
        m_length = intArr.m_length;
        mp_arr = new int[m_length]();
        for (int i = 0; i < m_length; i++)
        {
            mp_arr[i] = intArr.mp_arr[i];
        }
    }

    ~IntArray()
    {
        delete [] mp_arr;
        mp_arr = NULL;
    }

    int& operator[](const int index)
    {
        return mp_arr[index];
    }
    const int& operator[](const int index) const
    {
        return mp_arr[index];
    }

    private:
    int m_length;
    int *mp_arr;
};

int main(int argc, char *argv[])
{
    IntArray arr(3);
    for (int i = 0; i < 3; i++)
    {
        cout << "arr[" << i << "] = " << arr[i] << endl;
        arr[i] = i;
    }
    cout << endl;

    for (int i = 0; i < 3; i++)
    {
        cout << "arr[" << i << "] = " << arr[i] << endl;
    }

    return 0;
}

重载箭头操作符->

1.重载为成员函数还是非成员函数?

C++规定,重载箭头操作符->必须为类的成员函数

2.形参

因为重载箭头操作符->必须为类的成员函数,第一个形参是成员函数内隐含的this指针

3.返回值类型

返回指向对象的指针,看设计类的需求。

  1. 解引用操作符*和箭头操作符->通常用在实现智能指针的类中

example:通过智能指针,设计一个怪物类,这类怪物具有分身技能,可以1变为2,分身越多,给每只怪物额外加成的属性就越多,同时这类怪物具有魅惑功能,可以将另一种怪物魅惑后,成为自己的分身。

#include <iostream>
#include <unistd.h>
//#include <windows.h> //windows平台下屏蔽上一行代码,打开这一行代码
using namespace std;

#define BASE_BLOOD_ADD 100
#define BASE_MAGIC_ADD 50

class SmartPoint;
class AttrAdd
{
    friend class SmartPoint;
    public:
    AttrAdd(const int bloodAdd, const int magicAdd):m_bloodAdd(bloodAdd), m_magicAdd(magicAdd)
    {

    }

    int getBloodAdd() const
    {
        return m_bloodAdd;
    }
    int getMagicAdd() const
    {
        return m_magicAdd;
    }

    private:

    int m_bloodAdd; //血量加成值
    int m_magicAdd; //魔法加成值
};

class SmartPoint
{
    friend class Monster;
    public:
    void updateAttrAdd()
    {
        mp_attrAdd->m_bloodAdd = BASE_BLOOD_ADD * m_counter;
        mp_attrAdd->m_magicAdd = BASE_MAGIC_ADD * m_counter;
    }
    private:
    SmartPoint(AttrAdd* const p_attrAdd):m_counter(1), mp_attrAdd(p_attrAdd)
    {
        updateAttrAdd();
    }
    ~SmartPoint()
    {
        cout << "~SmartPoint()" << endl;
        delete mp_attrAdd;
    }

    int m_counter;
    AttrAdd* const mp_attrAdd; //根据自己复刻出来的怪物数量,给同属的怪物给与额外的属性加成值指针。
};

class Monster
{
    friend ostream& operator<<(ostream &cout, const Monster &m);

    public:
    Monster(AttrAdd* const p, const int monsterId, const string name, const int blood, const int magic):
    mp_attrAdd(new SmartPoint(p)), m_monsterId(monsterId), m_name(name), m_blood(blood), m_magic(magic)
    {

    }
    Monster(const Monster &m):mp_attrAdd(m.mp_attrAdd)
    {
        ++mp_attrAdd->m_counter;
        mp_attrAdd->updateAttrAdd();

        m_monsterId = m.m_monsterId;
        m_name = m.m_name;
        m_blood = m.m_blood;
        m_magic = m.m_magic;
    }

    ~Monster()
    {
        cout << "~Monster(): 怪物数量 = " << mp_attrAdd->m_counter << endl;
        --mp_attrAdd->m_counter;
        mp_attrAdd->updateAttrAdd();
        if (mp_attrAdd->m_counter == 0)
            delete mp_attrAdd;
    }

    Monster& operator=(const Monster &m)
    {
        //右值
        ++m.mp_attrAdd->m_counter;
        m.mp_attrAdd->updateAttrAdd();

        //左值
        --mp_attrAdd->m_counter;
        mp_attrAdd->updateAttrAdd();
        if (mp_attrAdd->m_counter == 0)
            delete mp_attrAdd;

        m_monsterId = m.m_monsterId;
        m_name = m.m_name;
        m_blood = m.m_blood;
        m_magic = m.m_magic;
        mp_attrAdd = m.mp_attrAdd;

        return *this;
    }

    AttrAdd& operator*() const //重载解引用操作符
    {
        return *mp_attrAdd->mp_attrAdd;
    }
    AttrAdd* operator->() const //重载箭头操作符->
    {
        return mp_attrAdd->mp_attrAdd;
    }

    int getMonsterId() const
    {
        return m_monsterId;
    }

    string getName() const
    {
        return m_name;
    }

    int getBlood() const
    {
        return m_blood + ((*this)->getBloodAdd());
    }
    int getMagic() const 
    {
        return m_magic + ((*this)->getMagicAdd());
    }
    private:
    int m_monsterId;    //怪物id
    string m_name;
    int m_blood; //血量
    int m_magic; //魔法
    SmartPoint* mp_attrAdd;
};

ostream& operator<<(ostream &cout, const Monster &m)
{
    cout << m.m_name << ",id = " << m.m_monsterId << ",基础血量 = " << m.m_blood << ",额外血量加成 = " << 
    m->getBloodAdd() << ",总血量 = " << m.getBlood() << ",基础魔法 = " << m.m_magic << ",额外魔法加成 = " 
    << m->getMagicAdd() << ",总魔法 = " << m.getMagic();
    return cout;
}

int main(int argc, char *argv[])
{
    string temp;
    Monster m1((new AttrAdd(0, 0)), 10001, "雪女", 10000, 1000);
    cout << "场景内出现了一只怪物:" << m1.getName() << endl;
    cout << m1 << endl;
    cout << "---------------------" << endl << endl;
    sleep(3);

    Monster m2(m1);
    cout << m2.getName() << " 触发了分身术技能,目前场景内有2只 " << m1.getName() << " 并且每只" << m1.getName() <<"属性都有加强" << endl;
    cout << m2 << endl;

    cout << "---------------------" << endl << endl;
    sleep(4);
    Monster m3(m2);
    cout << m3.getName() << " 触发了分身术技能,目前场景内有3只 " << m2.getName() << " 并且每只" << m1.getName() <<"属性都有加强" << endl;
    cout << m3 << endl;

    cout << "---------------------" << endl << endl;
    sleep(4);
    Monster m11 = Monster((new AttrAdd(0, 0)), 10002, "紫衣仙子", 10000, 1000);
    cout << "场景内出现了另一种怪物:" << m11.getName() << endl;
    cout << "目前场景内有两种怪物:有1只 " << m11.getName() << ",有3只 " << m3.getName() << endl;
    cout << m3 << endl;
    cout << m11 << endl;

    cout << "---------------------" << endl << endl;
    sleep(4);
    temp = m2.getName();
    m2 = m11;
    cout << m11.getName() << " 对 " << temp << " 发动了魅惑技能,把1只 " << temp << " 魅惑成了自己的分身," << 
    m11.getName() << " 属性加强," << temp << " 属性减弱" << endl;
    cout << "目前场景内有两种怪物:有2只 " << m11.getName() << ",有2只 " << m3.getName() << endl;
    cout << m1 << endl;
    cout << m11 << endl;

    cout << "---------------------" << endl << endl;
    sleep(5);
    temp = m1.getName();
    m1 = m11;
    m3= m11;
    cout << m11.getName() << " 对 " << temp << " 发送了魅惑技能,把剩余的2只 " << temp << " 魅惑成了自己的分身,"
    << m11.getName() << "属性加强," << temp << " 从场景内消失" << endl;
    cout << "目前场景内有一种怪物:有4只 " << m11.getName() << endl;
    cout << m11 << endl;
    cout << "---------------------" << endl << endl;

    return 0;
}

重载函数调用操作符()

1.重载为成员函数还是非成员函数

C++规定,重载函数调用操作符必须为类的成员函数,而且一个类可以定义函数调用操作符的多个版本,由形参的个数和参数类型进行区分。

2.形参

不确定,根据实际开发需求而定

3.返回值类型

不确定,根据实际开发需求而定

#include <iostream>
using namespace std;

class CallFunc
{
    public:
    void operator()(int a) //重载函数调用操作符()
    {
        cout << a << endl;
    }

    void operator()(int a, int b) //重载函数调用操作符()
    {
        cout << a << endl;
        cout << b << endl;
    }
};

int main(int argc, char *argv[])
{
    CallFunc print;
    int a = 1;
    int b = 2;
    print(a);
    print(b);
    print(a, b);
    return 0;
}

好了,C++重载操作符举例先写到这。下面总结一下

1.C++允许我们自定义的类类型重载操作符,从而让我们自定义的类类型也能像内置数据类型使用一样直观,进行加减乘除,比较大小等等。

2.重载操作符应该遵守跟内置的数据类型一样的使用习惯,重载操作符不改变操作符的优先级

3.大部分重载操作符可以为全局函数或类的成员函数,重载操作符为全局函数时,形参个数看起来比重载操作符为类的成员函数多一个,实际上,重载操作符为类的成员函数,有一个隐含的this指针形参,为操作符的第一个操作数。对于操作符简化调用来说,形参是有顺序的,所以我们在重载操作符时,应该考虑形参的顺序,让其适应简化调用,避免例如像出现9[]等等问题。

4.重载操作符为全局函数时,应该将全局函数设置为类的友元(friend)

5.必要时应该重载赋值操作符=,避免两个对象内部的指针指向同一片内存空间,导致出现悬垂指针的问题。

6.重载操作符操作符的本质是重载函数,例如:重载操作符为类的成员函数时,本质是调用了该类的成员函数。例如:MyInt c = a + b; //本质是:调用了MyInt类的成员函数,即 MyInt c = a.operator+(b);

7.不能的重载的操作符

.         :类的对象访问操作符
.*  ->*   :类的对象或或类的对象指针访问类中的函数指针操作符
::        :域操作符
?=        :条件操作符
sizeof    :长度操作符
#         :预处理操作符  

8.必须重载为类的成员函数的操作符

=    :赋值操作符
[]   :下标操作符
->   :箭头操作符
()   :函数调用操作符