zoukankan      html  css  js  c++  java
  • 运算符重载(三)

    • 必须是成员函数,不能是友元函数。
    • 没有参数(操作数是什么?)。
    • 不能指定返回类型(其实已经指定了)。
    • 函数原型:operator 类型名();

    下面用代码来说明,先新建工程,将上次实现的Integer类添加至项目中来:

    Integer.h:

    #ifndef _INTEGER_H
    #define _INTEGER_H
    
    class Integer
    {
    public:
        Integer(int n);
        ~Integer();
        void display() const;
        //Integer& operator ++();//++运算符重载
        friend Integer& operator ++(Integer& i);//友元方式重载前置++
        friend Integer operator ++(Integer& i, int n);//友元方式重载后置++
    
        //Integer operator ++(int i);//后置++运算符重载,其中的参数并没啥用,只是为了和前置++形成重载
    private:
        int n_;
    };
    
    #endif    //_INTEGER_H

    Integer.cpp:

    #include "Integer.h"
    #include <iostream>
    using namespace std;
    
    Integer::Integer(int n)    : n_(n) {
    
    }
    
    Integer::~Integer()
    {
    }
    
    //Integer& Integer::operator ++() {
    //    ++n_;
    //    return *this;
    //}
    
    Integer& operator ++(Integer& i) {
        ++i.n_;
        return i;
    }
    
    //Integer Integer::operator ++(int i) {
    //    Integer tmp(n_);//构造一个临时对象
    //    n_++;//紧接着再来修改n_
    //    return tmp;//这时返回的是对象,而不是引用了,因为如果是引用,临时对象会被回收,则指向了一个无效的对象了
    //}
    
    Integer operator ++(Integer& i, int n) {
        Integer tmp(i.n_);//构造一个临时对象
        i.n_++;//紧接着再来修改n_
        return tmp;//这时返回的是对象,而不是引用了,因为如果是引用,临时对象会被回收,则指向了一个无效的对象了
    }
    
    void Integer::display() const{
        cout<<n_<<endl;
    }

    测试代码:

    #include "Integer.h"
    #include <iostream>
    using namespace std;
    
    int main(void) {
        Integer n(100);
        n = 200;//可以将一个整型转换成类类型,因为实现了转换构造函数
        n.display();
        return 0;
    }

    上面的输出的写法及输出结果显而易见,就不多说了,接下来实现加法运算的函数:

    很遗憾,这样的写法目前还不支持:

    这时需要重写类型转换运算符了,下面来实现一下:

    再次编译运行:

    实际上用一个更加直观的例子更能看明白:

    结果:

    这是隐式转换,其实也可以使用显示转换:

     直接来代码,这里为了方便直接将类写在main函数文件中,举一个数据库相关的例子: 

    #include <iostream>
    using namespace std;
    
    class DB {
    public:
        DB() {
            cout<<"DB ..."<<endl;
        }
    
        ~DB() {
            cout<<"~DB ..."<<endl;
        }
    
        void open() {//数据库打开
            cout<<"~open ..."<<endl;
        }
    
        void close() {//数据库关闭
            cout<<"~close ..."<<endl;
        }
    
        void query() {//数据库查询
            cout<<"~query ..."<<endl;
        }
    };
    
    int main(void) {
    
        DB* db = new DB();
        db->open();
    
        db->query();
        
        db->close();
        delete db;
    
        return 0;
    }

    编译运行:

    但是说到数据库的关闭,可能在实际的程序中并没有这么简单,因为不知道它何时释放,我们想当数据库对象销毁的时候自动关闭,那首先能想到的办法是:

    #include <iostream>
    using namespace std;
    
    class DB {
    public:
        DB() {
            cout<<"DB ..."<<endl;
            open();
        }
    
        ~DB() {
            cout<<"~DB ..."<<endl;
            close();
        }
    
        void open() {//数据库打开
            cout<<"~open ..."<<endl;
        }
    
        void close() {//数据库关闭
            cout<<"~close ..."<<endl;
        }
    
        void query() {//数据库查询
            cout<<"~query ..."<<endl;
        }
    };
    
    int main(void) {
    
        DB* db = new DB();
        //db->open();
    
        db->query();
        
        //db->close();
        delete db;
    
        return 0;
    }

    其运行结果也是一样的,但是这种方案也不是很可取,一是构造里面的数据库打开比较耗时,在构造函数中不宜做太多事,另外在实际中也能难找到合适的delete销毁对象的地方,也就不能调用close()方法了,我们希望在对象的生命周期结束的时候能够被自动释放,可以采取另外一种方案,具体如下:

    #include <iostream>
    using namespace std;
    
    class DBHelper {//将原来的类改名了
    public:
        DBHelper() {
            cout<<"DB ..."<<endl;
        }
    
        ~DBHelper() {
            cout<<"~DB ..."<<endl;
        }
    
        void open() {//数据库打开
            cout<<"~open ..."<<endl;
        }
    
        void close() {//数据库关闭
            cout<<"~close ..."<<endl;
        }
    
        void query() {//数据库查询
            cout<<"~query ..."<<endl;
        }
    };
    
    class DB {//新声明一个类
    public:
        DB() {
            db_ = new DBHelper();
        }
    
        ~DB() {
            delete db_;
        }
    
        DBHelper* operator->() {//重写指针运算符
            return db_;
        }
    private:
        DBHelper* db_;
    };
    
    int main(void) {
    
        DB db;
        db->open();//为啥对象可以直接用指针调用,也就是由于DB重写了指针运算符了
    
        db->query();
        
        db->close();
    
        return 0;
    }

    编译运行:

    这样写有什么好处呢?可以很灵活的释放动态对象,利用的是确定性析构的办法,DB对象在生命周期开始时一定会调用构造函数,而当生命周期结束之后一定会调用析构函数,从而释放了所包装的DBHelper对象,使得我们对内存的控制更加方便,实际上这也是智能指针实现的一个技巧,就相当于DB是一个智能指针【smart pointer:关于智能指针之后会仔细学习,巧妙的使用智能指能能够避免内存泄漏】,它包装了DBHelper

    先回顾一下new的三种用法:new operator、operator new、placement new

    下面来用代码说明以上三种new的用法:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    private:
        int n_;
    };
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        return 0;
    }

    实际上可以debug一下既可看出是否是如上面所说的那样:

    按F11跟踪进去:

    跳出该方法,继续按F11跟踪,会发现调用了构造函数了:

    通过这个过程可以清楚的了解到new operator操作的内部执行过程。

    所以对于"new operator、operator new"的这两种new的用法已经清楚了,那接下来说明一下它的最后一种用法:“placement new”:

    编译运行:

    从结果打印来看是一样的,为了进一步说明,可以debug一下:

    其中operator new是可以被重载的,但是new operator是不能的,下面来看下相关的语法:

    • void* operator new(size_t size)
    • void operator delete(void* p)
    • void operator delete(void* p, size_t size)
    • void* operator new(size_t size, const char* file, long line)
    • void operator delete(void* p, const char* file, long line)
    • void* operator new[](size_t size)
    • void operator delete[](void* p)
    • void operator delete[](void* p, size_t size)

    一旦new运算符被重载了,对应的delete运算符也随之要被重载,是相匹配的,下面来实现下:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    
        void* operator new(size_t size) {//重载operator new
            cout<<"void* operator new(size_t size)"<<endl;
            void* p = malloc(size);
            return p;
        }
    
        void operator delete(void* p) {
            cout<<"void operator delete(void* p)"<<endl;
            free(p);
        }
    
        int n_;
    };
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        delete p1;
        char chunk[10];
        Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存
        cout<<p2->n_<<endl;
    
        //Test* p3 = (Test*)chunk;
        Test* p3 = reinterpret_cast<Test*>(chunk);
        cout<<p3->n_<<endl;
    
        return 0;
    }

    编译运行:

    这是为啥呢?因为下面有个placement new还没有重载,所以为了说明问题先将下面的语句注释掉,之后再来让其编译通过:

    再次编译运行:

    另外operator delete有两种方式,如下:

    那能否共存呢?

    编译运行:

    可以看出可以共存,默认是调用第一个参数的operator delete,那如果将一个参数的注释掉,程序还会编译通过么?

    看结果:

    也就是说这两个operator delete都是与operator new相匹配的。

    另外operator new分为局部重载和全局重载,像刚才的是属于局部重载,只针对Test类有关,而如果像下面这种new operator呢?

    它会不会调用Test类中的operator new运算符呢?答案当然不会,因为这是属于全局的,我们也可以重载全局的operator new,如下:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    
        void* operator new(size_t size) {//重载operator new
            cout<<"void* operator new(size_t size)"<<endl;
            void* p = malloc(size);
            return p;
        }
    
        /*void operator delete(void* p) {
            cout<<"void operator delete(void* p)"<<endl;
            free(p);
        }*/
    
        void operator delete(void* p, size_t size) {
            cout<<"void operator delete(void* p, size_t size)"<<endl;
            free(p);
        }
    
        int n_;
    };
    
    void* operator new(size_t size) {
        cout<<"global void* operator new(size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete(void* p) {
        cout<<"global void operator delete(void* p)"<<endl;
        free(p);
    }
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        delete p1;
    
        char* str = new char;
        delete str;
    
        char chunk[10];
        /*Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存
        cout<<p2->n_<<endl;
    
        //Test* p3 = (Test*)chunk;
        Test* p3 = reinterpret_cast<Test*>(chunk);
        cout<<p3->n_<<endl;*/
    
        return 0;
    }

    编译运行:

    那如果是new一个数组呢?

    可以重载带数组的函数,如下:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    
        void* operator new(size_t size) {//重载operator new
            cout<<"void* operator new(size_t size)"<<endl;
            void* p = malloc(size);
            return p;
        }
    
        /*void operator delete(void* p) {
            cout<<"void operator delete(void* p)"<<endl;
            free(p);
        }*/
    
        void operator delete(void* p, size_t size) {
            cout<<"void operator delete(void* p, size_t size)"<<endl;
            free(p);
        }
    
        int n_;
    };
    
    void* operator new(size_t size) {
        cout<<"global void* operator new(size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete(void* p) {
        cout<<"global void operator delete(void* p)"<<endl;
        free(p);
    }
    
    void* operator new[](size_t size) {
        cout<<"global void* operator new[](size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete[](void* p) {
        cout<<"global void operator delete[](void* p)"<<endl;
        free(p);
    }
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        delete p1;
    
        char* str = new char[100];
        delete[] str;
    
        char chunk[10];
        /*Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存
        cout<<p2->n_<<endl;
    
        //Test* p3 = (Test*)chunk;
        Test* p3 = reinterpret_cast<Test*>(chunk);
        cout<<p3->n_<<endl;*/
    
        return 0;
    }

    再次编译运行:

    可见这次就调用了我们重载的全局数组的operator new函数了。

    下面还有这种重载形式没有使用:

    那它的作用是什么呢?下面来使用一下:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    
        void* operator new(size_t size) {//重载operator new
            cout<<"void* operator new(size_t size)"<<endl;
            void* p = malloc(size);
            return p;
        }
    
        /*void operator delete(void* p) {
            cout<<"void operator delete(void* p)"<<endl;
            free(p);
        }*/
    
        void operator delete(void* p, size_t size) {
            cout<<"void operator delete(void* p, size_t size)"<<endl;
            free(p);
        }
    
        void* operator new(size_t size, const char* file, long line) {
            cout<<file<<":"<<line<<endl;
            void* p = malloc(size);
            return p;
        }
    
        void operator delete(void* p, const char* file, long line) {
            cout<<file<<":"<<line<<endl;
            free(p);
        }
    
        int n_;
    };
    
    void* operator new(size_t size) {
        cout<<"global void* operator new(size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete(void* p) {
        cout<<"global void operator delete(void* p)"<<endl;
        free(p);
    }
    
    void* operator new[](size_t size) {
        cout<<"global void* operator new[](size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete[](void* p) {
        cout<<"global void operator delete[](void* p)"<<endl;
        free(p);
    }
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        delete p1;
    
        char* str = new char[100];
        delete[] str;
    
        char chunk[10];
        /*Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存
        cout<<p2->n_<<endl;
    
        //Test* p3 = (Test*)chunk;
        Test* p3 = reinterpret_cast<Test*>(chunk);
        cout<<p3->n_<<endl;*/
    
        return 0;
    }

    另外解决一下placement new还没重载造成下面的测试代码编译不过的问题:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    
        void* operator new(size_t size) {//重载operator new
            cout<<"void* operator new(size_t size)"<<endl;
            void* p = malloc(size);
            return p;
        }
    
        /*void operator delete(void* p) {
            cout<<"void operator delete(void* p)"<<endl;
            free(p);
        }*/
    
        void operator delete(void* p, size_t size) {
            cout<<"void operator delete(void* p, size_t size)"<<endl;
            free(p);
        }
    
        void* operator new(size_t size, const char* file, long line) {
            cout<<file<<":"<<line<<endl;
            void* p = malloc(size);
            return p;
        }
    
        void operator delete(void* p, const char* file, long line) {
            cout<<file<<":"<<line<<endl;
            free(p);
        }
    
        void* operator new(size_t size, void* p) {
            return p;
        }
    
        int n_;
    };
    
    void* operator new(size_t size) {
        cout<<"global void* operator new(size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete(void* p) {
        cout<<"global void operator delete(void* p)"<<endl;
        free(p);
    }
    
    void* operator new[](size_t size) {
        cout<<"global void* operator new[](size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete[](void* p) {
        cout<<"global void operator delete[](void* p)"<<endl;
        free(p);
    }
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        delete p1;
    
        char* str = new char[100];
        delete[] str;
    
        char chunk[10];
        Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存
        cout<<p2->n_<<endl;
    
        //Test* p3 = (Test*)chunk;
        Test* p3 = reinterpret_cast<Test*>(chunk);
        cout<<p3->n_<<endl;
    
        return 0;
    }

    编译:

    new和delete操作是需要匹配的,所以再重载一下delete方法:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int n): n_(n) {
            cout<<"Test(int n): n_(n)"<<endl;
        }
        Test(const Test& other) {
            cout<<"Test(const Test& other)"<<endl;
        }
        ~Test() {
            cout<<"~Test()"<<endl;
        }
    
        void* operator new(size_t size) {//重载operator new
            cout<<"void* operator new(size_t size)"<<endl;
            void* p = malloc(size);
            return p;
        }
    
        /*void operator delete(void* p) {
            cout<<"void operator delete(void* p)"<<endl;
            free(p);
        }*/
    
        void operator delete(void* p, size_t size) {
            cout<<"void operator delete(void* p, size_t size)"<<endl;
            free(p);
        }
    
        void* operator new(size_t size, const char* file, long line) {
            cout<<file<<":"<<line<<endl;
            void* p = malloc(size);
            return p;
        }
    
        void operator delete(void* p, const char* file, long line) {
            cout<<file<<":"<<line<<endl;
            free(p);
        }
    
        void* operator new(size_t size, void* p) {
            return p;
        }
    
        void operator delete(void*, void* p) {//关于它的函数原形可能通过debug内部跟踪得到
        }
    
        int n_;
    };
    
    void* operator new(size_t size) {
        cout<<"global void* operator new(size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete(void* p) {
        cout<<"global void operator delete(void* p)"<<endl;
        free(p);
    }
    
    void* operator new[](size_t size) {
        cout<<"global void* operator new[](size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
    
    void operator delete[](void* p) {
        cout<<"global void operator delete[](void* p)"<<endl;
        free(p);
    }
    
    int main(void) {
        Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用
        delete p1;
    
        char* str = new char[100];
        delete[] str;
    
        char chunk[10];
        Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存
        cout<<p2->n_<<endl;
    
        //Test* p3 = (Test*)chunk;
        Test* p3 = reinterpret_cast<Test*>(chunk);
        cout<<p3->n_<<endl;
    
        return 0;
    }

    再次编译运行:

    修改代码如下:

    再次编译运行,看这回构造与虚构是否匹配了:

    好了,解决了placement new编译通过的问题之后,则下面来使用一下带多个参数的operator new的重载方法,如下:

    下面来看下结果:

    照理应该是调用与之匹配的它:

    具体原因这里也不太清楚,反正如果不重写这个delete会给出警告,这个待之后再做出考证。对于上面这种写法其实可以定义成宏,如下:

  • 相关阅读:
    网络七层模型
    border-radius 50% 和100%
    数据绑定
    前端一些基础的重要的知识2
    用 ul 和 li 模拟select控件
    两列布局
    盒子的水平垂直居中几种方法
    TCP时间戳
    帧聚合
    skb buff数据结构
  • 原文地址:https://www.cnblogs.com/webor2006/p/5431368.html
Copyright © 2011-2022 走看看