首頁 > 軟體

C++模擬實現vector流程詳解

2022-08-08 22:02:57

模擬vector

我們可以通過模板實現類似vector的類。我們實現一個StrVecTemp類,其內部通過allocator開闢空間,儲存的型別用T來表示,T是模板型別。

template <typename T>
class StrVecTemp
{
public:
    StrVecTemp() : elements(nullptr), first_free(nullptr),
                   cap(nullptr) {}
    //拷貝建構函式
    StrVecTemp(const StrVecTemp &);
    //拷貝賦值運運算元
    StrVecTemp &operator=(const StrVecTemp &);
    //移動建構函式
    StrVecTemp(StrVecTemp &&src) noexcept : elements(src.elements),
                                            first_free(src.first_free), cap(src.cap)
    {
        //將源資料置空
        src.elements = src.first_free = src.cap = nullptr;
    }
    template <class... Args>
    void emplace_back(Args &&...args);
    //解構函式
    ~StrVecTemp();
    //拷貝元素
    void push_back(const T &);
    //丟擲元素
    void pop_back(T &s);
    //返回元素個數
    size_t size() const { return first_free - elements; }
    //返回capacity返回容量
    size_t capacity() const { return cap - elements; }
    //返回首元素的指標
    T *begin() const
    {
        return elements;
    }
    //返回第一個空閒元素指標
    T *end() const
    {
        return first_free;
    }
private:
    //判斷容量不足靠皮新空間
    void chk_n_alloc()
    {
        if (size() == capacity())
        {
            reallocate();
        }
    }
    //重新開闢空間
    void reallocate();
    // copy指定範圍的元素到新的記憶體中
    std::pair<T *, T *> alloc_n_copy(const T *, const T *);
    //釋放空間
    void free();
    //陣列首元素的指標
    T *elements;
    //指向陣列第一個空閒元素的指標
    T *first_free;
    //指向陣列尾後位置的指標
    T *cap;
    //初始化alloc用來分配空間
    static std::allocator<T> alloc;
};
template <typename T>
std::allocator<T> StrVecTemp<T>::alloc;

alloc在使用前要在類外初始化,因為是模板類,所以放在.h中初始化即可。

接下來我們要實現根據迭代器開始和結束的區間copy舊元素到新的空間裡

//實現區間copy
template <typename T>
std::pair<T *, T *> StrVecTemp<T>::alloc_n_copy(const T *b, const T *e)
{
    auto newdata = alloc.allocate(e - b);
    //用舊的資料初始化新的空間
    auto first_free = uninitialized_copy(b, e, newdata);
    return {newdata, first_free};
}

實現copy構造

//實現拷貝建構函式
template <class T>
StrVecTemp<T>::StrVecTemp(const StrVecTemp &strVec)
{
    auto rsp = alloc_n_copy(strVec.begin(), strVec.end());
    //利用pair型別更新elements, cap, first_free
    elements = rsp.first;
    first_free = rsp.second;
    cap = rsp.second;
}

實現copy賦值

//拷貝賦值運運算元
template <class T>
StrVecTemp<T> &StrVecTemp<T>::operator=(const StrVecTemp &strVec)
{
    if (this == &strVec)
    {
        return *this;
    }
    //如果不是自賦值,就將形參copy給自己
    auto rsp = alloc_n_copy(strVec.begin(), strVec.end());
    elements = rsp.first;
    first_free = rsp.second;
    cap = rsp.second;
}

解構函式要先銷燬資料再回收記憶體

//解構函式
template <class T>
StrVecTemp<T>::~StrVecTemp()
{
    //判斷elements是否為空
    if (elements == nullptr)
    {
        return;
    }
    //快取第一個有效元素的地址
    auto dest = elements;
    //迴圈解構
    for (size_t i = 0; i < size(); i++)
    {
        //解構每一個元素
        alloc.destroy(dest++);
    }
    //再回收記憶體
    alloc.deallocate(elements, cap - elements);
    elements = nullptr;
    cap = nullptr;
    first_free = nullptr;
}

重新開闢空間

template <class T>
void StrVecTemp<T>::reallocate()
{
    T *newdata = nullptr;
    //陣列為空的情況
    if (elements == nullptr || cap == nullptr || first_free == nullptr)
    {
        newdata = alloc.allocate(1);
        elements = newdata;
        first_free = newdata;
        // cap指向陣列尾元素的下一個位置
        cap = newdata + 1;
        return;
    }
    //原資料不為空,則擴充size兩倍大小
    newdata = alloc.allocate(size() * 2);
    //新記憶體空閒位置
    auto dest = newdata;
    //就記憶體的有效位置
    auto src = elements;
    //通過移動操作將舊資料放到新記憶體中
    for (size_t i = 0; i != size(); ++i)
    {
        alloc.construct(dest++, std::move(*src++));
    }
    //移動完舊資料後一定要刪除
    free();
    //更新資料位置
    elements = newdata;
    first_free = dest;
    cap = newdata + size() * 2;
}

上面的函數用到了free函數,我們自己實現一個free

template <typename T>
void StrVecTemp<T>::free()
{
    //先判斷elements是否為空
    if (elements == nullptr)
    {
        return;
    }
    auto dest = elements;
    //遍歷解構每一個物件
    for (size_t i = 0; i < size(); i++)
    {
        // destroy 會解構每一個元素
        alloc.destroy(dest++);
    }
    //再整體回收記憶體
    alloc.deallocate(elements, cap - elements);
    elements = nullptr;
    cap = nullptr;
    first_free = nullptr;
}

壓入元素和彈出元素

//拷貝元素
template <class T>
void StrVecTemp<T>::push_back(const T &t)
{
    chk_n_alloc();
    alloc.construct(first_free++, t);
}
//丟擲元素
template <class T>
void StrVecTemp<T>::pop_back(T &s)
{
    //先判斷是否為空
    if (first_free == nullptr)
    {
        return;
    }
    //判斷size為1
    if (size() == 1)
    {
        s = *elements;
        alloc.destroy(elements);
        first_free = nullptr;
        elements = nullptr;
        return;
    }
    s = *(--first_free);
    alloc.destroy(first_free);
}

接下來要實現emplace_back,因為emplace_back支援多種建構函式的引數,所以要用模板參數列的方式定義該函數。

模板參數列和形參列表都要用引數包的方式

template <class T>
template <class... Args>
void StrVecTemp<T>::emplace_back(Args &&...args)
{
    chk_n_alloc();
    alloc.construct(first_free++, forward<Args>(args)...);
}

Args是模板引數包,args是參數列。因為construct的引數可能為右值參照,所以要用forward將原參數列型別原樣轉發。

// forward既擴充套件了模板引數包Args,又擴充套件了函數引數包args
// std::forward<Args>(args)... 等價於std::forward<Ti>(ti)
//比如傳遞給emplace_back(10,'c');
//相當於呼叫 alloc.construct(first_free++, forward<int>(10), forward<char>('c'))
//呼叫的就是插入cccccccccc

總結

本文模擬實現了vector的功能。

視訊連結

原始碼連結

到此這篇關於C++模擬實現vector流程詳解的文章就介紹到這了,更多相關C++ vector內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com