1. 程式人生 > 其它 >五、C++運算子過載,使面向物件程式設計更方便

五、C++運算子過載,使面向物件程式設計更方便

複數類CComplex

編譯器做物件運算的時候,會呼叫物件的運算子過載函式(優先呼叫成員方法);如果沒有成員方法,就砸全域性作用域找合適的運算子過載函式

++--運算子是單目運算子,在引數列表裡放上一個int表示其在數的前面還是後面:operator++()表示前置,operator++(int)表示後置,括號裡的int沒有任何作用。

複數類的具體實現:

//
// Created by 26685 on 2022-05-16 13:43.
// Description:CComplex.h
//

#ifndef C___CCOMPLEX_H
#define C___CCOMPLEX_H

#include <iostream>

using namespace std;

class CComplex {
    friend CComplex operator+(const CComplex &l, const CComplex &r);

    friend iostream &operator<<(ostream &os, const CComplex &src);

    friend istream & operator>>(istream& is, CComplex &src);

public:
    explicit CComplex(int r = 0, int i = 0) : _real(r), _image(i) {}

//    CComplex operator+(const CComplex& src) const{
//        return CComplex(this->_real+src._real,this->_image+src._image);
//    }
    void show() {
        cout << "real: " << _real << " image: " << _image << endl;
    }

    CComplex &operator++() {
        ++_real;
        ++_image;
        return *this;
    }

    CComplex operator++(int) {
        return CComplex(_real++, _image++);
    }

private:
    int _real;
    int _image;
};

inline CComplex operator+(const CComplex &l, const CComplex &r) {
    return CComplex(l._real + r._real, l._image + r._image);
}

inline iostream &operator<<(ostream &os, const CComplex &src) {//過載輸出操作
    os << "real: " << src._real << " image: " << src._image << endl;
}

inline istream & operator>>(istream& is,CComplex &src){//過載輸入操作
    is>>src._real>>src._image;
}

#endif //C___CCOMPLEX_H

主函式:

int main(){
    CComplex cp1(10,15);
    CComplex cp2(20,30);
    CComplex cp3=cp2+cp1;
    cp3.show();

    CComplex cp4=cp3++;
    cp4.show();
    cp3.show();
    CComplex cp5= ++cp3;
    cp5.show();
    cp3.show();

    cout<<cp4;

    CComplex cp6;
    cin>>cp6;
    cout<<cp6;

    return 0;
}

模擬實現string類的程式碼

//
// Created by 26685 on 2022-05-16 14:30.
// Description:String.h
//

#ifndef C___STRING_H
#define C___STRING_H

#include <iostream>
#include <cstring>

class String {

    friend std::ostream &operator<<(std::ostream &os, const String &src);

public:
    String(const char *src = nullptr) {
        if (src == nullptr) {
            _pstr = new char[1];
            *_pstr = '\0';
        } else {
            _pstr = new char[strlen(src) + 1];
            strcpy(_pstr, src);
        }
    }

    ~String() {
        delete[] _pstr;
        _pstr = nullptr;
    }

    String(const String &src) {
        _pstr = new char[strlen(src._pstr) + 1];
        strcpy(_pstr, src._pstr);
    }

    bool operator>(const String &str) const {
        return strcmp(_pstr, str._pstr) > 0;
    }

    bool operator<(const String &str) const {
        return strcmp(_pstr, str._pstr) < 0;
    }

    bool operator==(const String &str) const {
        return strcmp(_pstr, str._pstr) == 0;
    }

    int length() const {
        return strlen(_pstr);
    }

    char &operator[](int index) {
        return _pstr[index];
    }

    char *c_str() const {
        return _pstr;
    }

private:
    char *_pstr;
};

inline std::ostream &operator<<(std::ostream &os, const String &src) {
    os << src._pstr;
    return os;
}

inline String operator+(const String& l,const String& r){
    char* ptmp=new char[strlen(l.c_str())+ strlen(r.c_str())+1];
    strcpy(ptmp,l.c_str());
    strcat(ptmp,r.c_str());
    String temp(ptmp);
    delete[] ptmp;
    return temp;
}

#endif //C___STRING_H

目前程式碼中的加法的過載運算效率不高,需要進一步改進。

上面程式碼的加法過載函式,會生成臨時物件,影響效能。

暫時改進為:

inline String operator+(const String &l, const String &r) {
//    char *ptmp = new char[strlen(l.c_str()) + strlen(r.c_str()) + 1];
    String temp;
    temp._pstr=new char[strlen(l.c_str()) + strlen(r.c_str()) + 1];//避免了開闢兩次記憶體空間
    strcpy(temp._pstr, l.c_str());
    strcat(temp._pstr, r.c_str());
//    String temp(ptmp);
//    delete[] ptmp;
    return temp;
}

String物件的迭代器的實現

迭代器可以透明的訪問容器內部元素的值

foreach遍歷容器,其底層是用迭代器實現的

迭代器的功能:提供一種統一的方式,透明的遍歷容器

在對迭代器加加時,一般用前置的++,因為不會生成新的物件,效率會高一些

 /**
     * 迭代器的實現, 放在String類中
     */
    class Iterator{
    public:
        Iterator(char* p= nullptr):_p(p){}

        bool operator!=(const String::Iterator&it){//判斷兩個迭代器是否相等
            return _p!=it._p;
        }

        void operator++(){
            ++ _p;
        }

        char& operator*(){return *_p;}

    private:
        char* _p;
    };


    Iterator begin(){
        return {_pstr};
    }
    Iterator end(){
        return {_pstr+length()};
    }

實現vector容器中的迭代器

迭代器一般實現成容器的巢狀結構。

在VectorT類中新增以下程式碼:

class iterator {
    public:
        iterator(const T *p = nullptr)
                : _ptr((int *) p) {}

        bool operator!=(const VectorT::iterator &it) {
            return _ptr != it._ptr;
        }

        void operator++() {
            ++_ptr;
        }

        T &operator*() { return *_ptr; }

    private:
        T *_ptr;
    };

    iterator begin(){
        return {_first};
    }

    iterator end(){
        return {_last};
    }

迭代器的失效問題

1、呼叫erase後,當前位置到末尾元素的迭代器就會失效。

2、呼叫insert後,當前位置到末尾元素的迭代器就會失效

3、容器擴容後迭代器也會失效

首元素到插入點/刪除點的迭代器依然有效

迭代器失效該怎麼解決?要對迭代器進行更新操作!

不同容器的迭代器是不能進行比較運算的

vector中迭代器的實現(包含迭代器失效的判斷)

//
// Created by 26685 on 2022-05-15 20:33.
// Description:
//

#ifndef C___VECTORT_H
#define C___VECTORT_H

#include "AllocatorT.h"

using namespace std;

/**
 * 容器底層記憶體開闢,記憶體釋放,物件構造和析構都通過allocator實現
 * @tparam T
 * @tparam Alloc
 */
template<typename T, typename Alloc=AllocatorT<T> >
class VectorT {
public:
    VectorT(int size = 10) {
//        _first=new T[size];
        _first = _alloctor.allocate(size);
        _last = _first;
        _end = _first + size;
    }

    ~VectorT() {
//        delete[] _first;
        //使用allocator對vector逐個刪除
        for (T *p = _first; p != _last; ++p) {
            _alloctor.destory(p);
        }
        _alloctor.deallocate(_first);
        _first = _last = _end = nullptr;
    }

    VectorT(const VectorT<T> &src) {
        int size = src._end - src._first;
//        _first = new T[size];
        _first = _alloctor.allocate(size);
        int len = src._last - src._first;
        for (int i = 0; i < len; i++) {
//            _first[i] = src._first[i];
            _alloctor.contruct(_first + 1, src._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
    }

    VectorT<T> &operator=(const VectorT<T> &src) {
        if (src == *this) {
            return *this;
        }
        //delete[] _first;
        for (T *p = _first; p != _last; p++) {
            _alloctor.destory(p);
        }
        _alloctor.deallocate(_first);

        int size = src._end - src._first;
        _first = new T[size];
        int len = src._last - src._first;
        for (int i = 0; i < len; i++) {
//            _first[i] = src._first[i];
            _alloctor.contruct(_first + 1, src._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
        return *this;
    }

    T &operator[](int index) {
        if (index < 0 || index >= size()) {
            throw "OutOfRangeException";
        }
        return _first[index];
    }

    void push_back(T val) {
        if (full()) {
            expend();
        }
        //*_last++ = val;
        _alloctor.construct(_last, val);
        _last++;
    }

    void pop_back() {
        if (empty()) { return; }
        verify(_last - 1, _last);
        --_last;
        _alloctor.destory(_last);
    }

    T back() const {
        return *(_last - 1);
    }

    bool full() const {
        return _last == _end;
    }

    bool empty() const {
        return _first == _last;
    }

    int size() const {
        return _last - _first;
    }

    /**
     * 實現迭代器
     */
    class iterator {
        friend void VectorT<T, Alloc>::verify(T *first, T *last);
        friend iterator VectorT<T, Alloc>::insert(iterator it,const T& val);
        friend iterator VectorT<T, Alloc>::erase(iterator it);

    public:
        /*iterator(const T *p = nullptr)
                : _ptr((int *) p) {}*/
        /**
         * 根據新的成員變數實現新的建構函式,使其能實現迭代器失效
         * @param pvec 容器指標
         * @param ptr 位置指標
         */
        iterator(VectorT<T, Alloc> *pvec = nullptr, T *ptr = nullptr) : _ptr(ptr), _pVec(pvec) {
            Iterator_Base *itb = new Iterator_Base(this, _pVec->_head._next);//構造新節點
            _pVec->_head._next = itb;//將頭結點連線新節點
        }//接下來就是在改變陣列的過程中使迭代器失效

        bool operator!=(const VectorT<T, Alloc>::iterator &it) {
            /**
             * 判斷迭代器是否失效
             */
            if (_pVec == nullptr || _pVec != it._pVec) {
                throw "iterator incompatable!";
            }

            return _ptr != it._ptr;
        }

        void operator++() {
            if (_pVec == nullptr) {
                throw "iterator invalid!";
            }
            ++_ptr;
        }

        T &operator*() {
            if (_pVec == nullptr) {
                throw "iterator invalid!";
            }
            return *_ptr;
        }

    private:
        T *_ptr;

        /**
         * 實現迭代器失效,首先要新增一個指向容器的指標
         */
        VectorT<T, Alloc> *_pVec;
    };

    /**
     * 根據新的成員方法生成相應的begin和end方法
     * @return
     */
    iterator begin() {
        return {this, _first};
    }

    iterator end() {
        return {this, _last};
    }

    /**
     * 最後一步:判斷迭代器是否失效
     * @param first
     * @param last
     */
    void verify(T *first, T *last) {
        Iterator_Base *pre = &this->_head;
        Iterator_Base *it = this->_head._next;
        while (it != nullptr) {
            if (it->_cur->_ptr > first && it->_cur->_ptr <= last) {
                //迭代器失效,把iterator持有的容器指標置null
                it->_cur->_pVec = nullptr;
                //刪除當前迭代器節點,繼續判斷後面的迭代器節點是否失效
                pre->_next = it->_next;
                delete it;
                it = pre->_next;
            }else{
                pre=it;
                it=it->_next;
            }
        }
    }
    /**
     * 插入操作
     * @param it 迭代器位置
     * @param val 插入的值
     * @return 迭代器
     */
    iterator insert(iterator it,const T& val){
        /*
         * 不考慮擴容,
         * 不考慮指標的合法性
         */
        verify(it._ptr-1,_last);
        T* p=_last;
        while(p>it._ptr){
            _alloctor.construct(p,*(p-1));
            _alloctor.destory(p-1);
            p--;
        }
        _alloctor.construct(p,val);
        _last++;
        return {this,p};

    }

    iterator erase(iterator it){
        verify(it._ptr-1,_last);
        T* p=it._ptr;
        while(p<_last-1){//元素向前移
            _alloctor.destory(p);
            _alloctor.construct(p,*(p+1));
            p++;
        }
        _alloctor.destory(p);
        _last--;
        return {this,it._ptr-1};

    }

private:
    T *_first;//表示vector起始位置

    T *_last;//表示vector定義元素的末尾

    T *_end;//表示vector的末尾

    Alloc _alloctor;//負責記憶體管理

    /**
     * 在連結串列的結構中儲存每個迭代器
     */
    struct Iterator_Base {
        Iterator_Base(iterator *c = nullptr, VectorT<T, Alloc>::Iterator_Base *n = nullptr) : _cur(c), _next(n) {}

        iterator *_cur;
        Iterator_Base *_next;
    };

    /**
     * 頭結點
     */
    Iterator_Base _head;

    void expend() {//size擴大兩倍
        int size = _end - _first;
//        T *ptmp = new T[size * 2];
        T *ptmp = _alloctor.allocate(2 * size);
        for (int i = 0; i < size; i++) {
            //ptmp[i] = _first[i];
            _alloctor.construct(ptmp + i, _first[i]);
        }
        //delete[] _first;
        for (T *p = _first; p != _last; p++) {
            _alloctor.destory(p);
        }
        _alloctor.deallocate(_first);

        _first = ptmp;
        _last = _first + size;
        _end = _first + (2 * size);
    }
};


#endif //C___VECTORT_H

深入理解new和delete的原理

1、malloc和new的區別:

  • malloc按位元組開闢記憶體,new開闢記憶體時需要指定型別,如 new int[10],所以malloc開闢記憶體返回的都是void*
  • malloc只負責開闢空間,new不僅有malloc的功能,還可以進行資料的初始化
  • malloc開闢記憶體失敗返回nullptr指標,new丟擲的是bad_alloc型別的異常

2、free和delete的區別:

  • delete呼叫解構函式,free是記憶體釋放

檢查記憶體洩漏要重寫new和delete

new和delete能混用嗎?C++為什麼要區分單個元素和陣列的記憶體分配和釋放呢?

對於內建型別int等,可以混用。但是對於自定義的類,就不能混用,因為自定義的類型別有解構函式,為了正確的解構函式,在開闢物件陣列的時候會在陣列前多開闢4個位元組,記錄物件的個數。

//兩個操作符的過載
void* operator new(size_t size){
    void* p=malloc(size);
    if(p== nullptr){
        throw bad_alloc();
    }
    cout<<"opeartor new addr:"<<p<<endl;
    return p;
}

void operator delete (void *ptr) noexcept{
    cout<<"opeartor delete addr:"<<ptr<<endl;
    free(ptr);
}

new和delete過載實現物件池應用

物件池是在堆上開闢的靜態連結串列

//
// Created by 26685 on 2022-05-17 9:40.
// Description:
//

#ifndef C___QUEUEWITHITEMPOOL_H
#define C___QUEUEWITHITEMPOOL_H

#include <iostream>

using namespace std;

template<typename T>
class Queue {

public:
    Queue(){//預設構造
        _front=_rear=new QueueItem();
    }
    ~Queue(){
        QueueItem* cur=_front;
        while(cur!= nullptr){//遍歷連結串列,依次刪除元素
            _front=_front->_next;
            delete cur;
            cur=_front;
        }
    }

    void push(const T& val){
        QueueItem* item=new QueueItem(val);
        _rear->_next=item;
        _rear=item;//尾部元素置為新值,與front區分開
    }

    void pop(){
        if(empty()){
            return;
        }
        QueueItem* first=_front->_next;
        _front->_next=first->_next;
        if(_front->_next== nullptr){//如果佇列只有一個有效節點
            _rear=_front;
        }
        delete first;
    }

    bool empty() const{
        return _front==_rear;
    }

    T front()const {
        return _front->_next->_data;
    }

private:
    /**
     * 實現一個鏈式的佇列,帶有頭結點
     */
    struct QueueItem {
        QueueItem(T data=T()):_data(data),_next(nullptr){}

        //過載new實現物件池
        void* operator new (size_t size){
            if(_itemPool== nullptr){//如果未開闢空間;如果當前記憶體池使用完,最後一個元素指向的也是nullptr,會分配新的記憶體池
                _itemPool=(QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];//開闢物件池
                //我們用char,按位元組開闢,因為如果用new QueueItem,
                //就又會呼叫到當前這個方法了,
                //我們現在就是在給QueueItem自定義new運算子過載
                QueueItem* p=_itemPool;
                for(;p<_itemPool+POOL_ITEM_SIZE-1;++p){
                    p->_next=p+1;//初始化連續連結串列
                }
                p->_next= nullptr;
            }
            //新建queueItem的時候會使用物件池中未使用的節點,然後指向下一個未使用的節點

            QueueItem* p=_itemPool;
            _itemPool=_itemPool->_next;
            
            return p;
        }

        void operator delete (void* ptr){
            QueueItem* p=(QueueItem*)ptr;
            p->_next=_itemPool;
            _itemPool=p;
        }

        T _data;
        QueueItem *_next;

        static const int POOL_ITEM_SIZE=100000;

        static QueueItem *_itemPool;

    };

    QueueItem* _front;//指向頭結點
    QueueItem* _rear;//指向隊尾,
};

template<typename T>
typename Queue<T>::QueueItem* Queue<T>::QueueItem::_itemPool= nullptr;

#endif //C___QUEUEWITHITEMPOOL_H