vector的介紹
- vector是表示可變大小數(shù)組的序列容器。
- 就像數(shù)組一樣,vector也采用的連續(xù)存儲(chǔ)空間來(lái)存儲(chǔ)元素。也就是意味著可以采用下標(biāo)對(duì)vector的元素進(jìn)行訪問(wèn),和數(shù)組一樣高效。但是又不像數(shù)組,它的大小是可以動(dòng)態(tài)改變的,而且它的大小會(huì)被容器自動(dòng)處理。
- 本質(zhì)講,vector使用動(dòng)態(tài)分配數(shù)組來(lái)存儲(chǔ)它的元素。當(dāng)新元素插入時(shí)候,這個(gè)數(shù)組需要被重新分配大小為了增加存儲(chǔ)空間。其做法是,分配一個(gè)新的數(shù)組,然后將全部元素移到這個(gè)數(shù)組。就時(shí)間而言,這是一個(gè)相對(duì)代價(jià)高的任務(wù),因?yàn)槊慨?dāng)一個(gè)新的元素加入到容器的時(shí)候,vector并不會(huì)每次都重新分配大小。
- vector分配空間策略:vector會(huì)分配一些額外的空間以適應(yīng)可能的增長(zhǎng),因?yàn)榇鎯?chǔ)空間比實(shí)際需要的存儲(chǔ)空間更大。不同的庫(kù)采用不同的策略權(quán)衡空間的使用和重新分配。但是無(wú)論如何,重新分配都應(yīng)該是對(duì)數(shù)增長(zhǎng)的間隔大小,以至于在末尾插入一個(gè)元素的時(shí)候是在常數(shù)時(shí)間的復(fù)雜度完成的。
- 因此,vector占用了更多的存儲(chǔ)空間,為了獲得管理存儲(chǔ)空間的能力,并且以一種有效的方式動(dòng)態(tài)增長(zhǎng)。
- 與其它動(dòng)態(tài)序列容器相比(deque, list and forward_list), vector在訪問(wèn)元素的時(shí)候更加高效,在末尾添加和刪除元素相對(duì)高效。對(duì)于其它不在末尾的刪除和插入操作,效率更低。比起list和forward_list統(tǒng)一的迭代器和引用更好
vector的使用及其實(shí)現(xiàn)
vector的定義
(constructor)構(gòu)造函數(shù)聲明 | 接口說(shuō)明 |
---|---|
vector()(重點(diǎn)) | 無(wú)參構(gòu)造 |
vector(size_type n, const value_type& val = value_type()) | 構(gòu)造并初始化n個(gè)val |
vector (const vector& x); (重點(diǎn)) | 拷貝構(gòu)造 |
vector (InputIterator first, InputIterator last); | 使用迭代器進(jìn)行初始化構(gòu)造 |
int main()
{
vector<int> v1;//無(wú)參構(gòu)造
vector<int> v2(5, 3);
for (auto ch : v2)
{
cout << ch << " ";
}
cout << endl;
vector<int> v3(v2);
//拷貝構(gòu)造是定義一個(gè)新的,賦值是已經(jīng)定義好了一個(gè)了
for (auto ch : v3)
{
cout << ch << " ";
}
cout << endl;
vector<int> v4(v3.begin()+ 1, v3.end()- 1);
for (auto ch : v4)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
模擬實(shí)現(xiàn),需要注意的地方注釋在代碼中了
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
vector(size_t n,const T& val=T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
//1.vector(size_t n,const T& val=T())
//2.vector<int> v1(10, 5);
//3.template <class InputIterator>
//vector(InputIterator first, InputIterator last)
//上面的三行注釋的代碼,1和2互相沖突,2會(huì)優(yōu)先訪問(wèn)3,它們的類型更加匹配,
//編譯器會(huì)優(yōu)先找與自己更加合適的人匹配
//錯(cuò)誤非法的間接尋址
//解決辦法可以在重載一個(gè)構(gòu)造函數(shù),將size_t改成int
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//vector(const vector<T>& v)
//{
// _start = new T[v.capacity()];
// //memcpy(_start, v._start, sizeof(T)*v.size());
// //因?yàn)閙emcpy也是進(jìn)行淺拷貝,vector<string>
// for (size_t i = 0; i < v.size(); ++i)
// {
// _start[i] = v._start[i];
// //
// }
// _finish = _start + v.size();
// _end_of_storage = _start + v.capacity();
//}
//這里使用上面被注釋的代碼,拷貝構(gòu)造也是可以的,上面的更加規(guī)范
//注意memcpy是不能隨便用的,會(huì)造成淺拷貝
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//注意下面是傳值,臨時(shí)拷貝,所以交換時(shí)將他修改也是無(wú)所謂的,
//也并不會(huì)影響v2的結(jié)果!?。。。。。。。。。。?!
//v1=v2
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
從上面的兩張圖片當(dāng)中,在實(shí)現(xiàn)拷貝構(gòu)造和賦值重載時(shí)是可以將vector<T>
替換為vector
在這里介紹一個(gè)自己當(dāng)時(shí)的疑問(wèn),為什么模板函數(shù)不能直接代替
vector<int>
,
vector(const vector<T>& v
)
vector(int n,const T& val = T()
)
問(wèn)題也是從上面的兩個(gè)函數(shù)參數(shù)得來(lái)的,在第二個(gè)當(dāng)中就能代替vector<int>
,而上面的卻不能。
T是vector存儲(chǔ)的數(shù)據(jù)類型,拷貝構(gòu)造是要用已有的vector對(duì)象構(gòu)造新的vector,所以類型應(yīng)該是const vector<T>
的
vector iterator 的使用
iterator的使用 | 接口說(shuō)明 |
---|---|
begin + end(重點(diǎn)) | 獲取第一個(gè)數(shù)據(jù)位置的iterator/const_iterator, 獲取最后一個(gè)數(shù)據(jù)的下一個(gè)位置的iterator/const_iterator |
rbegin + rend | 獲取最后一個(gè)數(shù)據(jù)位置的reverse_iterator,獲取第一個(gè)數(shù)據(jù)前一個(gè)位置的reverse_iterator |
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
vector<int>::reverse_iterator rit = v1.rbegin();
while (rit !=v1.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
}
相關(guān)模擬實(shí)現(xiàn),反向迭代器會(huì)在后期實(shí)現(xiàn)的。
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;
}
vector空間增長(zhǎng)問(wèn)題
容量空間 | 接口說(shuō)明 |
---|---|
size | 獲取數(shù)據(jù)個(gè)數(shù) |
capacity | 獲取容量大小 |
empty | 判斷是否為空 |
resize(重點(diǎn)) | 改變vector的size |
reserve (重點(diǎn)) | 改變vector的capacity |
- capacity的代碼在vs和g++下分別運(yùn)行會(huì)發(fā)現(xiàn),vs下capacity是按1.5倍增長(zhǎng)的,g++是按2倍增長(zhǎng)的。
這個(gè)問(wèn)題經(jīng)常會(huì)考察,不要固化的認(rèn)為,vector增容都是2倍,具體增長(zhǎng)多少是根據(jù)具體的需求定義
的。vs是PJ版本STL,g++是SGI版本STL。- reserve只負(fù)責(zé)開(kāi)辟空間,如果確定知道需要用多少空間,reserve可以緩解vector增容的代價(jià)缺陷問(wèn)
題。- resize在開(kāi)空間的同時(shí)還會(huì)進(jìn)行初始化,影響size。
// 測(cè)試vector的默認(rèn)擴(kuò)容機(jī)制
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
vs:運(yùn)行結(jié)果:vs下使用的STL基本是按照1.5倍方式擴(kuò)容
making foo grow :
capacity changed : 1
capacity changed : 2
capacity changed : 3
capacity changed : 4
capacity changed : 6
capacity changed : 9
capacity changed : 13
capacity changed : 19
capacity changed : 28
capacity changed : 42
capacity changed : 63
capacity changed : 94
capacity changed : 141
g++運(yùn)行結(jié)果:linux下使用的STL基本是按照2倍方式擴(kuò)容
making foo grow :
capacity changed : 1
capacity changed : 2
capacity changed : 4
capacity changed : 8
capacity changed : 16
capacity changed : 32
capacity changed : 64
capacity changed : 128
// 如果已經(jīng)確定vector中要存儲(chǔ)元素大概個(gè)數(shù),可以提前將空間設(shè)置足夠
// 就可以避免邊插入邊擴(kuò)容導(dǎo)致效率低下的問(wèn)題了
void TestVectorExpandOP()
{
vector<int> v;
size_t sz = v.capacity();
v.reserve(100); // 提前將容量設(shè)置好,可以避免一遍插入一遍擴(kuò)容
cout << "making bar grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
下面為相關(guān)操作
int main()
{
vector<int> v1(5, 3);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
for (auto ch : v1)
{
cout << ch << " ";
}
cout << endl;
v1.reserve(10);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
for (auto ch : v1)
{
cout << ch << " ";
}
cout << endl;
v1.resize(20,1);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
for (auto ch : v1)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
模擬實(shí)現(xiàn)
bool empty() const
{
return _start == _finish;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = size();//提前做好記錄,防止更改地址
if (_start)//判斷一下,如果為空,就可以省去這步
{
//memcpy(tmp, _start, sizeof(T) * n);
//在擴(kuò)容這里也是有問(wèn)題的,因?yàn)樾枰獙?shù)據(jù)移過(guò)來(lái)
//如果只是進(jìn)行淺拷貝,在執(zhí)行delete[] _start;時(shí)數(shù)據(jù)會(huì)銷毀
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
//開(kāi)新的空間地址會(huì)更改,_strat,_finsh,_end_of_storage都會(huì)改變,***
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize(size_t n, T val = T())
{//這里的第二個(gè)參數(shù)是可以為任意類型
if (n < size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val;
_finish++;
}
}
}
vector的增刪查改
vector增刪查改 | 接口說(shuō)明 |
---|---|
push_back(重點(diǎn)) | 尾插 |
pop_back (重點(diǎn)) | 尾刪 |
find | 查找。(注意這個(gè)是算法模塊實(shí)現(xiàn),不是vector的成員接口) |
insert | 在position之前插入val |
erase | 刪除position位置的數(shù)據(jù) |
swap | 交換兩個(gè)vector的數(shù)據(jù)空間 |
operator[] (重點(diǎn)) | 像數(shù)組一樣訪問(wèn) |
1.2.4 vector 迭代器失效問(wèn)題。(重點(diǎn))
迭代器的主要作用就是讓算法能夠不用關(guān)心底層數(shù)據(jù)結(jié)構(gòu),其底層實(shí)際就是一個(gè)指針,或者是對(duì)指針進(jìn)行了
封裝,比如:vector的迭代器就是原生態(tài)指針T* 。因此迭代器失效,實(shí)際就是迭代器底層對(duì)應(yīng)指針?biāo)赶虻?br> 空間被銷毀了,而使用一塊已經(jīng)被釋放的空間,造成的后果是程序崩潰(即如果繼續(xù)使用已經(jīng)失效的迭代器,
程序可能會(huì)崩潰)。
對(duì)于vector可能會(huì)導(dǎo)致其迭代器失效的操作有:
- 會(huì)引起其底層空間改變的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、
push_back等。
#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> v{ 1,2,3,4,5,6 };
auto it = v.begin();
// 將有效元素個(gè)數(shù)增加到100個(gè),多出的位置使用8填充,操作期間底層會(huì)擴(kuò)容
// v.resize(100, 8);
// reserve的作用就是改變擴(kuò)容大小但不改變有效元素個(gè)數(shù),操作期間可能會(huì)引起底層容量改變
// v.reserve(100);
// 插入元素期間,可能會(huì)引起擴(kuò)容,而導(dǎo)致原空間被釋放
// v.insert(v.begin(), 0);
// v.push_back(8);
// 給vector重新賦值,可能會(huì)引起底層容量改變
v.assign(100, 8);
/*
出錯(cuò)原因:以上操作,都有可能會(huì)導(dǎo)致vector擴(kuò)容,也就是說(shuō)vector底層原理舊空間被釋放掉,
而在打印時(shí),it還使用的是釋放之間的舊空間,在對(duì)it迭代器操作時(shí),實(shí)際操作的是一塊已經(jīng)被釋放的
空間,而引起代碼運(yùn)行時(shí)崩潰。
解決方式:在以上操作完成之后,如果想要繼續(xù)通過(guò)迭代器操作vector中的元素,只需給it重新
賦值即可。
*/
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
- 指定位置元素的刪除操作–erase
#include <iostream>
using namespace std;
#include <vector>
int main()
{
int a[] = { 1, 2, 3, 4 };
vector<int> v(a, a + sizeof(a) / sizeof(int));
// 使用find查找3所在位置的iterator
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
// 刪除pos位置的數(shù)據(jù),導(dǎo)致pos迭代器失效。
v.erase(pos);
cout << *pos << endl; // 此處會(huì)導(dǎo)致非法訪問(wèn)
return 0;
}
erase刪除pos位置元素后,pos位置之后的元素會(huì)往前搬移,沒(méi)有導(dǎo)致底層空間的改變,理論上講迭代
器不應(yīng)該會(huì)失效,但是:如果pos剛好是最后一個(gè)元素,刪完之后pos剛好是end的位置,而end位置是
沒(méi)有元素的,那么pos就失效了。因此刪除vector中任意位置上元素時(shí),vs就認(rèn)為該位置迭代器失效
了。
以下代碼的功能是刪除vector中所有的偶數(shù),請(qǐng)問(wèn)那個(gè)代碼是正確的,為什么?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-424201.html
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;
}
return 0;
}
//這段代碼的問(wèn)題出在了,當(dāng)刪除了,最后一個(gè)值,end和it正好錯(cuò)過(guò)
//因此it != v.end()也就無(wú)法達(dá)到
//下面的代碼是解決方法,這也是erase為什么要有返回值的原因
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
it = v.erase(it);
else
++it;
}
return 0;
}
迭代器失效解決辦法:在使用前,對(duì)迭代器重新賦值即可
模擬實(shí)現(xiàn)
在這里要重點(diǎn)
注意一下memcpy
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-424201.html
- memcpy是內(nèi)存的二進(jìn)制格式拷貝,將一段內(nèi)存空間中內(nèi)容原封不動(dòng)的拷貝到另外一段內(nèi)存空間中
- 如果拷貝的是自定義類型的元素,memcpy既高效又不會(huì)出錯(cuò),但如果拷貝的是自定義類型元素,并且
自定義類型元素中涉及到資源管理時(shí),就會(huì)出錯(cuò),因?yàn)閙emcpy的拷貝實(shí)際是淺拷貝。
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(!empty());
_finish--;
}
iterator insert(iterator pos, const T& val)
{
assert(pos <= _finish);
assert(pos >= _start);
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++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos < _finish);
assert(pos >= _start);
iterator start = pos + 1;
while (start != _finish)
{
*(start - 1) = *(start);
start++;
}
_finish--;
return pos;
}
T& operator[](size_t n)
{
assert(n < size());
return _start[n];
}
const T& operator[](size_t n) const
{
assert(n < size());
return _start[n];
}
最后:文章有什么不對(duì)的地方或者有什么更好的寫法歡迎大家在評(píng)論區(qū)指出 |
到了這里,關(guān)于【C++】vector模擬實(shí)現(xiàn)及其應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!