C++學習 STL組件之vector部分總結

自從上一次學習STL的組件(string)已經過去有些日子了,主要是還在進行其他方面的學習,現在有了空閒繼續來總結C++STL方面,vectot也是很早之前就學過的部分,學習過程中也是讓我深深感到了C++STL的實用,現在在做有關方面的題目時有的地方會忘記(所以總結順便複習),總之要學習和總結的東西還有很多,就請路過的看客和我一起學習前進吧。-----(≧∇≦)ノ

首先是關於vector的介紹

經過之前一遍學習後,我也大致瞭解了vector的學習步驟,我將其分爲以下三個部分:
C++學習 STL組件之vector部分總結

有人可能會問那一條紅線?那是與之相關的內容,也是我最近在學習的部分之一,下一次的內容就是他了(當然還是STL相關的,我學的慢。。。)

然後當然就是給出vector的定義了!

由於瞭解到定義這一部分也是蠻重要的知識點內容,所以我將此詳細總結了一下,如下:

  1. vector是表示可變大小數組的序列容器

  2. 像數組一樣,vector也採用的連續存儲空間來存儲元素,可以採用下標對vector的元素進行訪問

  3. 和數組不同,vector的大小是可以動態改變的,而且它的大小會被容器自動處理(kksk^_^)

  4. 本質上講,vector使用動態分配數組來存儲它的元素,當新元素插入時候,這個數組爲了增加存儲空間需要被重新分配大小,具體做法是 “分配一個新的數組,然後將全部元素移到這個數組”

  5. vector分配空間策略是:

    • vector會分配一些額外的空間以適應可能的增長,所以存儲空間比實際需要的存儲空 間更大

    • 雖然不同的庫採用不同的策略權衡空間的使用和重新分配,但是無論如何,重新分配 對數增長的間隔大小,以至於在末尾插入一個元素的時間都應該是在常數時間的複雜 度內完成的
  6. 與其它動態序列容器相比(deque、 list 、 forward_list), vector在訪問元素的時候更加高效,在末尾添加和刪除元素相對高效。對於其它不在末尾的刪除和插入操作,效率較低。

再再來到最最重要的使用環節!

首先是vector內所包含的接口有哪些:

1.構造函數使用:
構造函數上的使用有以下四種

* vector()                                                                                無參構造
* vector(size_type n, const value_type& val = value_type()    構造並初始化n個val
* vector (const vector& x)                                                           拷貝構造
* vector (InputIterator first, InputIterator last)                             使用迭代器進行初始化構造

vector的這四種構造函數中最常用的就是
無參構造函數vector()和拷貝構造vector (const vector& x)
第二種構造vector(size_type n, const value_type& val = value_type() 看場合使用即可
而關於第4種構造 在這裏先跳過,畢竟迭代器相關我還沒有學習,之後跟進的博客到達迭代器部分的時候再說

2.vector提供的迭代器(iterator)使用:
也就是以下兩組

  • begin + end 獲取第一個數據位置iterator/const_iterator, 獲取最後一個數據的下一個位置的iterator/const_iterator

  • rbegin + rend 獲取最後一個數據位置的reverse_iterator, 獲取第一個數據前一個位置的reverseiterat

畫個圖的話差不多是這種感覺(畫畫苦手):
C++學習 STL組件之vector部分總結

此處只總結迭代器的使用方法:
eg1: const對象使用const迭代器進行遍歷打印

vector<int>::const_iterator it = v.begin();
//C++中可以使用auto關鍵字來代替上面一句it的類型部分,自動匹配數據的類型。
 while (it != v.end())
 {
 cout << *it << " ";
 ++it;
 }
 cout << endl;
}

eg2:使用迭代器進行遍歷打印

 vector<int>::iterator it = v.begin();//此句同上可使用auto關鍵字
 while (it != v.end())
 {
 cout << *it << " ";
 ++it;
 }

eg3:使用反向迭代器進行遍歷再打印

 vector<int>::reverse_iterator rit = v.rbegin(); //同上(沒錯就是懶得寫)
 while (rit != v.rend())
 {
 cout << *rit << " ";
 ++rit;
 }

2.有關容量空間變化的接口
常用的有以下幾種:

  • size 獲取數據個數
  • capacity 獲取容量大小
  • empty 判斷是否爲空
  • resize(重點) 改變vector的size
  • reserve (重點) 改變vector放入capacity

前三種接口都是常用且簡單的,與string相似,所以這裏不再做演示。
(注意,再次提醒capacity的增長是不固定的,具體增長多少是根據具體的需求定義
的,比方說:vs下capacity是按1.5倍增長的,g++是按2倍增長的,vs是PJ版本STL,g++是SGI版本STL)

主要需要注意的是resize和reserve兩個函數:

  • reserve只負責開闢空間,如果確定知道需要用多少空間,reserve就可以緩解vector增容代價缺陷問題。

  • resize在開空間的同時還會進行初始化(對於擴大的那一部分空間),影響size
    此處給出兩個使用例子:

eg1:

std::vector<int> b;
 int sz = b.capacity();
 b.reserve(100);
 int ss=b.capacity();
 //可輸出sz和ss來觀察reserve效果

eg2:

std::vector<int> a;
a.resize(5);
a.resize(8,100); 
a.resize(12);
//可通過同上手段來觀察

3.vector內容增刪查改操作相關接口
常用有如下幾種:

  • push_back 尾插
  • pop_back 尾刪
  • find 查找(算法模塊實現,不是vector的成員接口)
  • insert 在position之前插入val
  • erase 刪除position位置的數據,返回下一個位置的迭代器
  • swap 交換兩個vector的數據空間
  • operator[] 像數組一樣訪問元素

其實我感覺上述這些接口都有用武之地,所以需要重點記憶的!

(1)push_back以及pop_back
eg:
vector<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
//此時a中內容爲1 2 3 4
a.pop_back(3);
a.pop_back(4);
//此時a中內容爲1 2

(2).operator[](其實這個接口主要學習還是在模擬實現部分,這裏給出使用例)
eg:
vector<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);
cout<< a[0]<<a[1]<<a[2];
//輸出結果爲123

(3).find
eg:
int a[] = { 1, 2, 3, 4 };
vector<int> s(a, a+sizeof(a)/sizeof(int)) //vector可以將數組直接插入具體實現在模擬實現
auto it = find(v.begin(), v.end(), 3);
// 此時*it的值爲3
//結果it爲前兩迭代器表示的範圍內第一次找到3這個元素時的迭代器位置。

其他的幾種具體實現和學習在模擬實現部分(耐心)

4.關於增刪查改操作的接口有的時候還會導致迭代器的失效
迭代器的失效產生的原因很多,這裏舉例常見的幾種場景

(1)insert或者erase導致的迭代器失效

  • insert導致的情況: insert會導致迭代器失效,是因爲insert可能會導致增容,增容後pos還指向原來的空間,而原來的空間已經釋放了

  • erase導致的情況:刪除pos位置的數據,會導致pos迭代器失效,此時再用pos迭代器進行訪問就會出現程序錯誤。

(2)其他場景出現的迭代器失效

eg:
當進行以下操作時
auto it = a.begin();
while (it != a.end())
{
if (*it % 2 == 0) //簡單的刪除偶數
v.erase(it);
++it; //在這一步就會出現程序錯誤,因爲erase已經將 it 變成了一個失效迭代器, } 對於失效的迭代器進行++會引發錯誤

那麼問題來了,上面這段代碼要怎麼改呢?
由於erase接口被調用會返回刪除位置的下一個位置,所以將代碼改爲以下這種就可以啦:
while (it != a.end())
{
if (*it % 2 == 0)
it = a.erase(it);
else
++it;
}

每當我學不動了時候我都會找張圖片來細細觀賞一番(洗眼睛),下面有請老婆登場:
C++學習 STL組件之vector部分總結

(嘿嘿)
來來來現在我們繼續,現在進入的就是最後的一部分啦,學完這裏我們就可以放開手大膽的去用vector了(maybe~):

vector的模擬實現(包括深度解析)

1.對vector深度解析
這一步是事關重要的,如果沒有這一步的鋪墊,要進行模擬實現就會難上加難,不清楚構造的東西自然難以實現
首先附上一張圖來做說明(手繪勿噴):
C++學習 STL組件之vector部分總結

上圖中:start表示指向數據塊的開始
finish表示指向有效數據的尾
end_of_storage表示指向存儲容量的尾

根據上面我畫的這張圖我們可以大致瞭解到vector的結構。
已上圖爲例,當a中元素超過capacity時,容量會擴充至兩倍,若是兩倍還不夠則會擴充至足夠大的容量。

注意:“但是擴容的步驟可不是直接在原來空間後面開闢空間這麼簡單,其中要經過重新配置、元素搬移、釋放原空間等過程,是較爲複雜的”

最後到了模擬實現的部分,部分需要注意的點會寫在註釋裏:

namespace key{
    template<class K>
    class vector{
    public:
        // vector的迭代器是一個原生指針,所以做以下重定義方便實用
        typedef K* iterator;
        typedef const K* const_iterator;
        vector()                             //無參構造
            :_start(nullptr)                 //初始化列表
            , _finish(nullptr)
            , _endofstorage(nullptr){}

        vector(int n, const K& value = K())  //含參數構造,且初始化,插入n個value元素,第二個參數做缺省值可不填
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstorage(nullptr){
            reserve(n);
            while (n--)
            {
                push_back(value);         //若是有第二個參數輸入則初始化元素
            }
        }

        //所以重新聲明迭代器,迭代器區間[first,last]可以是任意容器的迭代器
        template<class InIterator>
        vector(InIterator first, InIterator last){
            reserve(last - first);
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }
        vector(const vector<K>& v)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstorage(nullptr){
            reserve(v.capacity());
            K* it = begin();
            K* vit = v.cbegin();
            while (vit != v.cend())
            {
                *it++ = *vit++;
            }
            _finish = _start + v.size();
            _endofstorage = _start + v.capacity();
        }
        vector<K>& operator= (vector<K> v){
            swap(v);
            return *this;
        }
        ~vector(){
            delete[] _start;
            _start = _finish = _endofstorage = nullptr;
        }

        // capacity
        size_t size() const { return _finish - _start; }
        size_t capacity() const { return _endofstorage - _start; }

        void reserve(size_t n){  //擴容,改變capacity的值
            if (n > capacity())
            {
                size_t oldSize = size();
                K* tmp = new K[n];
                if (_start)
                {
                    for (size_t i = 0; i < oldSize; ++i)
                        tmp[i] = _start[i];
                }
                _start = tmp;
                _finish = _start + size;
                _endofstorage = _start + n;
            }
        }

        void resize(size_t n, const K& value = K()){   //重新設定大小並初始化(部分)
            // 1.如果n小於當前的size,則數據個數縮小到n
            if (n <= size())
            {
                _finish = _start + n;
                return;
            }
            // 2.空間不夠則增容
            if (n > capacity())
                reserve(n);
            // 3.將size擴大到n        //擴大的部分要初始化
            iterator it = _finish;
            iterator _finish = _start + n;
            while (it != _finish)
            {
                *it = value;
                ++it;
            }
        }
        ///////////////操作符重載///////////////////////////////
        T& operator[](size_t pos){ 
            return _start[pos]; 
        }
        const T& operator[](size_t pos)const {
            return _start[pos]; 
        }

        ///////////////增刪查改/////////////////////////////
        void push_back(const K& x){     //後插
            insert(end(), x); 
        }
        void pop_back(){             //後刪
            erase(--end()); 
        }

        void swap(vector<K>& v){           //交換兩個vector對象的數據空間
            swap(_start, v._start);
            swap(_finish, v._finish);
            swap(_endofstorage, v._endofstorage);
        }

        iterator insert(iterator pos, const K& x){   //插入
            assert(pos <= _finish);
            // 空間不夠先進行增容
            if (_finish == _endofstorage)
            {
                size_t size = size();
                size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;
                reserve(newCapacity);
                // 如果發生了增容,需要重置pos
                pos = _start + size;
            }
            K* end = _finish - 1;
            while (end >= pos)
            {
                *(end + 1) = *end;
                --end;
            }
            *pos = x;
            ++_finish;
            return pos;
        }
        // 返回刪除數據的下一個數據
        // 方便解決:一邊遍歷一邊刪除的迭代器失效問題
        iterator erase(Iterator pos)
        {
            // 挪動數據進行刪除
            iterator begin = pos + 1;
            while (begin != _finish) {
                *(begin - 1) = *begin;
                ++begin;
            }
            --_finish;
            return pos;
        }

        iterator begin() { return _start; }
        iterator end() { return _finish; }
        const_iterator cbegin() const { return _start; }
        const_iterator cend() const { return _finish; }

        private:
            iterator _start; // 指向數據塊的開始
            iterator _finish; // 指向有效數據的尾
            iterator _endofstorage; //指向存儲文件的尾

    };
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章