vector模擬實現
- 一、看源代碼
- 簡單實現
- 1. push_back
- capacity(容量)
- size
- reserve(擴容)
- operator[ ] (元素訪問)
- 2. pop_back
- 3. itorator(迭代器)
- 4.insert & erase (頭插頭刪)
- 5. 拷貝構造和析構函數
- default關鍵字(強制編譯器生成)
- 其他問題
一、看源代碼
- 在我們自己實現 vector 的時候,我們可以參考 vector 的源代碼
- 大致功能初步了解
-
- 成員變量
-
- 核心成員函數
- 根據名字連蒙帶猜,通過時間看源碼細節確認
我們自定義的成員變量:
template<class T>
class vector
{
public:private:T* _a;size_t _size;size_t _capacity;
};
修改后:
namespace bit //同一個域內,就不會和編譯器里面的vector弄混
{template<class T>class vector{public:typedef T* iterator;private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
簡單實現
前情提要:我們分離定義不分離是因為分離會出現連接問題,這個我們后面會提到
1. push_back
下面是push-back的大致框架:
void push_back(const T& x)
{//如果滿了就擴容if(_finish == _end_of_storage){//擴容}
}
注意:在這里我們還要實現三個前提函數:capacity()、size()、reserve()
capacity(容量)
size_t capacity()
{return _end_of_storage - _start;
}
size
size_t size()
{return _finish - _start;
}
reserve(擴容)
void reserve(size_t n)
{//直接擴容if (n > capacity()){T* tmp = new T[n]; //開辟空間memcpy(tmp, _start, sizeof(T) * size()); //拷貝delete[] _start; //釋放舊空間_start = tmp; //指向新空間}_finish = _start + size();_end_of_storage = _start + n;
}
?通過以上代碼,我們就可以開始實現👇
void push_back(const T& x)
{//如果滿了就擴容if (_finish == _end_of_storage){//如果capacity 是 0 那么就給四個空間,不是就乘二倍size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;
}
operator[ ] (元素訪問)
T& operator[](size_t i)
{assert(i < size());//斷言檢查越界情況return _start[i];
}
問題一:測試會發現,我們沒有包iostream頭文件,所以cout無法使用
- 這里就涉及到一個問題 頭文件 .h 在 .cpp 文件里面會展開
所以當我們在Test.cpp里面展開vector.h時,又可以使用了
- 因為展開時他會向上查找
但但但是,又有一個問題,運行報錯了
- 空指針問題,通過測試,我們可以發現的是,size算法出現問題
start 是新的 但是 finish 是舊的
當我們重新擴容之后,_start == tmp , 而_ finish還是原來的那個t
- 修改后代碼如下:
void reserve(size_t n)
{//直接擴容if (n > capacity()){size_t oldsize = size();T* tmp = new T[n]; //開辟空間if (_start) {memcpy(tmp, _start, sizeof(T) * size()); //拷貝delete[] _start; //釋放舊空間 }_start = tmp; //指向新空間_finish = tmp + oldsize;_end_of_storage = _start + n;}}
2. pop_back
那么這個就比較簡單了,代碼實現如下👇:
void pop_back()
{assert(size() > 0);--_finish
}
3. itorator(迭代器)
- 在沒有迭代器的情況下時,我們是不能使用范圍for的
迭代器代碼實現👇
typedef T* iterator;
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
- 當然,也有const迭代器
是指迭代器指向的內容不可修改
typedef const T* const_iterator;iterator begin() const
{return _start;
}
iterator end() const
{return _finish;
}
4.insert & erase (頭插頭刪)
void test_vector3()
{bit::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.insert(v1.begin(), 0); //頭插v1.erase(v1.begin()); //頭刪
}
運行結果:
?insert的的實現
void insert(iterator pos, const T& x)
{if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}
注意:這里的擴容會導致迭代器失效,本質上也是一種野指針,pos指向的位置已經失效了
- 所以我們只需要將pos指向新空間對應的位置就好
修改后的insert👇
void insert(iterator pos, const T& x)
{if (_finish == _end_of_storage){size_t len = pos - _start; //加上這一句計算pos的位置size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len; //重置為新pos的位置}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}
- 緊接而來的問題,當我們調用自己寫insert時,pos會失效,使得在后面不能重新在調用pos
insert(pos,100);
erase的實現代碼👇
void erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;**it;}--_finish;
}
- 當我們使用erase但是也會出現失效的可能性,這也說明迭代器失效不只是野指針的問題
- 這取決于VS的編譯器對于iterator,我們出了作用域時,會類似標記為 false ,此時再次調用,就會報錯
5. 拷貝構造和析構函數
- 拷貝構造
問題一:如果我們不寫拷貝構造的話,在VS里面默認是什么
在內置類型里面,我們完成的是值拷貝,也就是所謂的淺拷貝,這不是我們所需要的
拷貝構造函數代碼如下👇
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}// v1 = v3
vector<T>& operator=(vector<T>& v)
{this->swap(v);return *this;
}
//v2(v1)
vector(const vector<T>& v)
{for (auto e : v){push_back(e);}
}//但是現在并沒有寫構造
- 但是在這里并沒有運行,所以在這里我們需要提前了解一下關鍵字
default關鍵字(強制編譯器生成)
//強制編譯器生成默認的
vector() = dafault;
析構函數代碼如下👇
~vector()
{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
}
其他問題
- 有人在編譯的時候可能會出現內部編譯器出錯
- 因為有模板的原因,編譯器報錯比較混亂
- 一般都是少了分號的原因
- 可以用分段注釋的方法來解決