vector各個接口函數
//構造函數
vector()
vector(size_t n,const T& val=T())
vector(int n,const T& val = T())
//拷貝構造函數
vector(const vector<T>& v)
//迭代器版本的
vector(inputiterator first, inputiterator end)
//賦值運算符重載
vector<T>& operator=( vector<T> v)
//析構函數
~vector()
//容量相關的函數
void reserve(size_t n)//擴容版本
void resize(size_t n, T val = T())//擴容+初始化版本
size_t size()const//數量
size_t capacity() const容量
//插入修改容器中的數據的函數
void push_back(T x)
iterator insert(iterator pos, const T& val)
iterator erase(iterator pos)
迭代器相關的函數
iterator begin()//指向首元素
iterator end()//指向最后一個元素的下一個位置
const_iterator begin()const
const_iterator end()const
//容器中數據訪問的[]
T& operator[](size_t pos)
const T& operator[](size_t pos)const
vector成員中相關成員變量
vector本質上就是一個動態開辟的數組,里面的成員跟以前的順序表沒啥本質區別,但是vector是用模板實現的,可以任意類型的數據。我們也可以看看源碼中vector是怎么實現的呢?
分別使用了一個迭代器的指針,一個表示首元素,一個表示最后一個元素的下一個位置,還有一個就是end_of_storage表示容量。我在下面的寫的vector當中給了缺省值,避免每次寫構造函數的時候都要初始化一下。
private:
iterator _start=nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
默認成員函數
我給了缺省值,會自動走初始化列表,因此我在這邊不要在初始化一下了。
vector()
{}
構造函數的實現
vector支持一種這樣的構造,你可以給出n個你想初始化的值,在l類與對象那么我們知道構造函數對內置類型不做處理,對自定義類型會去調用它的構造函數。但是C++11以后對內置類型也會做處理了。int x= int(); char c=char();
vector(size_t n,const T& val=T())//c++11之后支持對內置類型也會初始化
{if (size() + n > capacity()){reserve(size() + n);}while ((int)n--)//會發生隱式類型的轉化{push_back(val);}_finish += n;}
我上面還寫了一個int類型的構造這邊就不放了,主要是模板會自動匹配它的最優選擇,我們一般給vector v(10,1);會自動隱式類型轉換成int int 的類型。
拷貝構造函數
寫法思路就是_start先開辟一塊與該容器大小相等的空間,然后將該容器的數據一個一個的拷貝過來,最后改變一下_finish跟_end_of_storage
vector(const vector<T>& v)
{std::cout << "vector(const T& v)" << std::endl;_start = new T[v.capacity()];//memcpy(_start, v._start, v.size()*sizeof(T));for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}
賦值運算符重載的函數
傳統寫法
思路就是開辟一個與容器大小相同的tmp空間,然后將容器中數據傳給tmp數組,思路跟拷貝構造后面一致。
//傳統寫法
vector<T>& operator=(vector<T>& v)
{if (this != &v){iterator tmp = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){tmp[i] = v._start[i];}_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}
現代寫法
就是通過算法庫里面的swap交換一下倆者的指針所指向的地址
void Swap(vector<T>& v)
{std::swap(_start,v._start);std::swap(_finish,v._finish);std::swap(_end_of_storage,v._end_of_storage);
}
vector<T>& operator=( vector<T> v){Swap(v);return *this;}
迭代器版本的構造函數
vector還支持使用一段迭代器區間進行對象的構造。因為該迭代器區間可以是其他容器的迭代器區間,也就是說該函數接收到的迭代器的類型是不確定的,所以我們這里需要將該構造函數設計為一個函數模板,在函數體內將該迭代器區間的數據一個個尾插到容器當中即可。
template<class inputiterator>
//迭代器版本默認是左閉右開的
vector(inputiterator first, inputiterator end)
{while (first != end){push_back(*first);first++;}
}
析構函數
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}
容量相關的函數
size_t size() const
{return _finish - _start;
}
size_t capacity() const
{return _end_of_storage - _start;
}
reserve的實現規則就是判斷當前的是否達到最大的容積,達到就擴容,開辟一塊tmp空間,當_start不為空的時候就開始挪動數據。
void reserve(size_t n)
{if (n > capacity()){iterator tmp = new T[n];//挪動數據if (_start){size_t sz = size();//memcpy(tmp, _start, sizeof(T) * sz);//淺拷貝for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];//實現深拷貝}}delete[] _start;_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}
resize函數實現的思路就是當輸入n小于當前內存的時候我們就不需要初始化,并且跟新一下_finish=_start+n,,當大于的時候就需要分倆種情況,一種是當vector為空的時候,我們就直接在頭部開始插入數據,第二種就是本來就有數據,需要尾插數據一直達到n個大小。
void resize(size_t n, T val = T())
{if (n <=size()){_finish =_start+n;}else{reserve(n);while ((int)n--){if (_start){push_back(val);}else{insert(begin(), val);}}}
}
插入修改容器中的數據的函數
尾插數據push_back,實現思路就是插入數據首先就是擴容,然后在finish后面插入數據,然后讓finish++一下。
void push_back(T x)
{if (_finish >= _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;_finish++;
}
insert()的寫法,以前的寫法就是給一個下標pos的位置,然后刪除對應pos位置上的數據,而現在這個pos變成了指針,我們在使用指針插入對應數據的時候,特別容易出現迭代器失效的情況,也就是野指針的行為,為啥呢?因此我們在插入數據的時候需要擴容,我們是額外開了一個數組,然后就會將原數組釋放掉,再讓_start指向新開辟的數組,所以我們需要保存pos指針的位置。
iterator insert(iterator pos, const T& val)
{assert(pos <=_finish);size_t len = pos - _start;if (_finish >= _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}iterator end = _finish - 1;pos = _start + len;//跟新pos指針的位置while (end >= pos){*(end + 1) = *end;end--;}*pos = val;_finish++;return pos;
}
erase的實現,在Linux跟vs中對erase()底層實現都有區別,vs中只要刪除了該數據,該位置的值就不可訪問或者解引用修改查看,而inux環境下就寬松了很多,我們可以隨意的訪問或者修改,但是出錯了也意味著不容易判斷。
iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator it = pos + 1;while (it !=_finish){*(it - 1) = *it;it++;}_finish--;return pos;}
迭代器相關的函數和數據訪問
這個很簡單直接放代碼
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
const_iterator begin()const
{return _start;
}
const_iterator end()const
{return _finish;
}
T& operator[](size_t pos)
{assert(pos <= size());return _start[pos];
}
const T& operator[](size_t pos)const
{assert(pos <= size());return _start[pos];
}