「地表最强」C++核心编程(八)类和对象--运算符重载

环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1


地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象----封装
「地表最强」C++核心编程(五)类和对象----对象初始化和清理
「地表最强」C++核心编程(六)类和对象----对象模型和this指针
「地表最强」C++核心编程(七)类和对象----友元
「地表最强」C++核心编程(八)类和对象----运算符重载
「地表最强」C++核心编程(九)类和对象----继承
「地表最强」C++核心编程(十)类和对象----多态
「地表最强」C++核心编程(十一)文件操作

对于内置的数据类型,编译器知道如何进行运算,但是不知道自定义数据类型如何运算。为了应对这种情况,可以对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型,这就是运算符重载。但要注意,对于内置的数据类型的表达式的的运算符是不可能改变的。

一、加号运算符重载

实现加号+的重载可以有两种方式:成员函数重载全局函数重载

1.1 成员函数实现+重载

class Person {
public:
    int m_A;
    int m_B;

public:
    Person() {};

    Person(int a, int b) {
        this->m_A = a;
        this->m_B = b;
    }

    //成员函数实现 + 号运算符重载
    Person operator+(const Person &p) {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }
};

void test() {
    Person p1(10, 10);
    Person p2(20, 20);

    //成员函数方式
    Person p3 = p2 + p1;  //右边本质是p2.operaor+(p1);  也就是p2 + p1相当于p2调用+函数,p1是参数
    cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;//30 30
}

1.2 全局函数实现+重载

//全局函数实现 + 号运算符重载
Person operator+(const Person &p1, const Person &p2) {
    Person temp(0, 0);
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}

void test() {
    Person p1(10, 10);
    Person p2(20, 20);

    //全局函数方式
    Person p3 = p1 + p2;//本质是 p3 = operator+(p1,p2);
    cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;//30  30

}

1.3 运算符重载可以进行函数重载

//运算符重载 可以发生函数重载
Person operator+(const Person &p2, int val) {
    Person temp;
    temp.m_A = p2.m_A + val;
    temp.m_B = p2.m_B + val;
    return temp;
}

void test() {
    Person p1(10, 10);

    //函数重载
    Person p4 = p1 + 10; //相当于 operator+(p3,10)
    cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;//20  20
}

二、左移运算符重载

重载左移运算符可以实现输出自定义数据类型,一般配合友元使用。
需要注意的是,左移运算符的重载一般不用成员函数实现而只用全局函数实现。

class Person {
    friend ostream& operator<<(ostream& out, Person& p);

private:
    int m_A;
    int m_B;

public:
    Person(int a, int b)
    {
        this->m_A = a;
        this->m_B = b;
    }

    //成员函数实现不了:若参数为Person& p,则调用的时候是p.operator<<(p),这不符合逻辑
    //              若参数为cout,则调用的时候是p.operator<<(cout),简化为p << cout;我们需要cout << p;显然这不是我们想要的效果
//    void operator<<(Person& p){//因此不用成员函数来实现重载<<
//    }
};

//全局函数实现左移重载
//ostream对象只能有一个,因此传引用
ostream& operator<<(ostream& out, Person& p) {//返回值不是void
    out << "a:" << p.m_A << " b:" << p.m_B;
    return out;
}

void test() {
    Person p1(10, 20);
    cout << p1 << "hello world" << endl; //若返回类型为void,此处报错,因为只有ostream对象才能继续使用这个重载
}

三、递增运算符重载

为了重载++,先来自定义一个整型类:

//自定义整型
class MyInteger {
private:
    int m_Num;

public:
    MyInteger() {
        m_Num = 0;
    }
};

3.1 前置递增重载

 //重载前置++
MyInteger& operator++() {//返回值是引用保证了一直对一个对象递增,否则多次++时,只有第一次是目标对象的操作,后续的每次++都是对新的复制品的操作
    //先++
    m_Num++;
    //再返回
    return *this;
}

3.2 后置递增重载

    //重载后置++:返回值不能是引用,因为返回局部变量的引用是非法的
    MyInteger operator++(int) {//int代表占位参数,可以用于区分前置和后置递增,只能用int
        //先返回
        MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
        m_Num++;
        return temp;//返回之前记录过的值
    }

3.3 重载<<来输出

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

private:
    int m_Num;

public:
    MyInteger() {
        m_Num = 0;
    }
    
    //重载前置++
    MyInteger& operator++() {//返回值是引用保证了一直对一个对象递增,否则多次++时,只有第一次是目标对象的操作,后续的每次++都是对新的复制品的操作
        //先++
        m_Num++;
        //再返回
        return *this;
    }

    //重载后置++:返回值不能是引用,因为返回局部变量的引用是非法的
    MyInteger operator++(int) {//int代表占位参数,可以用于区分前置和后置递增,只能用int
        //先返回
        MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
        m_Num++;
        return temp;//返回之前记录过的值
    }
};

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

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

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

四、赋值运算符重载

c++编译器至少给一个类添加4个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符 operator=, 对属性进行值拷贝,是浅拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
关于深浅拷贝,可以参考「地表最强」C++核心编程(五)类和对象----对象初始化和清理第五点。

class Person {
public:
    //年龄的指针
    int *m_Age;

    Person(int age) {
        //将年龄数据开辟到堆区
        m_Age = new int(age);
    }

    //重载赋值运算符
    Person& operator=(Person &p) {//注意返回值类型
        //如果自身已经有数据,先释放掉
        if (m_Age != NULL) {
            delete m_Age;
            m_Age = NULL;
        }
        //编译器提供的代码是浅拷贝
        //m_Age = p.m_Age;

        //提供深拷贝 解决浅拷贝的问题
        m_Age = new int(*p.m_Age);

        //返回自身
        return *this;
    }

    ~Person() {
        if (m_Age != NULL) {
            delete m_Age;//释放堆区数据
            m_Age = NULL;
        }
    }
};


void test01() {
    Person p1(18);
    Person p2(20);
    Person p3(30);

    p3 = p2 = p1; //赋值操作,返回值是Person&才可以这样操作,若返回值为void则不能连等

    cout << "p1的年龄为:" << *p1.m_Age << endl;//18
    cout << "p2的年龄为:" << *p2.m_Age << endl;//18
    cout << "p3的年龄为:" << *p3.m_Age << endl;//18
}

五、关系运算符重载

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

class Person {
public:
    string m_Name;
    int m_Age;

    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;
        }
    }
};

void test01() {
    Person a("孙悟空", 18);
    Person b("孙悟空", 18);

    if (a == b) {
        cout << "a和b相等" << endl;
    } else {
        cout << "a和b不相等" << endl;
    }

    if (a != b) {
        cout << "a和b不相等" << endl;
    } else {
        cout << "a和b相等" << endl;
    }
}

六、函数调用运算符重载

函数调用运算符 () 也可以重载,由于重载后使用的方式非常像函数的调用,因此称为仿函数。仿函数没有固定写法,非常灵活。

class MyPrint {
public:
    void operator()(string text) {//重载什么operator后边就写什么
        cout << text << endl;
    }
};

void test01() {
    //重载的()操作符 也称为仿函数
    MyPrint myFunc;
    myFunc("hello world");//对象使用重载后的(),不是函数调用
}

class MyAdd {
public:
    int operator()(int v1, int v2) {
        return v1 + v2;
    }
};

void test02() {
    MyAdd add;
    int ret = add(10, 10);
    cout << "ret = " << ret << endl;

    //匿名函数对象调用
    cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;//MyAdd()是匿名对象,调用了重载的()
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值