目錄
一、定義
二、模擬實現
1、無參初始化
2、size&capacity
3、reserve
4、push_back
5、迭代器
6、empty
7、pop_back
8、operator[ ]
9、resize
10、insert
迭代器失效問題
11、erase
12、帶參初始化
13、迭代器初始化
14、析構函數
完整版代碼
一、定義
本次參考SGI版本STL中的vector模擬實現。
我們可以看到上述源代碼中,SGI版本vector是借助指針實現的,元素的處理是通過兩個指針來實現的,而不是三個迭代器。這兩個指針分別是_start和_finish。
- _start指針指向vector中的第一個元素。
- _finish指針指向vector中最后一個元素的下一個位置。
通過_start和_finish指針,可以確定vector中存儲的元素范圍。
?
此外,SGI版本的vector還使用了一個指針_end_of_storage來表示vector分配的內存空間的末尾位置。
這些指針的使用使得SGI版本的vector能夠高效地進行元素的插入、刪除和訪問操作。
為了不影響VS中STL庫已有的vector,我們選擇將模擬實現的vector放在自定義命名空間中。
namespace byte
{template<class T>class vector{public:private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
二、模擬實現
1、無參初始化
vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{}
2、size&capacity
size_t capacity() const
{return _end_of_storage - _start;
}size_t size() const
{return _finish - _start;
}
3、reserve
void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}
-
if (n > capacity())
:檢查傳入的n是否大于當前vector的容量。如果是,則需要進行內存重新分配。 -
size_t sz = size();
:保存當前vector的大小(元素個數)。 -
T* tmp = new T[n];
:創建一個新的大小為n的動態數組tmp,用于存儲重新分配后的元素。 -
if (_start)
:檢查_start指針判斷舊空間是否為非空。如果_start指針不為空,說明vector中已經有元素存儲在舊的內存空間中。 -
memcpy(tmp, _start, sizeof(T) * size());
:使用memcpy函數將舊的內存空間中的元素復制到新的內存空間tmp中。這樣可以保留元素的值。 -
delete[] _start;
:釋放舊的內存空間。 -
_start = tmp;
:將_start指針指向新的內存空間tmp。 -
_finish = _start + sz;
:更新_finish指針,使其指向新的內存空間中的最后一個元素的下一個位置。 -
_end_of_storage = _start + n;
:更新_end_of_storage指針,使其指向新的內存空間的末尾位置。?
4、push_back
void push_back(const T& x)
{if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
-
使用
const T& x
作為參數類型可以避免不必要的拷貝操作,因為傳入的實參可以直接通過引用訪問,而不需要進行拷貝構造。這可以提高性能和效率,特別是當處理大型對象時。另外,使用
const T& x
還可以確保傳入的元素不會被修改,因為const
關鍵字表示傳入的引用是只讀的,函數內部不能修改傳入的對象。 -
if (_finish == _end_of_storage)
?這個條件判斷用于檢查當前vector是否已經達到了內存空間的末尾。如果是,則需要進行內存重新分配。 -
reserve(capacity() == 0 ? 4 : capacity() * 2)
?在需要進行內存重新分配時,調用reserve
函數來預留更多的內存空間。這里使用了三目運算符,如果當前容量為0,則預留4個元素的空間,否則將當前容量乘以2來預留更多的空間。 -
*_finish = x
?將傳入的元素x
賦值給_finish
指針所指向的位置,即在vector的末尾插入元素。 -
++_finish
?將_finish
指針向后移動一位,指向新插入元素的下一個位置,以便維護vector的邊界。
5、迭代器
typedef T* iterator;
typedef const T* const_iterator;iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}
- 首先,通過
typedef
關鍵字,定義了兩個迭代器類型:iterator
和const_iterator
。iterator
表示可修改元素的迭代器,而const_iterator
表示只讀元素的迭代器。 - 然后,定義了
begin()
和end()
函數的多個重載版本,用于返回不同類型的迭代器。
6、empty
bool empty()
{return _start == _finish;
}
7、pop_back
void pop_back(const T& x)
{assert(!empty());--_finish;
}
8、operator[ ]
這個類中有兩個重載的下標運算符函數,一個是非常量版本的?operator[]
,另一個是常量版本的?operator[]
。這是為了支持對類對象的讀寫操作和只讀操作的區分。
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}
9、resize
void resize(size_t n, T val = T())
{if (n < size()){_finish = _start + n;}else {if (n 》 capacity())reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}
}
函數簽名為?
void resize(size_t n, T val = T())
,接受兩個參數:n
?表示新的大小,val
?表示新元素的默認值(默認為?T()
,通過匿名對象T()調用類型?T
?的默認構造函數)。
函數的作用是將容器的大小調整為?n
。如果?n
?小于當前的大小,則將容器的大小縮小為?n
,丟棄多余的元素;如果?n
?大于當前的大小,則在容器的末尾添加新的元素,直到容器的大小達到?n
。
- 首先,函數會檢查?n?是否小于當前的大小。如果是,說明需要縮小容器的大小,將?_finish?指針移動到新的位置?_start + n,丟棄多余的元素。
- 如果?n?大于等于當前的大小,則需要添加新的元素。首先,函數會檢查?n?是否大于容器的容量?capacity()。如果?n?大于容量,則調用?reserve?函數來增加容器的容量,以確保容器有足夠的空間來存放新的元素。
- 然后,使用循環將新的元素?val?添加到容器的末尾,直到容器的大小達到?n。循環中,將?val?賦值給?_finish?指向的位置,然后將?_finish?指針向后移動一位。
匿名對象調用默認構造初始化。
template<class T>void f(){T x = T();cout << x << endl;}
- 在?
resize
?函數中,T val = T()
?是一個帶有默認值的函數參數。這里?T()
?是對模板參數?T
?類型的值初始化,對于內置類型,它會初始化為零(對于指針類型,初始化為?nullptr
)。這和?f<T>()
?模板函數中的?T x = T()
?是一樣的。 - 當你調用?
resize
?函數時,如果你沒有提供第二個參數,那么?val
?就會被初始化為?T
?類型的默認值。然后,resize
?函數會使用?val
?的值來填充新添加的元素。 - 例如,如果你有一個?
byte::vector<int>
?對象?v
,并調用?v.resize(10)
,那么?resize
?函數會將?v
?的大小改變為?10
,并使用?int
?類型的默認值?0
?來填充新添加的元素。這和?f<int>()
?函數打印?int
?類型的默認值?0
?是一樣的。
?內置類型的默認初始化和直接初始化。
void test_vector2(){// 內置類型有沒有構造函數int i = int();int j = int(1);f<int>();f<int*>();f<double>();}
-
int i = int();?使用值初始化,將?i?初始化為零。int j = int(1);?使用直接初始化,將?j?初始化為?1。
-
分別使用?int、int* 和?double?作為模板參數調用了?f<T>()?函數。這將分別打印?int、int* 和?double?類型的默認值,即?0、nullptr?和?0。
10、insert
iterator insert(iterator pos, const T& val)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 擴容后更新pos,解決pos失效的問題pos = _start + len;}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}
函數接受兩個參數,第一個參數?pos
?是一個迭代器,表示要插入元素的位置,第二個參數?val
?是要插入的元素的值。
函數的實現分為以下幾個步驟:
-
首先,使用?
assert
?斷言來確保?pos
?是一個有效的位置,即?pos
?必須在?_start
?和?_finish
?之間。 -
然后,檢查是否有足夠的空間來插入新的元素。如果?
_finish
?等于?_end_of_storage
,表示當前的內存已經用完,需要重新分配內存。這時,會調用?reserve
?函數來重新分配內存,新的容量是當前容量的兩倍,如果當前容量為?0
,則新的容量為?4
。然后,更新?pos
?的值,因為重新分配內存后,原來的?pos
?可能已經失效。 -
接下來,從?
_finish-1
?開始,將每個元素向后移動一位,直到?pos
?的位置,為插入新的元素騰出空間。 -
然后,將?
val
?的值賦給?*pos
,即在?pos
?的位置插入新的元素。 -
最后,將?
_finish
?向后移動一位,表示?vector
?的大小增加了一個元素。 -
函數返回插入新元素的位置?
pos
。
迭代器失效問題
- 在 `byte::vector` 類的 `insert` 函數中,如果需要重新分配內存(即 `_finish+ + == _end_of_storage`),那么所有指向原來內存的迭代器都會失效。這是因為 `reserve` 函數會申請新的內存,復制原來的元素到新的內存,然后釋放原來的內存。這個過程會導致原來的內存地址不再有效,因此所有指向原來內存的迭代器都會失效。
- 在這個函數中,`pos` 是一個迭代器,它指向要插入新元素的位置。如果在插入新元素之前需要重新分配內存,那么 `pos` 就會失效。為了解決這個問題,函數在重新分配內存后,會根據 `pos` 原來的位置(即 `len = pos - _start`)來更新 `pos` 的值(即 `pos = _start + len`)。這樣,`pos` 就會指向新內存中相同的位置。
- 所以,如果你在調用 `insert` 函數之后還需要使用原來的迭代器,你需要注意迭代器可能已經失效。你可以在插入新元素后,重新獲取迭代器的值。例如,如果你在插入新元素后,想要訪問新元素,這里不能常量pos使用引用傳值,你可以使用 `insert` 函數的返回值,它返回的是插入新元素的位置。這時外部插入元素后? (*pos)++; 可以正常運行了。
11、erase
我們先看這個版本的erase:
void erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;
}
?當我們運行以下代碼程序VS會報錯,linux下g++不會報錯。
void test4(){std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;auto pos = find(v1.begin(), v1.end(), 2);if (pos != v1.end()){v1.erase(pos);}(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}
}
VS下:?
g++下:
這段代碼中,v1.erase(pos)
?會刪除?vector
?中的一個元素,這會導致?pos
?以及所有在?pos
?之后的迭代器失效。然后,代碼試圖通過?(*pos)++
?訪問和修改已經失效的迭代器?pos
,這是未定義行為,可能會導致程序崩潰或其他錯誤。
至于為什么 Visual Studio(VS) 會報錯,而 g++ 不會報錯,這主要是因為不同的編譯器對未定義行為的處理方式不同。VS 的調試模式下對迭代器進行了更嚴格的檢查,當你試圖訪問失效的迭代器時,它會立即報錯。而 g++ 在默認設置下可能不會進行這樣的檢查,所以它可能不會立即報錯,但這并不意味著這段代碼是正確的。
下面第一種情況刪除非末尾元素時,VS的報錯沒有意義,但在第二種情況下,VS的報錯就非常有意義了。?
為了避免這種問題,你應該在刪除元素后,不再使用已經失效的迭代器。如果你需要在刪除元素后繼續訪問?vector
,你應該在刪除元素后重新獲取迭代器的值。例如,vector::erase
?函數會返回一個指向被刪除元素之后的元素的迭代器,你可以使用這個返回值來更新?pos
?。
正確版本:?
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;
}
我們來測試一下刪除偶數:
void test5()
{byte::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;//要求刪除所有偶數byte::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it=v1.erase(it);}else{++it;}}for (auto e : v1){cout << e << " ";}cout << endl;
}
?
12、帶參初始化
?一定要對_start、_finish、_out_of_storage進行初始化,不初始化默認隨機值。?
vector(size_t n, const T& value = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(n);while (n--){push_back(value);}
}
這個構造函數創建一個包含?n
?個元素的?vector
,每個元素都初始化為?value
。value
?參數有一個默認值,即?T()
,它是?T
?類型的默認構造值。
_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
: 這一行初始化三個迭代器,它們分別指向數組的開始、當前最后一個元素之后的位置,和分配的內存末端。初始化為?nullptr
?表示開始時沒有分配任何內存。reserve(n)
: 這個函數調用會分配足夠容納?n
?個元素的內存,但不會創建任何元素。while (n--) { push_back(value); }
: 這個循環會不斷地添加?value
?到?vector
?中,直到添加了?n
?個元素。push_back
?函數會在?vector
?的末尾添加一個新元素,并可能會增加?vector
?的容量(如果需要)。
為什么對 T& 前面要加 const ?
- 匿名對象聲明周期只在當前一行,因為這行之后沒人會用它了。
- const引用會延長匿名對象的聲明周期到引用對象域結束,因為以后用xx就是用匿名對象。
13、迭代器初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
這個構造函數使用兩個迭代器?first
?和?last
,它們分別指向輸入序列的開始和結束,來初始化?vector
。這個構造函數可以用于從任何可迭代的容器(如另一個?vector
、列表、數組等)復制元素。
- 在這個構造函數中,沒有顯式地調用?
reserve
?來預分配內存。這意味著每次用?push_back
?時,如果當前容量不足以容納新元素,就會自動進行內存重新分配。 while (first != last) { push_back(*first); ++first; }
: 這個循環會遍歷輸入序列的每個元素,從 first 開始,一直到達 last(但不包括 last),并使用每個元素的值調用 push_back,將其添加到 vector 中。
?但是對于這句代碼編譯之后會報錯:
vector<int> v1(10, 5);
?這是因為這段代碼在vector(InputIterator first, InputIterator last)和vector(size_t n, const T& value = T())同時存在時,會優先調用前者,但調研之后在函數內部first的模板類型為int,而*first為對int類型解引用,所以這樣報錯了。
我們只要添加一個int類型重載函數即可解決。
vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i < n; ++i){push_back(val);}
}
?這種情況在不加上上述函數可以正常使用,調用vector(size_t n, const T& value = T())。
vector<int> v1(10u, 5);
14、析構函數
~vector()
{delete[] _start;_start = _finish = _end_of_storage = nullptr;
}
完整版代碼&測試代碼
#pragma once
#include<assert.h>
namespace byte
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}void resize(size_t n, T val = T()){if (n < size()){_finish = _start + n;}else {if (n < capacity())reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}vector(size_t n, const T& value = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);while (n--){push_back(value);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; ++i){push_back(val);}}template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}}void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}void pop_back(const T& x){assert(!empty());--_finish;}void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}bool empty(){return _start == _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];}private:iterator _start;iterator _finish;iterator _end_of_storage;};void func(const vector<int>& v){for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl << endl;}void test1(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;}void test2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);cout << v1.size() << endl;cout << v1.capacity() << endl;v1.resize(10);cout << v1.size() << endl;cout << v1.capacity() << endl;func(v1);v1.resize(3);func(v1);}void test3(){std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;/*v1.insert(v1.begin(), 0);for (auto e : v1){cout << e << " ";}cout << endl;*/auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){//v1.insert(pos, 30);pos = v1.insert(pos, 30);}for (auto e : v1){cout << e << " ";}cout << endl;// insert以后我們認為pos失效了,不能再使用(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}void test4(){std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;//auto pos = find(v1.begin(), v1.end(), 2);auto pos = find(v1.begin(), v1.end(), 4);if (pos != v1.end()){v1.erase(pos);}(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}void test5(){byte::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;//要求刪除所有偶數byte::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it=v1.erase(it);}else{++it;}}for (auto e : v1){cout << e << " ";}cout << endl;}void test6(){vector<int> v1(10, 5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2(v1.begin() + 1, v1.end() - 1);for (auto e : v2){cout << e << " ";}cout << endl;std::string s1("hello");vector<int> v3(s1.begin(), s1.end());for (auto e : v3){cout << e << " ";}cout << endl;int a[] = { 100, 10, 2, 20, 30 };vector<int> v4(a, a + 3);for (auto e : v4){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 10);for (auto e : v1){cout << e << " ";}cout << endl;}
}