本質
本質上就是一個陣列,儲存區域在記憶體中連續,但相比於靜態陣列,其可以在執行時動態調整大小(push_back,pop),無需手動管理記憶體
動態調整 -- 記憶體管理
vector中有兩個狀態資訊來維護記憶體管理:capacity和size。
capacity:表示當前分配到的記憶體空間大小
size:表示當前內部的元素數量
當size大於了capacity就需要重新分配
重新分配
當出現重新分配時,vector會被分配到一塊新的記憶體空間,然後將原有資料複製到新的記憶體空間,然後對原有資料空間進行釋放。這樣保持了連續性。
動態擴容
vector採用了指數增長的策略進行動態擴容,即當需要擴容時,vector將容量翻倍增長。這種擴容方式不僅避免了頻繁的記憶體分配,還確保了插入操作具有常數時間複雜度
簡易實現
透過編寫MyVector類實現類似std::vector的增、刪、獲取、遍歷四種功能
成員變數
主要是一個陣列指標、陣列大小以及陣列容量
//動態陣列頭指標
T* elements;
//動態陣列大小
size_t size;
//動態陣列容量
size_t capacity;
建構函式和解構函式
預設建構函式就是將頭指標置為空,大小和容量置為0
MyVector():elements(nullptr),capacity(0),size(0){}
解構函式採用delete清除已經分配的記憶體
~MyVector() {delete[] elements;}
複製建構函式,透過複製傳入的另一個類物件初始化改物件,首先為大小和容量進行賦值,然後利用new和std::copy對指標進行賦值
MyVector(const MyVector& v):size(v.size),capacity(v.capacity)
{
elements = new T[capacity];
std::copy(v.elements,v.elements+size,elements);
}
複製複製函式,透過過載=實現,需要先判斷兩者是否相等
MyVector &operator=(const MyVector& v)
{
if(this != &v)
{
delete[] elements;
size = v.size;
capacity = v.capacity;
elements = new T[capacity];
std::copy(v.elements,v.gelements+size,elements);
}
return *this;
}
相關操作
reserve使用對陣列的擴容
void reserve(size_t newcapacity)
{
if(newcapacity > capacity)
{
T* newelements = new T[newcapacity];
std::copy(elements,elements+size,newelements);
delets[] elements;
capacity = newcapacity;
elements = newelemenys;
}
}
push_back函式實現在陣列尾部增加元素,判斷大小是否超過容量
void push_back(const T& value)
{
if(size >= capacity)
{
reserve(capacity == 0 ? 1 : 2*capacity);
}
elements[size++] = value;
}
透過過載[]運算子實現對下標的索引
T& operator[](int index)
{
if(index >= size)
{
throw std::out_of_range("invalid Index: out of range");
}
return elements[index];
}
insert透過傳入下標和數值實現元素的插入
void insert(int index, const T& value)
{
if (index > size)
{
throw std::out_of_range("Index out of range");
}
else if(capacity == size) // 擴容
{
reserve(capacity == 0 ? 1:2*capacity);
}
//將index到末尾size的元素向後移動
for(size_t i = size;i>index;i--)
{
elements[i] = elements[i-1];
}
elements[index] = value;
size++;
}
begin()和end()是為了實現迭代器而設計的
// 使用迭代器遍歷陣列的開始位置
T *begin()
{
return elements;
}
// 使用迭代器遍歷陣列的結束位置
T *end()
{
return elements + size;
}
pop_back()彈出陣列末尾元素只需要將陣列元素減一就可以實現了
void pop_back()
{
size--;
}
vector相關
擴容過程
一般的擴容過程涉及到以下幾個步驟
- 分配一個更大的記憶體塊、通常是當前大小的兩倍
- 將當前所有元素移到新分配的記憶體塊中
- 銷燬舊元素,並釋放舊記憶體塊
- 插入新元素
push_back 和 emplace_back
其實現效果都是向vector的末尾新增元素
push_back會對給定物件進行複製或者移動構造,將元素新增導末尾,即會先透過建構函式將value給構造出來,然後再呼叫複製建構函式,新增到末尾
emplace_back則使用給定的引數直接在vector末尾構造一個元素,無需複製或者移動構造,只需要在末尾呼叫建構函式構造即可
減少vector的佔用空間
在C++ 11中引入了shrink_to_fit來請求移除未使用的容量,會嘗試將容量壓縮至size,打不保證一定壓縮
檢查vector是否為空
使用empty()方法,empty()方法是常數時間,因為一般透過檢測尾節點來實現判斷
而size()方法通常是遍歷整個陣列獲取長度
迭代器失效
如果對vector進行重新分配,那麼其所有指向元素的迭代器都會失效
如果在vector中間插入元素,那麼從該點到末尾的所有迭代器都會失效。
為了避免這些問題,最好使用remove() remove_if()方法搭配erase()方法來刪除元素
如果vector的元素型別為指標,需要考慮什麼問題
- 記憶體管理:在清除vector的頭指標之前,要保證每個元素分配的記憶體空間被清理掉
- 所有權和生命週期:在對vector元素訪問期間,要保證這些指標指向的物件是有效的。同時需要清楚的定義誰擁有這些物件和修改時機
- 異常控制:當建立和填充vector時需要需要一個機制來處理已經分配的記憶體,避免記憶體洩漏
- 智慧指標:一般建議使用智慧指標(unique_ptr或則shared_ptr)來當作指標型別,vector被銷燬後,會自動銷燬
- 避免懸垂指標:當指向的物件失效時,要確保沒有懸垂指標指向無效的記憶體地址,同樣vector被重新分配後,這些指標也會失效
- 深複製和淺複製:如果是指標型別,在複製vector時,需要考慮是複製指標+物件還是隻複製指標