• 【C++】运算符重载


    运算符重载

    引入

    像+、-、*、/ … 等操作符默认支持内置类型(如 intdoublelong…)的运算,但是对于自定义类型如果也想用这些操作符该怎么办呢?

    以下面的 Date 类为例:

    class Date {
    public:
        Date(int year = 2022, int month = 8, int day = 24)
            :_year(year)
            , _month(month)
            , _day(day)
        {}
        
        //析构函数和拷贝构造函数编译器默认生成的就可以
    
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main() {
        Date d1;
        d1 += 3;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    假如我想给 d1 加上三天,d1 加上后的结果表示的日期为 2022-8-27

    默认的 += 完不成这个任务,而想要实现这一点,c++则引入了运算符重载。


    概念和使用

    运算符重载是具有特殊函数名的函数,它可以定义成类的成员函数,也可以定义成全局的。

    函数名字为:关键字后面接需要重载的运算符符号(比如operator+=)

    函数原型:返回类型 + operator运算符 + 参数列表

    注意事项:

    1. 不能通过连接其他符号来创建新的操作符,比如operator@
    2. 重载操作符必须有一个自定义类型的操作数
    3. 用于内置类型的操作符,其含义不能改变,例如内置的整型 + ,不能将它重载成 - 的作用;
    4. 作为类的成员函数时,形参要比运算符的操作数少一个,因为还有一个默认的 this 指针,this 指针默认为操作符的第一个操作数
    5. .* 、::sizeof? :. 以上 5 个运算符不能重载。

    那么简单实现一下上面的 += 运算符重载:

    写成成员函数的形式:

    Date& operator+=(int day) {
        _day += day;
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4

    写成全局的形式:

    Date& operator+=(Date& d, int day) {
        d._day += day;
        return d;
    }
    
    • 1
    • 2
    • 3
    • 4

    注意上面只是极为粗略的实现了一下,进位什么的都没有考虑。

    对于这个运算符重载有几个点需要注意一下:

    1. 两个函数的返回值都是 Date& ,这是因为 += 的作用就是改变它本身。如果重载的运算符是 + 的话返回值类型就应为 Date ,并返回一个临时创建对象。
    2. 当写成全局函数的话会有一个问题,在类外面是访问不到私有成员的。粗暴一点的话,我可以将成员变量设置为共有,但这就破坏了封装性。这个问题的解决方案是将 operator+= 函数设置成 Date 类的一个友元函数,具体语法是在 Date 类内部的任意位置加上一句 friend Date& operator+=(Date& d, int day);

    赋值运算符重载

    概念

    类还有一个特殊的成员函数——赋值运算符重载函数。

    字面意思,这个函数实现的功能就是用一个对象给另一个对象赋值。

    注意:

    1. 赋值运算符重载函数是成员函数,参数类型就是类的类型;
    2. 赋值运算符重载完成的是两个已经存在的对象之间的赋值操作,如果是一个已经存在的对象给一个即将创造的对象赋值调用的是拷贝构造函数;
    3. 返回类型为引用类型,返回值是 *this
    4. 需要检查一下自己给自己赋值,但原则上是没有这样用的,可以加一句 if 判断;
    5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝,也就是浅拷贝。

    上面又提到了浅拷贝,相应的又要涉及到深拷贝。

    其实像 Date 类这种简单的类就是要完成浅拷贝,不需要我们自己写,而一旦涉及到深拷贝就需要我们自己完成了。


    深拷贝

    下面以模拟实现 string 类为例,讲解一下赋值运算符重载函数的深拷贝怎么写。

    类的部分声明如下:

    class string {
    public:
        string(const char* str = "") {
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_size + 1];
            strcpy(_str, str);
        }
        
        ~string() {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }
        
    private:
    	char* _str;
        int _Size;
        int _capacity;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    赋值运算符重载的深拷贝有两种写法。

    一种是我们可以先把原来的资源先清理掉,然后拷贝另一个对象的所有成员变量,实现起来就是下面这样:

    string& operator=(const string& s) {
        // 判断是否是自己给自己赋值
        if (&s != this) {
            delete[] _str;
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_size + 1];
            // 这里不用 strcpy 的原因是我们不知道 s 内部会不会有 '\0' 
            strncpy(_str, s, _size);
        }
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这是较为传统的写法。

    不过都什么年代了,还在写传统深拷贝。

    下面提供更简洁的一种写法:

    string& operator=(string s) {
        // c++库提供了一个全局的swap函数,可以按字节序实现两个变量的交换
        // swap前面加::表明这是调用全局的swap,防止与类内部可能定义的swap函数冲突
        ::swap(_str, s._str);
    	::swap(_size, s._size);
        ::swap(_capacity, s._capacity);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这就不多做解释了,一目了然。


    <<、>>运算符重载函数的简单实现

    我们在使用输入输入输出函数时不知有没有注意到一点,我们并没有表示要输入或输出对象的类型,但编译器都能自动识别出来。

    这实际上就调用了 << 和 >> 的运算符重载函数:

    image-20220824144027081

    image-20220824144133860

    还是那句话,库里提供的只能实现内置类型的输入输出,对于自定义类型的输出还是需要我们自己写。

    Date 类为例,我们写一下它的输入输出运算符重载。

    类的简单声明:

    class Date {
    public:
        Date(int year = 2022, int month = 8, int day = 24)
            :_year(year)
            , _month(month)
            , _day(day)
        {}
    
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main() {
        Date d;
        cout << d << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    首先明确一点,我们想要的是上面代码似的像输出内置类型一样输出自定义类型。

    简单粗略的理解一下 << :

    无论是输出自定义类型还是内置类型,都会去调用它的运算符重载函数 operator<<,它的返回类型为 ostream& ,所以可以实现连续输出。<< 是有两个操作数的,像我们使用最多的 cout ,它的类型就是 ostream,是 << 的第一个操作数,要输出的对象就是第二个操作数。

    而一旦我们把它的运算符重载函数定义成成员函数,那么 this 指针就是第一个操作数,实际使用起来就会是 d1 << cout; 这种形式。这并不是我们想要的。

    所以考虑到操作数的顺序问题,我们需要把它的运算符重载函数定义成全局的。而为了访问到类内部成员,还需要将它设置成友元函数

    不过这只是一种解决方案,如果是 string 类的话,我们还可以通过迭代器或**[]重载**的形式直接进行访问。

    那么输出运算符重载就可以写成:

    ostream& operator<< (ostream& out, Date& d) {
    	out << d._year << '-' << d.month << '-' << d.day;
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4

    相应的,输入运算符也可以写出来:

    istream& operator>> (istream& in, Date& d) {
    	in >> d._year >> d.month >> d.day;
    	return in;
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    ”戏说“ 交换机 与 路由器
    Appium和Android常用9种自动化测试框架对比有哪些优势?
    【云原生】Kubernetes----Rancher助力Kubernetes监控
    【Python零基础入门篇 · 20】:面向对象基础(类和对象)
    嵌入式分享合集51
    Scrum 四个会议的正确召开方式
    深度学习之CNN宫颈癌预测
    基于uniapp+vite4+vue3搭建跨端项目|uni-app+uview-plus模板
    西华师范大学 数学考研真题及解析(全收录) 考研 数学分析 708 高代代数 810 真题
    杭电oj 2032 杨辉三角 C语言
  • 原文地址:https://blog.csdn.net/Ye_Ming_OUC/article/details/126505416