??主要講述vector的模擬實現。
創新互聯專注于木蘭網站建設服務及定制,我們擁有豐富的企業做網站經驗。 熱誠為您提供木蘭營銷型網站建設,木蘭網站制作、木蘭網頁設計、木蘭網站官網定制、微信小程序服務,打造木蘭網絡公司原創品牌,更為您提供木蘭網站排名全網營銷落地服務。文章目錄??同string類模擬實現一致,此處為了解決命名沖突問題,我們使用添加命名空間myvector
的方式來處理。
#pragma once
namespace myvector
{templateclass vector
{typedef T* iterator;//將模板T*命名為迭代器iterator
public:
private:
iterator _start;//起始
iterator _finish;//結束
iterator _end_of_storage;//容量空間
};
}
??由于后續涉及到迭代器問題,若將typedef T* iterator;
定義成私有,則無法在類外很好的使用。此處修改如下:
#pragma once
namespace myvector
{templateclass vector
{ public:
typedef T* iterator;//將模板T*命名為迭代器iterator
typedef const T* const_iterator;
private:
iterator _start;//起始
iterator _finish;//結束
iterator _end_of_storage;//容量空間
};
}
??
??
??1)、構造函數
??對構造函數,我們之前學習時看到其中有內存池的相關內容,此處由于我們暫時沒學習它,故對vector的模擬實現中我們不使用它。
vector() //構造函數
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
??
??2)、析構函數
~vector()//析構函數
{ delete[] _start;//null出來的空間是連續一塊的,以_start為起始點。注意delete的使用方式
_start = _finish = _end_of_storage = nullptr;
}
??
??
??1)、庫函數中聲明回顧
??2)、push_back模擬實現
void push_back(const T& x)
{ //涉及擴容檢查
if (_finish == _end_of_storage)
{ reserve(capacity() == 0?4:capacity() * 2);
}
//尾插數據
*_finish = x;
_finish++;
}
??為什么需要使用引用和const修飾?
??因為這里使用的是T模板參數,我們傳入的值可能是內置類型,也可能是自定義類型,如果是后者,則傳值傳參代價很大。
??
??3)、pop_back模擬實現
void pop_back()
{ assert(_finish >_start);
_finish--;
}
??
??
??
??
??1)、庫函數中聲明回顧
??2)、模擬實現
size_t size()const
{ return _finish - _start;
}
size_t capacity()const
{ return _end_of_storage - _start;
}
void reserve(size_t n)
{ if (n >capacity())//滿足該條件才進行擴容
{ size_t sz = size();//因為后續重新確定指向關系時需要知道size值
T* tmp = new T[n];
if (_start)//說明原先空間有數據,
{memcpy(tmp, _start, sizeof(T) * sz);//需要挪動
delete[] _start;//釋放舊空間
}
//重新確定指向關系
_start = tmp;
_finish = _start + sz;
//_finish = _start + size();//如果是在這里獲取size值,則在原先空間有數據的情況下,_start已經被delete
_end_of_storage = _start + n;
}
}
??
??
??
??1)、庫函數中聲明回顧
??2)、模擬實現
T& operator[](size_t pos)//加&是為了支持可讀可寫
{ assert(pos< size());//檢查下標是否非法
return _start[pos];
}
const T& operator[](size_t pos)const
{ assert(pos< size());
return _start[pos];
}
??
??
??1)、普通迭代器
//vector的迭代器就是原生指針
iterator begin()
{ return _start;
}
iterator end()
{ return _finish;
}
??
??
??2)、const修飾的迭代器
??為什么需要?
??存在如下的情況:const vector
,所創建的vector對象被const修飾,如果直接使用vector
,則屬于權限放大。
typedef const T* const_iterator;
const const_iterator begin()const
{ return _start;
}
const const_iterator end()const
{ return _finish;
}
void Func(const vector& v)
{vector::const_iterator it = v.begin();
while (it != v.end())
{ //*it = 10;//error
cout<< *it<< " ";
++it;
}
cout<< endl;
for (auto e : v)//此處若使用范圍for,const對象會調用對應的const迭代器
{ cout<< e<< " ";
}
cout<< endl;
}
??
??3)、為什么說范圍for是傻瓜式替換?
??只要我們仿照庫中使用對應字符begin、end,則訪問for就能起效。相應的,如果我們使用了Begin、End等其它字母,則在我們模擬的vector中范圍for失效。
??
??
??1)、庫函數中聲明回顧
??2)、insert模擬實現1.0
void insert(iterator pos, const T& val)
{ assert(pos<= _finish && pos >= _start);
//擴容檢查
if (_finish == _end_of_storage)
{ reserve(capacity() == 0 ? 4 : capacity() * 2);
}
//數據挪動
iterator end = _finish - 1;
while (end >= pos)
{ *(end + 1) = *end;
end--;
}
//插入val值
*pos = val;
++_finish;
}
??
??
??3)、insert涉及擴容時迭代器失效問題
??問題:
??
??原因解釋:
??
??
??4)、insert模擬實現2.0
??解決方案: 若擴容,則要順帶更新pos指向位置
//保存二者指針差距
size_t len = pos - _start;
//擴容后更新pos指向
pos = len + _start;
void insert(iterator pos, const T& val)
{ assert(pos<= _finish && pos >= _start);
//擴容檢查
if (_finish == _end_of_storage)
{ //保存二者指針差距
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//擴容后更新pos指向
pos = len + _start;
}
//數據挪動
iterator end = _finish - 1;
while (end >= pos)
{ *(end + 1) = *end;
end--;
}
//插入val值
*pos = val;
++_finish;
}
??
??
??
??1)、問題引入與現象分析
??接上一階段代碼,分析下述情形:
??一開始我們使用find找到了p位置,然后在p位置前插入一次,現在我們變為p位置前連續插入多次。問:是否能成功?
//使用算法中的find
auto p = find(v.begin(), v.end(), 3);
if (p != v.end())//找到了
{ v.insert(p, 30);//1、插入一個30
cout<< *p<< endl;//2、再次來到p的位置
v.insert(p, 40);//3、我們p位置前插入一個40,問:是否能成功?
}
??現象如下:
??
??可能出現疑問如下:我們在3.5.1中解決了insert中pos位置更新的問題,為什么此處p仍舊不起效?
??這里我們需要思考上述寫的insert函數中,形參pos和實參p之間的關系。可知曉的是,在insert函數中,它們是值傳遞,故形參改變不影響實參。
void insert(iterator pos, const T& val)
{ assert(pos<= _finish && pos >= _start);
//擴容檢查
if (_finish == _end_of_storage)
{ //保存二者指針差距
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//擴容后更新pos指向
pos = len + _start;
}
//數據挪動
iterator end = _finish - 1;
while (end >= pos)
{ *(end + 1) = *end;
end--;
}
//插入val值
*pos = val;
++_finish;
}
??
??
??2)、解決方案
??①一個相對比較適合的方法是,在此類情況中,最好別再p位置失效后再去訪問p。
??
??
??②有人可能提出,我們在insert中為pos加上一個引用,即傳引用返回,這樣不就解決了?void insert(iterator& pos, const T& val)
void insert(iterator& pos, const T& val)
{ assert(pos<= _finish && pos >= _start);
//擴容檢查
if (_finish == _end_of_storage)
{ //保存二者指針差距
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//擴容后更新pos指向
pos = len + _start;
}
//數據挪動
iterator end = _finish - 1;
while (end >= pos)
{ *(end + 1) = *end;
end--;
}
//插入val值
*pos = val;
++_finish;
}
??事實上這樣做,針對insert而言確實可以解決問題,但同樣會面臨新的問題。
??第一,這樣的模擬實現與庫中不匹配,庫中直接使用iterator pos
;
??第二,這樣做會帶來新的問題,如下:
v.insert(v.begin(), 1);
??此段代碼無法編譯通過。因為begin模擬實現時,我們使用的是iterator傳值返回,中間會生成一份臨時變量,具有常性,后續insert處權限放大。
????Ⅰ、若為insert加上const,那么又無法解決修改問題。
????Ⅱ、而如果將begin也寫為傳引用返回,iterator& begin()
,這樣會使得begin具有修改能力,反而增添麻煩。
iterator begin()
{ return _start;
}
??
??
??3)、思考:上述值傳遞中,p一定存在迭代器失效問題嗎?
??回答:同3.5.2中所講,在涉及擴容問題時,p才存在失效。
??演示:如下述,假如我們一開始插入5個數值,容量空間足夠的情況下,此處不存在p失效問題。
??
??
??4)、insert為push_back提供復用
void push_back(const T& x)
{ insert(end(), x);
}
??
??
??5)、insert模擬實現3.0:案例演示
??案例要求: 在所有的偶數前插入該偶數的二倍值。
??代碼演示+現象分析:
??我們以上述insert2.0版本為例進行演示,順帶再來回顧一下迭代器失效問題。
void test_vector5()
{std::vectorv;
v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
//在所有偶數前插入該偶數的2倍
auto it = v.begin();
while (it != v.end())
{ if (*it % 2 == 0)
{ v.insert(it, *it * 2);
}
++it;
}
}
??上述代碼我們直接運行則程序崩潰無輸出結果,調試后發現如下:it始終指向2的位置。這就是迭代器失效的另一種模式:因為數據挪動,導致外部指針指向錯亂。
??ps: 迭代器失效的第一種模式:函數內擴容,導致野指針。
??
??為了解決上述問題,也一并解決insert中擴容后外部迭代器失效問題,一個方案如下:
????Ⅰ、對insert
函數,模仿庫中帶上iterator
返回值;
????Ⅱ、對原先的二倍插入循環,需要更新it
指向的新位置,旨在解決擴容后外部迭代器失效問題。
iterator insert(iterator pos, const T& val)
{ assert(pos<= _finish && pos >= _start);
//擴容檢查
if (_finish == _end_of_storage)
{ //保存二者指針差距
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//擴容后更新pos指向
pos = len + _start;
}
//數據挪動
iterator end = _finish - 1;
while (end >= pos)
{ *(end + 1) = *end;
end--;
}
//插入val值
*pos = val;
++_finish;
return pos;
}
void test_vector5()
{std::vectorv;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
//在所有偶數前插入該偶數的2倍
auto it = v.begin();
while (it != v.end())
{ if (*it % 2 == 0)
{ it=v.insert(it, *it * 2);
++it;
}
++it;
}
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
}
??演示結果:
??
??
??
??1)、erase模擬實現1.0
void erase(iterator pos)
{ assert(pos< _finish && pos >= _start);
iterator end = pos + 1;
while (end< _finish)
{ *(end - 1) = *end;
++end;
}
--_finish;
}
??
??2)、erase是否會導致迭代器失效?
??①主要看編譯器如何實現erase函數,不排除有些編譯器以時間換空間進行縮容:
??
if (size()< capacity()/2)
{ // 縮容 -- 以時間換空間
}
??
??
??②其它案例演示:刪除vector中的偶數
??使用代碼如下:
void erase(iterator pos)
{ assert(pos< _finish && pos >= _start);
iterator end = pos + 1;
while (end< _finish)
{ *(end - 1) = *end;
++end;
}
--_finish;
}
void test_vector4()
{vectorv;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
//刪除所有偶數
auto it = v.begin();
while (it != v.end())
{ if(*it % 2 == 0)
{ v.erase(it);
}
++it;
}
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
}
??現象如下:
??
??
??
??
??3)、erase模擬實現2.0
iterator erase(iterator pos)
{ assert(pos< _finish && pos >= _start);
iterator end = pos + 1;
while (end< _finish)
{ *(end - 1) = *end;
++end;
}
--_finish;
//if (size()< capacity()/2)
//{ // // 縮容 -- 以時間換空間
//}
return pos;
}
??基于2.0版
本的erase
我們再來修改上述 2) 中的題目:
void test_vector4()
{vectorv;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
//刪除所有偶數
auto it = v.begin();
while (it != v.end())
{ if(*it % 2 == 0)
{ it=v.erase(it);
}
else { ++it;
}
}
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
}
??
??
??
??需要注意的是,上述我們對迭代器失效的問題演示,其結果是未定義的,因為針對不同平臺其STL底層實現并不一致。
??即,STL只是一個規范,其細節如何實現不做要求。
??VS:PJ版。
??g++:SGI版。
??
??
??
3.6、vector::resize??1)、庫函數中聲明回顧
??
??2)、模擬實現
??根據上述可知,resize
面臨三種情況:
????Ⅰ、當n>capacity
:擴容+使用val初始化;
????Ⅱ、當size
????Ⅲ、當n
??
??模擬實現如下:
void resize(size_t n, const T& val=T())
{ if (n >capacity())
{ reserve(n);
}
if (n >size())//兩種情況:n>capacity、size //只需要初始化即可
while (_finish< _start + n)
{push_back(val);
++_finish;
}
}
else
{ _finish = _start + n;
}
}
??演示驗證一:
void test_vector11()
{//resize常用場景:在生成對象后開辟空間
vectorv;
v.resize(5);//驗證默認缺省值
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
v.resize(10, 2);
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
}
??
??演示驗證二:
void test_vector11()
{vectorv1;
v1.reserve(10);
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.resize(8,8);//由大到小,而值只有5個,會添3個值
for (auto e : v1)
{ cout<< e<< " ";
}
cout<< endl;
}
??
??
??
??
??1)、庫函數中聲明回顧
??
??2)、模擬實現
T& front()
{ assert(size() >0);
return *_start;
}
T& back()
{ assert(size() >0);
return *(_finish - 1);
}
??
??
??
??
??1)、知識回顧
??在類和對象章節,我們曾說明:拷貝構造對于內置類型完成淺拷貝/值拷貝,對于自定義類型則會調用它對應的拷貝構造。
??
??
??2)、vector拷貝構造分析
??vector的成員變量是內置類型,故編譯器默認生成的拷貝構造函數完成的是淺拷貝。
??PS:typedef T* iterator;
、此處盡管iterator
是類模板,且T*
會存在自定義類型的指針,但其仍舊是內置類型。
private:
iterator _start;//起始
iterator _finish;//結束
iterator _end_of_storage;//容量空間
??以如下代碼進行拷貝構造:
vectorv;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vectorv2(v);
for (auto e : v2)
{ cout<< e<< " ";
}
cout<< endl;
??淺拷貝帶來的兩個問題:
????Ⅰ、析構兩次
????Ⅱ、一個對象的修改,會影響另一個對象
??
??
??
??
vector(const vector& v)
{ _start = new T[v.size()];//此處也可以開辟v.capacity()大小的空間,各有各的優缺點
memcpy(_start, v._start, sizeof(T)*v.size());//照搬數據
_finish = _start + v.size();
_end_of_storage = _start + v.size();//此處this._finish的大小根據上述我們開辟空間時的選擇而變動
}
??
??
vector(const vector& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{ reserve(v.size());
for (const auto& e : v)
{ push_back(e);//this.push_back()
}
}
??
??
??
??1)、vector構造函數:使用迭代器區間構造1.0
??涉及問題:
??Ⅰ、能否雙模板嵌套式使用?
??Ⅱ、為什么需要新定義一個模板InputIterator
,而不使用原先的Iterator
?
templatevector(InputIterator first, InputIterator last)
{ while (first != last)
{ push_bakc(*first);
++first;
}
}
void test_vector7()
{string str("hello world");
vectorv(str.begin(), str.end());//使用迭代器區間構造
for (auto e : v)
{ cout<< e<< " ";
}
cout<< endl;
}
??上述代碼是否會存在什么問題?
??回答:_start
、_finish
、_end_of_storage
沒有初始化,在有些編譯器下其是隨機值,那么reserve
開辟空間時,此處非空就會拷貝數據、釋放空間、存在越界問題。
??
??2)、vector構造函數:使用迭代器區間構造2.0
??_start
、_finish
、_end_of_storage
初始化為空。
//使用迭代區間的構造函數:含類模板
templatevector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{ while (first != last)
{ push_back(*first);
++first;
}
}
??
??
void swap(vector& v)
{ std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v.__end_of_storage);
}
vector(const vector& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{ vectortmp(v.begin(), v.end());
swap(tmp);
}
??
??
??
??
??1)、模擬實現及細節解析
vector(size_t n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{ reserve(n);
for (size_t i = 0; i< n; ++i)
{ push_back(val);
}
}
??對const T& val = T()
,實際上這個函數使用了半缺省參數,其缺省值是T()
,一個T類型的匿名對象。
????Ⅰ、假若T()
是自定義類型,則調用自定義類型的默認構造(事實上內置類型也有模板參數)
????Ⅱ、假若T()
是內置類型,因C++中模板的出現,也擁有對應的默認構造函數,此處以int舉例。
void test_vector9()
{int i=11;
int j = int();
int k(10);
cout<< "i:"<< i<< endl;
cout<< "j:"<< j<< endl;
cout<< "k:"<< k<< endl;
}
??
??
??2)、演示
void test_vector10()
{vectorv1(10);//驗證默認缺省值
for (auto e : v1)
{ cout<< e<< " ";
}
cout<< endl;
vectorv2(5);
for (auto e : v2)
{ cout<< e<< " ";
}
cout<< endl;
}
??
??
??3)、一個錯誤說明
vectorv1(10,1);//驗證默認缺省值
for (auto e : v1)
{ cout<< e<< " ";
}
cout<< endl;
??為什么在該函數中輸入兩個int類型的變量會顯示如下錯誤,且報錯還是報在我們之前寫的迭代器區間構造上?
??原因解釋:類型匹配。相比于(size_t
、T
)類型,(InputIterator
、InputIterator
)更匹配(int、int)
。
??
??解決方法:
??①強制類型轉換:vector
;
??②修改函數形參類型:vector(int n, const T& val = T())
??③使用函數重載:
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{ reserve(n);
for (size_t i = 0; i< n; ++i)
{ push_back(val);
}
}
??
??
//賦值v1=v2
vector& operator=(vectorv)
{ swap(v);
return *this;
}
??
??
??
??1)、問題引入
??在vector(一)中,我們曾寫過楊輝三角:其涉及到了vector
嵌套使用。
class Solution {public:
vector>generate(int numRows) {vector>vv;//定義一個vector>類型的數據
vv.resize(numRows);//第一次開辟空間:numRows,表示總行數(整體大小)
for(size_t i=0;ivv[i].resize(i+1,0);//第二次開辟空間,表示初始化楊輝三角的每行大小
vv[i].front()=vv[i].back()=1;//楊輝三vv.size()角每行首尾數據為1
//vv[i].resize(i+1,1);//上述代碼也可以合并為一行實現
}
for(size_t i=2;ifor(size_t j=1;jvv[i][j]=vv[i-1][j-1]+vv[i-1][j];
}
}
return vv;
}
};
vector>ret = Solution().generate(5);
??在使用std中的vector時,這段代碼成功運行。而將其放入我們自己實現的vector中,則會發現運行失敗。
??為什么我們自己的模擬實現的vector會失敗呢?
??Ⅰ、對vector
,如果我們不傳值返回ector
則運行成功,而傳值返回運行失敗。需要注意的是,此處傳值返回涉及自定義類型,存在數據拷貝的問題。
??Ⅱ、基于此我們調試發現:外層的vector深拷貝成功(值一致、地址空間不一致),而內存的vector居然是淺拷貝。
??以上是現象,現在來分析情況:
??①自定義類型傳值返回,中間生成一個臨時變量,涉及深淺拷貝問題。
??②拷貝構造我們模擬實現了兩類,一類是傳統寫法,使用了memcpy,由上述圖二可知,memcpy是淺拷貝,那么就涉及到同一空間析構兩次的問題。修改方法如下:
vector(const vector& v)//拷貝構造傳統寫法
{ _start = new T[v.size()];//此處也可以開辟v.capacity()大小的空間,各有各的優缺點
//memcpy(_start, v._start, sizeof(T)*v.size());//照搬數據
for (size_t i = 0; i< sz; i++)//照搬數據
{ _start[i] = v._start[i];//此處假若是自定義類型,則是賦值運算符
}
_finish = _start + v.size();
_end_of_storage = _start + v.size();//此處this._finish的大小根據上述我們開辟空間時的選擇而變動
}
??③假若使用的是現代寫法,在我們寫的兩個以swap為交換的拷貝構造,都沒問題。此處出現問題的是擴容。原先我們寫的擴容函數中用了memcpy,同樣是淺拷貝導致。修改方法如下:
void reserve(size_t n)
{ if (n >capacity())//滿足該條件才進行擴容
{ size_t sz = size();//因為后續重新確定指向關系時需要知道size值
T* tmp = new T[n];
if (_start)//說明原先空間有數據,
{//memcpy(tmp, _start, sizeof(T) * sz);//需要挪動
for (size_t i = 0; i< sz; i++)//需要挪動
{tmp[i] = _start[i];
//*(tmp + i) = *(_start + i);
}
delete[] _start;//釋放舊空間
}
//重新確定指向關系
_start = tmp;
_finish = _start + sz;
//_finish = _start + size();//如果是在這里獲取size值,則在原先空間有數據的情況下,_start已經被delete
_end_of_storage = _start + n;
}
}
??
??
??
??
??
??
??
??
??
你是否還在尋找穩定的海外服務器提供商?創新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統配攻擊溯源,準確流量調度確保服務器高可用性,企業級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧
本文題目:【ONE·C++||vector(二)】-創新互聯
新聞來源:http://vcdvsql.cn/article16/dsdedg.html
成都網站建設公司_創新互聯,為您提供微信公眾號、企業網站制作、網站建設、定制網站、品牌網站設計、外貿網站建設
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯