3运算符重载

运算符重载

运算符重载的好处:使运算符的表现和编译器内置类型一样

复数类的实现:

class CComplex
{
public:
    CComplex(int r=0,int m=0)
            :mimage_(m),mreal_(r)
    {
        cout<<"CComplex(int r=0,int m=0)"<<endl;
    }
    CComplex(const CComplex&another)
    {
        cout<<"CComplex(const CComplex&another)"<<endl;
        this->mimage_ = another.mimage_;
        this->mreal_ = another.mreal_;
    }
    CComplex& operator=(const CComplex& another)
    {
        cout<<"CComplex& operator=(const CComplex&another)"<<endl;
        this->mimage_ = another.mimage_;
        this->mreal_ = another.mreal_;
        return *this;
    }
    CComplex operator+(const CComplex&another)
    {
        CComplex ret;
        ret.mimage_ = this->mimage_ + another.mimage_;
        ret.mreal_ = this->mreal_ + another.mreal_;
        cout << "CComplex operator+(const CComplex&another)" << endl;
        return ret;
    }
    CComplex operator++(int)  //后置++
    {
        return CComplex(this->mreal_+1, this->mimage_+1);
    }
    CComplex& operator++()     //前置++
    {
        this->mreal_++;
        return *this;
    }
    void show()
    {
        cout<<"real:"<<mreal_<<endl<<"image:"<<mimage_<<endl;
    }
    friend  CComplex operator+(const CComplex&lhs ,const CComplex& rhs);
    friend ostream& operator<<(ostream& out,const CComplex& comp);
    friend istream& operator>>(istream& in,CComplex& comp);
private:
    int mreal_;
    int mimage_;
};

CComplex operator+(const CComplex&lhs ,const CComplex& rhs)  //全局 operator+
{
    cout<<"CComplex operator+(const CComplex&lhs , CComplex& rhs)"<<endl;
    return CComplex(lhs.mreal_+ rhs.mreal_,lhs.mimage_+rhs.mimage_);
}
ostream& operator<<(ostream& out,const CComplex& comp)
{
    out<<"real:"<<comp.mreal_<<"image"<<comp.mimage_;
    return out;
}
istream& operator>>(istream& in,CComplex& comp)
{
    in >> comp.mreal_ >> comp.mimage_;
    return in;
}
int main()
{
    CComplex com2(1,2);
    CComplex com4 = com2 + 30;
    /*可以运行不报错的原因
     * com2 operator+(30)  -->  com2 operator(int)
     * 可是重载 + 的类型为 CComplex
     * 编译器就要寻找 int 转const CCmplex临时对象
     * 看构造函数  CComplex(int r=0,int m=0)
     * 存在 int 转 const CComplex
     * 所以可以成功编译
     * */
     
    com4.show();
}

模拟实现string类的代码

#include <iostream>
#include "typeinfo"
#include "string.h"
using namespace std;

class String
{
public:
    String(const char* str= nullptr)
    {
        cout<<"    String(const char* str= nullptr)"<<this<<endl;
        if(str == nullptr)
            pstr_ = new char('\0');
        else
        {
            pstr_ = new char(strlen(str) + 1);
            for(int i=0;i< strlen(str);i++)
            {
                pstr_[i] = str[i];
            }
        }
    }
    ~String()
    {
        cout<<"    ~String()"<<this<<endl;
        delete[] pstr_;
        pstr_ = nullptr;
    }
    String(const String&another)
    {
        cout<<"    String(const String&another) this"<<this<<"another "<<&another<<endl;
        this->pstr_ = new char[strlen(another.pstr_)  +1];
        strcpy(this->pstr_,another.pstr_);
    }
    int length() const
    {
        return strlen(this->pstr_);
    }
    const char* c_str() const
    {
        return pstr_;
    }

    String& operator=(const String& another)
    {
        cout<<"    String& operator=(const String& another) this"<<this <<"another "<<&another<<endl;
        if(this == &another)
            return *this;
        delete[] pstr_;
        this->pstr_ = new char[strlen(another.pstr_) + 1];
        strcpy(this->pstr_,another.pstr_);
        return *this;
    }

    bool operator>(const String&another) const  //这个方法只涉及读操作没有写操作,写为const方法
    {
        return strcmp(this->pstr_,another.pstr_)  > 0;
    }
    bool operator<(const String&another) const  //这个方法只涉及读操作没有写操作,写为const方法
    {
        return strcmp(this->pstr_,another.pstr_)  < 0;
    }
    bool operator==(const String&another) const  //这个方法只涉及读操作没有写操作,写为const方法
    {
        return strcmp(this->pstr_,another.pstr_)  == 0;
    }
    char operator[](int index)
    {
        return (this->pstr_)[index];
    }
    const char& operator[](int index) const
    {
        return pstr_[index];
    }

    friend String operator+(const String& lhs,const String& rhs);
    friend ostream& operator<<(ostream&out,String&another);
private:
    char* pstr_;
};

ostream& operator<<(ostream&out,String&another)
{
    cout<<"ostream& operator<<(ostream&out,String&another)"<<endl;

    out<<another.pstr_;
    return out;
}

String operator+(const String& lhs,const String& rhs)
{
   /*
    这种方法是低效的
    char * tem =new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
    strcpy(tem,lhs.pstr_);
    strcat(tem,rhs.pstr_);
    String ret(tem);
    delete[] tem;
    return ret;*/
   cout<<"String operator+(const String& lhs,const String& rhs)"<<endl;
    String tem;
    delete[] tem.pstr_;
    tem.pstr_ = new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
    strcpy(tem.pstr_,lhs.pstr_);
    strcat(tem.pstr_,rhs.pstr_);
    return tem;
}

int main() {
    String s1 = "abcdefg";        //隐式转换调用构造函数,生成临时对象
    String s2 = s1 + "asdjpajsd";
    cout << s2;

    /*
     *  g++ -fno-elide-constructors  //关闭优化
     String(const char* str= nullptr)0x7ffff49f1490                        字符串隐式转化
    String(const String&another) this0x7ffff49f1478another 0x7ffff49f1490  拷贝复制给s1
    ~String()0x7ffff49f1490                                                析构临时对象
    String(const char* str= nullptr)0x7ffff49f1488                         隐式转化生成临时对象
String operator+(const String& lhs,const String& rhs)                      s1和临时对象相加调用operator+函数
    String(const char* str= nullptr)0x7ffff49f1440                         operator+函数内构造对象tem
    String(const String&another) this0x7ffff49f1490another 0x7ffff49f1440  拷贝赋值给另一个临时对象,位于operator+栈外
    ~String()0x7ffff49f1440                                                operator+函数站内对象tem消失
    String(const String&another) this0x7ffff49f1480another 0x7ffff49f1490  拷贝赋值给s2
    ~String()0x7ffff49f1490                                                临时对象消失
    ~String()0x7ffff49f1488                                                将字符串转换的临时对象析构       
ostream& operator<<(ostream&out,String&another)
abcdefgasdjpajsd    ~String()0x7ffff49f1480                                析构s2
    ~String()0x7ffff49f1478                                                析构s1
     * */
    return 0;
}

string容器的迭代器:

先看看string本身的iterator

int main() {
    string s1 = "abcdefg";
    string::iterator it;
    for(it=s1.begin();it!=s1.end();it++)
    {
        cout<<*it;
    }
    }

自实现:

#include <iostream>
#include "string"
#include "typeinfo"
#include "string.h"
using namespace std;

class String
{
public:
    String(const char* str= nullptr)
    {
        //cout<<"    String(const char* str= nullptr)"<<this<<endl;
        if(str == nullptr)
            pstr_ = new char('\0');
        else
        {
            pstr_ = new char(strlen(str) + 1);
            for(int i=0;i< strlen(str);i++)
            {
                pstr_[i] = str[i];
            }
        }
    }
    ~String()
    {
        //cout<<"    ~String()"<<this<<endl;
        delete[] pstr_;
        pstr_ = nullptr;
    }
    String(const String&another)
    {
        //cout<<"    String(const String&another) this"<<this<<"another "<<&another<<endl;
        this->pstr_ = new char[strlen(another.pstr_)  +1];
        strcpy(this->pstr_,another.pstr_);
    }
    int length() const
    {
        return strlen(this->pstr_);
    }
    const char* c_str() const
    {
        return pstr_;
    }

    String& operator=(const String& another)
    {
        //cout<<"    String& operator=(const String& another) this"<<this <<"another "<<&another<<endl;
        if(this == &another)
            return *this;
        delete[] pstr_;
        this->pstr_ = new char[strlen(another.pstr_) + 1];
        strcpy(this->pstr_,another.pstr_);
        return *this;
    }

    bool operator>(const String&another) const  //这个方法只涉及读操作没有写操作,写为const方法
    {
        return strcmp(this->pstr_,another.pstr_)  > 0;
    }
    bool operator<(const String&another) const  //这个方法只涉及读操作没有写操作,写为const方法
    {
        return strcmp(this->pstr_,another.pstr_)  < 0;
    }
    bool operator==(const String&another) const  //这个方法只涉及读操作没有写操作,写为const方法
    {
        return strcmp(this->pstr_,another.pstr_)  == 0;
    }
    char operator[](int index)
    {
        return (this->pstr_)[index];
    }
    const char& operator[](int index) const
    {
        return pstr_[index];
    }

    friend String operator+(const String& lhs,const String& rhs);
    friend ostream& operator<<(ostream&out,String&another);



    class iterator
    {
    public:
        iterator(char *p = nullptr)
        {
            p_ = p;
        }
        iterator(const iterator& another)
        {
            p_ = another.p_;
        }
        void operator++()
        {
            p_+=1;
        }
        void operator++(int)
        {
            this->p_ += 1;

        }
        char operator*()
        {
            return *p_;
        }
        bool operator!=(const iterator&another)
        {
            return this->p_ != another.p_;
        }
    private:
        char* p_;
    };
    iterator begin()
    {
        return iterator(pstr_);
    }
    iterator end()
    {
        return iterator(pstr_+ strlen(pstr_));
    }

private:
    char* pstr_;
};

ostream& operator<<(ostream&out,String&another)
{
    //cout<<"ostream& operator<<(ostream&out,String&another)"<<endl;

    out<<another.pstr_;
    return out;
}

String operator+(const String& lhs,const String& rhs)
{
   /*
    这种方法是低效的
    char * tem =new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
    strcpy(tem,lhs.pstr_);
    strcat(tem,rhs.pstr_);
    String ret(tem);
    delete[] tem;
    return ret;*/
   //cout<<"String operator+(const String& lhs,const String& rhs)"<<endl;
    String tem;
    delete[] tem.pstr_;
    tem.pstr_ = new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
    strcpy(tem.pstr_,lhs.pstr_);
    strcat(tem.pstr_,rhs.pstr_);
    return tem;
}

int main() {
    //迭代器的功能是通过统一的方式来透明的遍历容器

    String s1 = "abcdefg";
    String::iterator it;
    /*for(it=s1.begin();it!=s1.end();it++)
    {
        cout<<*it;
    }*/
    //所谓的for-each,遍历容器其底层就是通过迭代器工作的
    for(char c:s1){cout<<c<<" ";}

#if 0
    String s1 = "abcdefg";        //隐式转换调用构造函数,生成临时对象
    String s2 = s1 + "asdjpajsd";
    cout << s2;

    /*
     *
     String(const char* str= nullptr)0x7ffff49f1490                        字符串隐式转化
    String(const String&another) this0x7ffff49f1478another 0x7ffff49f1490  拷贝复制给s1
    ~String()0x7ffff49f1490                                                析构临时对象
    String(const char* str= nullptr)0x7ffff49f1488                         隐式转化生成临时对象
String operator+(const String& lhs,const String& rhs)                      s1和临时对象相加调用operator+函数
    String(const char* str= nullptr)0x7ffff49f1440                         operator+函数内构造对象tem
    String(const String&another) this0x7ffff49f1490another 0x7ffff49f1440  拷贝赋值给另一个临时对象,位于operator+栈外
    ~String()0x7ffff49f1440                                                operator+函数站内对象tem消失
    String(const String&another) this0x7ffff49f1480another 0x7ffff49f1490  拷贝赋值给s2
    ~String()0x7ffff49f1490                                                临时对象消失
    ~String()0x7ffff49f1488                                                将字符串转换的临时对象析构
ostream& operator<<(ostream&out,String&another)
abcdefgasdjpajsd    ~String()0x7ffff49f1480                                析构s2
    ~String()0x7ffff49f1478                                                析构s1
     * */
    return 0;
#endif
}

迭代器的功能是通过统一的方式来透明的遍历容器

auto it = container.begin();      //统一
for(;it!=containter.end();++it){cout<<*it<<endl;}  

迭代器让作为使用者不必关心容器底层的数据结构

容器迭代器失效问题

先来看看STL中迭代器失效的情况:

int main()
{
    std::vector<int> vi = {1,2,3,4,5,6,7,8};
    for(auto i = vi.begin();i!=vi.end();++i)
    {
        if(*i % 2 == 0)
        vi.erase(i);
    }
    return 0;
}
终端;
进程已结束,退出代码-1073740940 (0xC0000374)  //程序执行出现错误,问题是迭代器失效了

迭代器为什么会失效?

  1. 当容器调用erase犯法后,从当前位置到容器的末尾元素的所有迭代器失效
  2. 当容器调用insert方法后,从当前位置到容器的末尾元素所有的迭代器全部失效,对于insert来收,如果引起了容器的扩容操作,就会使全部的迭代器失效

迭代器失效后问题要如何解决?

  1. 对插入删除点的迭代器进行更新操作

    int main()    //earse 
    {
        vector<int> vi = {1,2,3,4,5,6,7,8};
        auto it = vi.begin();
        while (it!=vi.end())
        {
            if(*it % 2 == 0)
                it = vi.erase(it); //earse()返回删除元素的当前位置
            else
            {
                it++;
            }
        }
        return 0;
    }
    
    
    int main()
    {
        vector<int> vi = {1,2,3,4,5,6,7,8};
        auto it = vi.begin();
        for(;it!=vi.end();++it)
        {
            if(*it % 2 ==0 )
            {
                it = vi.insert(it,*it -1);
            }
        }
        return 0;
    }
    
    
    

    深入理解new和delete

    先看一下new和delete的汇编实现

    int main() {
        int *p = new int;
        delete p;
        return 0;
    }
    反汇编:
    0x00007ff6515c16e0 <+0>:	push   %rbp
       0x00007ff6515c16e1 <+1>:	mov    %rsp,%rbp
       0x00007ff6515c16e4 <+4>:	sub    $0x30,%rsp
       0x00007ff6515c16e8 <+8>:	call   0x7ff6515c1897 <__main>
       0x00007ff6515c16ed <+13>:	mov    $0x4,%ecx
       0x00007ff6515c16f2 <+18>:	call   0x7ff6515c17c0 <operator new(unsigned long long)>
       0x00007ff6515c16f7 <+23>:	mov    %rax,-0x8(%rbp)
       0x00007ff6515c16fb <+27>:	mov    -0x8(%rbp),%rax
       0x00007ff6515c16ff <+31>:	test   %rax,%rax
       0x00007ff6515c1702 <+34>:	je     0x7ff6515c1711 <main()+49>
       0x00007ff6515c1704 <+36>:	mov    $0x4,%edx
       0x00007ff6515c1709 <+41>:	mov    %rax,%rcx
       0x00007ff6515c170c <+44>:	call   0x7ff6515c17c8 <operator delete(void*, unsigned long long)>
       0x00007ff6515c1711 <+49>:	mov    $0x0,%eax
       0x00007ff6515c1716 <+54>:	add    $0x30,%rsp
       0x00007ff6515c171a <+58>:	pop    %rbp
       0x00007ff6515c171b <+59>:	ret
    

可以发现new和delete的本质是一个 重载函数 operator new(unsigned long long)

#include <iostream>
using std::cout;
using std::endl;


void operator delete(void *ptr) {
    cout << "void operator delete(void *ptr)" << endl;
    free(ptr);
}

void* operator new(size_t size)
//typedef unsigned __int64 size_t  #define __int64 long long
// size_t 等价与unsinged long long
{
    cout<<"void* operator new(size_t size)"<<endl;
    void* p = malloc(size);
    if(p == nullptr)
        throw std::bad_alloc();   //bad_alloc是一个构造函数,即将一个临时对象抛出
    return p;
}

void* operator new[](size_t size)
{
    void *p = malloc(size);
    if(p == nullptr)
        throw std::bad_alloc();
        return p;
        cout<<"void operator new[](size_t size)"<<endl;

}
void operator delete[](void *ptr)
{
    cout<<"void operator delete[](void *ptr)"<<endl;
    free(ptr);
}
class test
{
public:
    test(){cout<<"test()"<<endl;}
    ~test(){cout<<"~test()"<<endl;}
private:
    int a;
    int b;
    int c;
};
int main()
{
    test* p = new test;
    cout<<sizeof(*p)<<endl;
    delete p;
    return 0;
}
输出:
void* operator new(size_t size)
test()
12
~test()
void operator delete(void *ptr)

通过调试发现:
new和delete本质的操作,就是 new = operator new(malloc) + 构造器 delete = 析构 + operator delete(free) 。
在调用自实现的new操作时会先调用自实现new块中的代码(必须要有malloc(size),这里的size会更具传入的类型自行取sizeof),之后在调用构造函数。
在调用自实现的delete操作时会先调用析构函数,在来调用自实现的delete块中的代码(其中必要要free操作)

new和delete能混用吗?c++为什么区分单个元素和数组的内存内存空间分配和释放呢?

当开辟数组空间的时候为了让编译器知道开辟了多少个对象,在内存中会首先开辟4字节的空间用于存放对象的个数,delete[] 表示重复析构与 free ,次数是由第一个对象的前4个字节的数值决定

第一个:delete p;
第二个:delete (p+sizeof(test));
第三个:delete(p+2*sizeof(test));

分析如下代码找出错误原因:

#include <iostream>
using std::cout;
using std::endl;


void operator delete(void *ptr) {
    cout << "void operator delete(void *ptr)" << ptr<<endl;
    free(ptr);
}

void* operator new(size_t size)
//typedef unsigned __int64 size_t  #define __int64 long long
// size_t 等价与unsinged long long
{
    void* p = malloc(size);
    cout<<"void* operator new(size_t size)"<<p<<endl;
    if(p == nullptr)
        throw std::bad_alloc();   //bad_alloc是一个构造函数,即将一个临时对象抛出
    return p;
}
void* operator new[](size_t size)
//编译器显示这个是重载函数,只能是和c++自带的new[]重载
// 但是为什么不是重定义了? 我觉得这是重定义
{
    void *p = malloc(size);
    if(p == nullptr)
        throw std::bad_alloc();
    cout<<"void operator new[](size_t size)"<<p<<endl;
    return p;
}
void operator delete[](void *ptr)
{
    cout<<"void operator delete[](void *ptr)"<<ptr<<endl;
    free(ptr);
}
class test
{
public:
    test(){cout<<"test():"<< this<<endl;}
    ~test(){cout<<"~test():"<<this<<endl;}
private:
    int a;

};

int main()
{
   test* p = new test[3];
   cout<<"p:"<<p<<endl;
   delete p;
}

输出:

输出:
void operator new[](size_t size)0x7fffde11aeb0
test():0x7fffde11aeb8
test():0x7fffde11aebc
test():0x7fffde11aec0
p:0x7fffde11aeb8
~test():0x7fffde11aeb8
void operator delete(void *ptr)0x7fffde11aeb8
munmap_chunk(): invalid pointer
Aborted (core dumped)

吐核说明执行有问题。其实就是刚才讲到的内存开辟问题,void operator new[](size_t size) 0x7fffde11aeb0 这是开辟new开辟空间的真实地址 ,但是析构时传入的参数p是p:0x298916818b8 与真实地址相差 8字节 ,从这里delete就导致 8字节 空间泄露,所以报错。正常是要先delete掉用于计数的8字节的内存,在循环析构掉多个对象

若改用delete[]

void operator new[](size_t size)0x7fffd9455eb0
test():0x7fffd9455eb8
test():0x7fffd9455ebc
test():0x7fffd9455ec0
p:0x7fffd9455eb8
~test():0x7fffd9455ec0
~test():0x7fffd9455ebc
~test():0x7fffd9455eb8
void operator delete[](void *ptr)0x7fffd9455eb0

若改为:

int main()
{
   test* p = new test;
   cout<<"p:"<<p<<endl;
   delete[] p;
}

也会导致吐核,因为delete[] 会首先free,p指针地址的之前4字节或8字节的位置,在讲这段内存中的值作为析构与free的次数进行循环删除对象和内存。这肯定是错误的

简单内存池的实现

#include <iostream>
using namespace std;

template<typename T>
class Queue
{
public:
    Queue()
    {
        front_ = rear_ = new QueueItem;
    }
    ~Queue()
    {
        QueueItem* it = front_;
        while(it != nullptr)
        {
            QueueItem* tem = it->next_;
            delete it;
            it = tem;
        }
    }
    void add(const T& val)   //入队
    {
        QueueItem item = new QueueItem(val);
        rear_->next_ = item;
        rear_ = item;
    }
    void pop()
    {
        if(empty()) return;
        QueueItem* first = front_->next_;
        front_->next_ = first->next_;
        if(front_->next_ == nullptr)
            rear_ = front_;
        delete first;
    }
    void push(const T& val)
    {
        auto* tem = new QueueItem(val);
        rear_->next_ = tem;
        rear_ = tem;
    }
    bool empty()
    {
        return (front_->next_ == nullptr);
    }
private:
    struct QueueItem{
        explicit QueueItem(T data=T()) :data_(data),next_(nullptr){}

        static void* operator new(size_t size)
        {
            if(itemPool == nullptr)
            {
                itemPool = (QueueItem *) malloc(sizeof(QueueItem) * POOL_Item_Size);
                //_itemPool = (QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];
                auto tem = itemPool;
                for(;tem < itemPool + POOL_Item_Size - 1;++tem)
                {
                    tem->next_ = tem + 1;
                }
                tem->next_ = nullptr;
            }
            QueueItem *p = itemPool;
            itemPool = itemPool->next_; //
            return p;
        }
        static void operator delete(void* ptr)
        {
            auto* p = (QueueItem*)ptr;
            p->next_ = itemPool;
            itemPool = p;
        }

        static const int POOL_Item_Size = 3;  //static修饰常量可以直接在类内赋值
        static QueueItem* itemPool;

        T data_;
        QueueItem *next_;
    };
    QueueItem* front_;  //队头
    QueueItem* rear_;   //队尾
    static Queue* ite;
};
template<typename T>
typename Queue<T>::QueueItem* Queue<T>::QueueItem::itemPool = nullptr;
//这里typename的作用是告诉编译器Queue<T>::QueueItem*是一个类型,因为模板类还没有实例化
//因此编译器不知道QueueItem是什么类型。这里就要加上typename让编译器放心这是一个类型

int main() {
    Queue<int> q;
    for(int i=0;i<100000;i++)
    {
        q.push(5);
        q.pop();
    }

    return 0;
}
posted @ 2022-10-18 20:19  satellite2002  阅读(33)  评论(0编辑  收藏  举报