目錄
0.前言
1.vector介紹
2.vector使用
2.1 構造函數(Constructor)
2.1.1. 默認構造函數 (Default Constructor)
2.1.2 填充構造函數 (Fill Constructor)
2.1.3?范圍構造函數 (Range Constructor)
2.1.4?拷貝構造函數 (Copy Constructor)
2.2 迭代器(Iterator)
2.2.1?begin 和 end
2.2.2?rbegin 和 rend
2.3 容量控制函數(Capacity)
2.3.1?size
2.3.2?resize
2.3.3?capacity
2.3.4?empty
2.3.5?reserve
2.4 元素訪問函數(Element access)
2.4.1?operator[]
2.4.2?at
2.5 修改函數(Modifiers)
2.5.1?assign
2.5.2?push_back
2.5.3?pop_back
2.5.4?insert
2.5.5?erase
2.5.6?swap
2.5.7?clear
3.vector的模擬實現
3.1 基本實現代碼
3.2 疑難點分析
3.2.1 用initializer_list構造函數
3.2.2 memcpy的淺拷貝問題
3.2.3 迭代器區間構造函數與int沖突問題
4.小結
(圖像由AI生成)?
0.前言
在前面的博客中,我們介紹了string
類的使用與模擬實現,深入探討了其在C++編程中的重要性和具體應用。然而,string
類僅僅是C++標準模板庫(STL)中的一個組成部分。為了更全面地了解STL的強大功能,本篇博客將聚焦于另一個核心容器類——vector
。vector
類在現代C++編程中扮演著至關重要的角色,其靈活的動態數組特性、豐富的成員函數以及高效的內存管理,使其成為開發者處理可變大小數組的首選工具。本文將詳細介紹vector
類的基本概念、使用方法、內部實現以及常見問題的解決方案,幫助讀者全面掌握這一重要的容器類。
1.vector介紹
(本部分內容參考:vector - C++ Reference (cplusplus.com))
vector
是C++標準模板庫(STL)中的一個序列容器,表示可以動態改變大小的數組。
與數組類似,vector
使用連續的存儲位置來存儲其元素,這意味著可以使用常規指針的偏移量來高效地訪問其元素。不同于數組,vector
的大小可以動態改變,其存儲空間由容器自動管理。
內部工作原理
內部來說,vector
使用一個動態分配的數組來存儲其元素。當插入新元素時,這個數組可能需要重新分配以增加大小,這涉及到分配一個新的數組并將所有元素移動到新數組中。這是一個相對耗時的操作,因此vector
不會在每次添加元素時都重新分配內存。
為了高效管理存儲,vector
容器可能會預先分配一些額外的存儲空間,以應對可能的增長。因此,容器的實際容量可能大于嚴格存儲其元素所需的容量(即其大小)。庫可以實現不同的增長策略,以在內存使用和重新分配之間取得平衡,但無論如何,重新分配應僅在容量按對數增長的間隔時發生,從而確保在vector
末尾插入單個元素的操作可以提供攤銷常數時間復雜度。
因此,與數組相比,vector
消耗更多內存以換取能夠高效地管理存儲和動態增長的能力。
與其他動態序列容器的比較
與其他動態序列容器(如deque
、list
和forward_list
)相比,vector
在訪問其元素方面非常高效(就像數組一樣),在從末尾添加或刪除元素方面也相對高效。對于涉及在末尾以外的位置插入或刪除元素的操作,vector
的性能不如其他容器,并且其迭代器和引用的穩定性也不如list
和forward_list
。
容器屬性
- 序列容器:序列容器中的元素按照嚴格的線性序列排序。可以通過其在序列中的位置訪問單個元素。
- 動態數組:允許直接訪問序列中的任何元素,甚至通過指針算術操作,并且提供了相對快速的在序列末尾添加/移除元素的功能。
- 分配器感知:容器使用一個分配器對象來動態處理其存儲需求。
2.vector使用
2.1 構造函數(Constructor)
default (1) | explicit vector (const allocator_type& alloc = allocator_type()); |
---|---|
fill (2) | explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type()); |
range (3) | template <class InputIterator>vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type()); |
copy (4) | vector (const vector& x); |
vector
類提供了多種構造函數,以便在不同的場景中靈活地創建vector
對象。以下是幾種主要的構造函數及其使用方法:
2.1.1. 默認構造函數 (Default Constructor)
explicit vector (const allocator_type& alloc = allocator_type());
這個構造函數創建一個空的vector
,可以選擇性地指定一個內存分配器。
示例代碼:
std::vector<int> first; // 創建一個空的int類型vector
2.1.2 填充構造函數 (Fill Constructor)
explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());
這個構造函數創建一個包含n
個元素的vector
,每個元素的初始值為val
,并可以選擇性地指定一個內存分配器。
示例代碼:
std::vector<int> second(4, 100); // 創建一個包含4個元素的vector,每個元素的值為100
2.1.3?范圍構造函數 (Range Constructor)
template <class InputIterator>
vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
這個構造函數使用迭代器區間[first, last)
的元素創建一個vector
,并可以選擇性地指定一個內存分配器。
示例代碼:
std::vector<int> third(second.begin(), second.end()); // 使用second的迭代器區間創建vector
2.1.4?拷貝構造函數 (Copy Constructor)
vector (const vector& x);
這個構造函數創建一個與x
相同的vector
。
示例代碼:
std::vector<int> fourth(third); // 使用third創建一個新的vector副本
下面是一個完整的示例代碼,展示了上述幾種構造函數的使用方法:
// constructing vectors
#include <iostream>
#include <vector>int main ()
{// constructors used in the same order as described above:std::vector<int> first; // empty vector of intsstd::vector<int> second (4,100); // four ints with value 100std::vector<int> third (second.begin(),second.end()); // iterating through secondstd::vector<int> fourth (third); // a copy of third// the iterator constructor can also be used to construct from arrays:int myints[] = {16,2,77,29};std::vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );std::cout << "The contents of fifth are:";for (std::vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}
代碼解釋:
first
是一個空的vector
,使用默認構造函數創建。second
是一個包含4個元素的vector
,每個元素的值為100,使用填充構造函數創建。third
是通過遍歷second
的所有元素創建的vector
,使用范圍構造函數創建。fourth
是third
的副本,使用拷貝構造函數創建。fifth
是通過從數組myints
中復制元素創建的vector
,同樣使用范圍構造函數。
2.2 迭代器(Iterator)
迭代器是一個重要的工具,使我們能夠遍歷和操作容器中的元素。vector
類提供了多種迭代器,以便在不同的場景中靈活地操作元素。以下是幾種主要的迭代器及其使用方法:
2.2.1?begin
和 end
begin
:返回指向vector
第一個元素的迭代器。如果vector
是空的,這個迭代器等于end
。end
:返回指向vector
末尾元素之后一個位置的迭代器。這個位置不包含在vector
中,因此不能被解引用。
示例代碼:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {1, 2, 3, 4, 5};std::cout << "Vector elements: ";for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}
在這個示例中,begin
和end
迭代器用于遍歷vector
中的所有元素,并輸出它們的值。
2.2.2?rbegin
和 rend
rbegin
:返回指向vector
最后一個元素的反向迭代器。反向迭代器允許我們從vector
的末尾向前遍歷。rend
:返回指向vector
第一個元素之前一個位置的反向迭代器。這使得我們可以使用反向迭代器來訪問vector
中的元素。
示例代碼:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {1, 2, 3, 4, 5};std::cout << "Vector elements in reverse order: ";for (std::vector<int>::reverse_iterator rit = myvector.rbegin(); rit != myvector.rend(); ++rit)std::cout << ' ' << *rit;std::cout << '\n';return 0;
}
在這個示例中,rbegin
和rend
反向迭代器用于反向遍歷vector
中的所有元素,并輸出它們的值。
2.3 容量控制函數(Capacity)
vector
類提供了一些函數用于管理和控制容器的容量和大小。這些函數能夠幫助我們在處理動態數組時更加高效和靈活。以下是主要的容量控制函數及其使用方法:
2.3.1?size
size
函數返回當前vector
中元素的數量。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {1, 2, 3, 4, 5};std::cout << "The size of myvector is: " << myvector.size() << '\n';return 0;
}
在這個示例中,size
函數返回vector
中的元素數量,即5。
2.3.2?resize
resize
函數改變vector
的大小。如果新的大小大于當前大小,則添加默認值元素;如果新的大小小于當前大小,則移除多余的元素。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {1, 2, 3, 4, 5};myvector.resize(3); // 將大小調整為3,移除多余元素std::cout << "After resize to 3, myvector contains:";for (int n : myvector) std::cout << ' ' << n;std::cout << '\n';myvector.resize(5, 100); // 將大小調整為5,新增元素值為100std::cout << "After resize to 5 with default value 100, myvector contains:";for (int n : myvector) std::cout << ' ' << n;std::cout << '\n';return 0;
}
代碼解釋:
- 第一次調用
resize
將vector
的大小從5調整為3,移除了多余的元素。 - 第二次調用
resize
將vector
的大小調整為5,新增的兩個元素值為100。
2.3.3?capacity
capacity
函數返回vector
分配的存儲容量,即vector
可以存儲多少元素而不需要重新分配內存。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector;myvector.reserve(10); // 預留容量std::cout << "The capacity of myvector is: " << myvector.capacity() << '\n';return 0;
}
在這個示例中,reserve
函數預留了10個元素的容量,然后capacity
函數返回vector
當前的容量。
2.3.4?empty
empty
函數檢查vector
是否為空。如果vector
不包含任何元素,則返回true
;否則返回false
。
使用示例:
#include <vector>int main() {std::vector<int> myvector;if (myvector.empty()) {std::cout << "myvector is empty.\n";} else {std::cout << "myvector is not empty.\n";}myvector.push_back(1);if (myvector.empty()) {std::cout << "myvector is empty.\n";} else {std::cout << "myvector is not empty.\n";}return 0;
}
代碼解釋:
- 初始時,
myvector
是空的,因此empty
函數返回true
。 - 在向
vector
添加一個元素后,empty
函數返回false
。
2.3.5?reserve
reserve
函數請求分配至少能夠存儲指定數量元素的內存容量。如果指定容量小于當前容量,則不會改變vector
的容量。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector;myvector.reserve(10); // 請求分配容量std::cout << "Capacity after reserve(10): " << myvector.capacity() << '\n';myvector.push_back(1);std::cout << "Capacity after push_back: " << myvector.capacity() << '\n';return 0;
}
代碼解釋:
reserve(10)
請求分配至少10個元素的內存容量。- 調用
push_back
函數向vector
添加一個元素后,容量保持不變,因為vector
已經有足夠的空間來存儲新元素。
2.4 元素訪問函數(Element access)
vector
類提供了一些函數用于訪問和操作存儲在容器中的元素。以下是主要的元素訪問函數及其使用方法:
2.4.1?operator[]
operator[]
是一個重載的下標操作符,用于訪問指定位置的元素。該操作符不進行邊界檢查,因此如果訪問越界,將導致未定義行為。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {10, 20, 30, 40, 50};std::cout << "Element at index 2 is: " << myvector[2] << '\n'; // 輸出 30myvector[2] = 100; // 修改元素值std::cout << "Element at index 2 after modification is: " << myvector[2] << '\n'; // 輸出 100return 0;
}
代碼解釋:
- 使用
myvector[2]
訪問并輸出索引為2的元素(初始值為30)。 - 通過
myvector[2] = 100
修改索引為2的元素值。 - 再次訪問并輸出修改后的元素值(新值為100)。
2.4.2?at
at
函數用于訪問指定位置的元素,與operator[]
不同的是,at
會進行邊界檢查。如果指定的位置超出范圍,將拋出一個out_of_range
異常。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {10, 20, 30, 40, 50};try {std::cout << "Element at index 2 is: " << myvector.at(2) << '\n'; // 輸出 30myvector.at(2) = 100; // 修改元素值std::cout << "Element at index 2 after modification is: " << myvector.at(2) << '\n'; // 輸出 100// 嘗試訪問超出范圍的元素std::cout << "Element at index 10 is: " << myvector.at(10) << '\n';} catch (const std::out_of_range& e) {std::cerr << "Out of range error: " << e.what() << '\n';}return 0;
}
代碼解釋:
- 使用
myvector.at(2)
訪問并輸出索引為2的元素(初始值為30)。 - 通過
myvector.at(2) = 100
修改索引為2的元素值。 - 再次訪問并輸出修改后的元素值(新值為100)。
- 嘗試訪問超出范圍的元素
myvector.at(10)
時,捕獲并處理out_of_range
異常,輸出錯誤信息。
2.5 修改函數(Modifiers)
vector
類提供了多種修改其內容的方法。以下是主要的修改函數及其使用方法:
2.5.1?assign
assign
函數用于為vector
分配新內容,替換其當前內容并調整其大小。它有多個重載版本,可以接受不同類型的輸入參數。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector;// 使用 count 和 value 版本的 assignmyvector.assign(7, 100); // 7 個值為 100 的元素std::cout << "myvector contains:";for (int i : myvector)std::cout << ' ' << i;std::cout << '\n';// 使用迭代器區間版本的 assignstd::vector<int> anothervector = {1, 2, 3, 4, 5};myvector.assign(anothervector.begin(), anothervector.end());std::cout << "myvector now contains:";for (int i : myvector)std::cout << ' ' << i;std::cout << '\n';return 0;
}
代碼解釋:
- 第一次調用
assign
使用7個值為100的元素替換vector
內容。 - 第二次調用
assign
使用另一個vector
的元素替換vector
內容。
2.5.2?push_back
push_back
函數在vector
的末尾添加一個元素。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector;myvector.push_back(10);myvector.push_back(20);myvector.push_back(30);std::cout << "myvector contains:";for (int i : myvector)std::cout << ' ' << i;std::cout << '\n';return 0;
}
代碼解釋:
- 依次向
vector
的末尾添加10、20和30。
2.5.3?pop_back
pop_back
函數刪除vector
末尾的元素。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {10, 20, 30};myvector.pop_back();std::cout << "myvector contains:";for (int i : myvector)std::cout << ' ' << i;std::cout << '\n';return 0;
}
代碼解釋:
- 刪除
vector
末尾的元素(30)。
2.5.4?insert
insert
函數用于在指定位置插入元素,有多個重載版本,支持插入單個元素、多個元素或一個迭代器區間的元素。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {10, 20, 30};// 插入單個元素myvector.insert(myvector.begin(), 5);// 插入多個相同元素myvector.insert(myvector.end(), 2, 100);// 插入另一個 vector 的元素std::vector<int> anothervector = {1, 2, 3};myvector.insert(myvector.begin() + 1, anothervector.begin(), anothervector.end());std::cout << "myvector contains:";for (int i : myvector)std::cout << ' ' << i;std::cout << '\n';return 0;
}
代碼解釋:
- 在
vector
開頭插入單個元素5。 - 在
vector
末尾插入兩個值為100的元素。 - 在
vector
第二個位置插入另一個vector
的元素。
2.5.5?erase
erase
函數用于刪除指定位置的元素或一個元素區間。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {10, 20, 30, 40, 50};// 刪除單個元素myvector.erase(myvector.begin() + 1); // 刪除第二個元素// 刪除一個區間的元素myvector.erase(myvector.begin(), myvector.begin() + 2); // 刪除前兩個元素std::cout << "myvector contains:";for (int i : myvector)std::cout << ' ' << i;std::cout << '\n';return 0;
}
代碼解釋:
- 刪除
vector
中第二個元素(20)。 - 刪除
vector
中前兩個元素(10和30)。
2.5.6?swap
swap
函數用于交換兩個vector
的內容。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> first = {1, 2, 3};std::vector<int> second = {4, 5, 6, 7};first.swap(second);std::cout << "first contains:";for (int i : first)std::cout << ' ' << i;std::cout << '\n';std::cout << "second contains:";for (int i : second)std::cout << ' ' << i;std::cout << '\n';return 0;
}
代碼解釋:
- 交換
first
和second
的內容。
2.5.7?clear
clear
函數用于清空vector
的所有元素。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> myvector = {10, 20, 30};myvector.clear();std::cout << "myvector contains " << myvector.size() << " elements.\n";return 0;
}
代碼解釋:
- 清空
vector
的所有元素,size
將為0。
3.vector的模擬實現
3.1 基本實現代碼
template <class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;typedef T& reference;typedef const T& const_reference;
private:iterator _start;iterator _finish;iterator _end_of_storage;
public:iterator begin(){return _start;}iterator end(){return _finish;}const_iterator cbegin() const{return _start;}const_iterator cend() const{return _finish;}// 默認構造函數vector(){_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}// 帶初始大小和初始值的構造函數vector(size_t n, const T& val = T()){_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = val;}_finish = _start + n;_end_of_storage = _start + n;}// 帶整數大小和初始值的構造函數vector(int n, const T& val = T()){_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = val;}_finish = _start + n;_end_of_storage = _start + n;}// 拷貝構造函數vector(const vector<T>& v){_start = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}// 區間構造函數template<class InputIterator>vector(InputIterator first, InputIterator last){size_t n = last - first;_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = *first;++first;}_finish = _start + n;_end_of_storage = _start + n;}// 初始化列表構造函數vector(std::initializer_list<T> l){size_t n = l.size();_start = new T[n];auto it = l.begin();for (size_t i = 0; i < n; i++){_start[i] = *it;it++;}_finish = _start + n;_end_of_storage = _start + n;}// 賦值運算符vector<T>& operator=(const vector<T>& v){if (this != &v){T* tmp = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){tmp[i] = v._start[i];}delete[] _start;_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;}// 析構函數~vector(){if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}}// 返回元素個數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 size = this->size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + size;_end_of_storage = _start + n;}}// 調整大小void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else if (n > size()){if (n > capacity()){reserve(n);}for (size_t i = size(); i < n; i++){_start[i] = val;}_finish = _start + n;}}// 檢查是否為空bool empty() const{return _start == _finish;}// 下標操作符reference operator[](size_t pos){assert(pos < size());return _start[pos];}const_reference operator[](size_t pos) const{assert(pos < size());return _start[pos];}// 返回第一個元素的引用reference front(){return *_start;}const_reference front() const{return *_start;}// 返回最后一個元素的引用reference back(){return *(_finish - 1);}const_reference back() const{return *(_finish - 1);}// 在末尾添加元素void push_back(const T& x){if (_finish == _end_of_storage){size_t n = capacity() == 0 ? 1 : 2 * capacity();reserve(n);}*_finish = x;++_finish;}// 刪除末尾元素void pop_back(){assert(_start < _finish);--_finish;}// 插入元素iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage){size_t n = pos - _start;size_t newcapacity = capacity() == 0 ? 1 : 2 * capacity();reserve(newcapacity);pos = _start + n;}iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}*pos = x;++_finish;return pos;}// 刪除指定位置的元素iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin < _finish){*(begin - 1) = *begin;++begin;}--_finish;return pos;}// 刪除區間元素iterator erase(iterator first, iterator last){assert(first >= _start && last <= _finish && first <= last);iterator begin = last;while (begin < _finish){*(first) = *begin;++first;++begin;}_finish = first;return first;}// 交換內容void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}// 清空內容void clear(){_finish = _start;}
};
3.2 疑難點分析
3.2.1 用initializer_list構造函數
在C++11中,引入了一種新的類型std::initializer_list
,它允許我們使用初始化列表的方式來初始化容器和對象。initializer_list
提供了一種簡單的語法,用于在構造對象時直接列出其元素,例如在構造vector
對象時可以像這樣:vector<int> v = {1, 2, 3, 4, 5};
。
std::initializer_list
類型提供了一些基本的成員函數,例如size()
可以返回列表中元素的數量,begin()
和end()
則分別返回指向列表開頭和末尾的迭代器。
下面是一個實現initializer_list
構造函數的示例:
// 初始化列表構造函數
vector(std::initializer_list<T> l)
{size_t n = l.size();_start = new T[n]; // 分配足夠的內存來存儲初始化列表中的元素auto it = l.begin();for (size_t i = 0; i < n; i++){_start[i] = *it; // 將初始化列表中的元素逐個復制到_vector_中it++;}_finish = _start + n; // 設置_finish_指向最后一個元素的下一個位置_end_of_storage = _start + n; // 設置_end_of_storage_指向分配的內存末尾
}
在這個構造函數中:
size_t n = l.size();
:首先獲取初始化列表的大小,即需要存儲的元素數量。_start = new T[n];
:分配一個大小為n
的數組,作為vector
的存儲空間。auto it = l.begin();
:獲取初始化列表的起始迭代器。for (size_t i = 0; i < n; i++) { _start[i] = *it; it++; }
:使用循環將初始化列表中的元素逐個復制到vector
中。迭代器it
從初始化列表的開頭開始,依次訪問每個元素,并將其賦值到_start
數組中對應的位置。_finish = _start + n;
:設置_finish
指向最后一個元素的下一個位置,這樣可以表示當前vector
的實際大小。_end_of_storage = _start + n;
:設置_end_of_storage
指向分配的內存末尾,表示當前vector
的容量。
通過這種方式,我們可以使用初始化列表來方便地構造vector
對象,使代碼更加簡潔和直觀。
3.2.2 memcpy的淺拷貝問題
在實現vector
的reserve
函數時,如果我們直接使用memcpy
來拷貝內存,而不是逐個元素復制,會引發一些問題,尤其是當vector
的元素類型是std::string
或其他復雜類型時。為了更好地理解這個問題,我們首先需要了解memcpy
和逐個元素復制的區別。
memcpy
與逐個元素復制的區別
memcpy
是一種高效的內存拷貝方法,用于復制原始字節流。但是,它只是簡單地將內存塊從一個位置復制到另一個位置,而不考慮元素的構造和析構。這意味著,對于像std::string
這樣具有內部動態內存管理和析構函數的類型,memcpy
不會正確地處理這些類型的內部資源。
相反,逐個元素復制(例如使用賦值運算符或拷貝構造函數)會確保每個元素的正確構造和析構。對于std::string
等復雜類型,這種方法會確保其內部資源(如動態分配的內存)被正確管理。
reserve
函數的實現
在vector
的reserve
函數中,我們需要為新的容量分配內存,并將現有元素復制到新分配的內存中。下面是一個正確實現的示例:
template <class T>
class vector
{// ... other members and functions ...// 預留空間void reserve(size_t n){if (n > capacity()){size_t size = this->size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < size; i++){tmp[i] = _start[i]; // 逐個元素復制}delete[] _start;}_start = tmp;_finish = _start + size;_end_of_storage = _start + n;}}// ... other members and functions ...
};
但是,如果我們使用memcpy
來拷貝內存,例如:
#include <cstring> // for std::memcpyvoid reserve(size_t n)
{if (n > capacity()){size_t size = this->size();T* tmp = new T[n];if (_start){std::memcpy(tmp, _start, size * sizeof(T)); // 使用memcpy來拷貝內存delete[] _start;}_start = tmp;_finish = _start + size;_end_of_storage = _start + n;}
}
這種實現對于std::string
等復雜類型會產生問題,因為memcpy
只是簡單地復制內存,而不會調用這些類型的拷貝構造函數。這可能導致:
-
淺拷貝問題:對于
std::string
,memcpy
只會復制指向內部字符數組的指針,而不會復制字符數組本身。這會導致多個std::string
對象共享同一塊內存,當其中一個對象被修改或銷毀時,可能會影響其他對象,導致未定義行為。 -
資源泄漏:如果源對象擁有動態分配的資源(如
std::string
的內部字符數組),使用memcpy
進行復制后,新的對象不會擁有這些資源的所有權,可能會導致資源泄漏或重復釋放。 -
破壞對象的狀態:一些類型可能在對象構造和析構時進行狀態管理,
memcpy
不會調用這些類型的構造和析構函數,從而破壞對象的狀態。
因此,對于需要正確管理資源的復雜類型,應該避免使用memcpy
來進行內存拷貝,而應使用逐個元素復制的方法,以確保每個元素的構造和析構函數被正確調用。這樣可以避免潛在的淺拷貝問題和資源管理問題,確保vector
的正確和安全使用。
3.2.3 迭代器區間構造函數與int沖突問題
在實現vector
類的構造函數時,我們可能會遇到不同構造函數之間的沖突問題。特別是當我們有一個帶有初始大小和初始值的構造函數和一個接受迭代器區間的構造函數時,這種沖突尤為明顯。
假設我們有如下兩個構造函數:
// 帶初始大小和初始值的構造函數
vector(size_t n, const T& val = T())
{_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = val;}_finish = _start + n;_end_of_storage = _start + n;
}// 區間構造函數
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{size_t n = last - first;_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = *first;++first;}_finish = _start + n;_end_of_storage = _start + n;
}
當我們調用vector<int> v(10, 10);
時,編譯器可能會選擇調用區間構造函數而不是帶初始大小和初始值的構造函數,因為int
類型可以與模板參數InputIterator
匹配。這會導致編譯錯誤或意外行為。
為了解決這個問題,我們可以使用SFINAE(Substitution Failure Is Not An Error)技術,即在模板參數中添加類型檢查,使得模板匹配只在特定條件下成立。這可以通過std::enable_if
和類型特征(如std::is_integral
)來實現。
#include <type_traits>template <class T>
class vector
{
public:typedef T* iterator;typedef const T* const_iterator;typedef T& reference;typedef const T& const_reference;private:iterator _start;iterator _finish;iterator _end_of_storage;public:// 默認構造函數vector(){_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}// 帶初始大小和初始值的構造函數template<typename U = T>vector(size_t n, const T& val = T(), typename std::enable_if<std::is_integral<U>::value>::type* = 0){_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = val;}_finish = _start + n;_end_of_storage = _start + n;}// 區間構造函數template<class InputIterator>vector(InputIterator first, InputIterator last, typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0){size_t n = last - first;_start = new T[n];for (size_t i = 0; i < n; i++){_start[i] = *first;++first;}_finish = _start + n;_end_of_storage = _start + n;}// ... 其他成員函數 ...
};
代碼解釋
-
帶初始大小和初始值的構造函數:
template<typename U = T>
:使用一個默認模板參數U
,默認值為T
。typename std::enable_if<std::is_integral<U>::value>::type* = 0
:僅當U
是整型時,這個構造函數才有效。
-
區間構造函數:
template<class InputIterator>
:接受兩個迭代器參數。typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0
:僅當InputIterator
不是整型時,這個構造函數才有效。
通過這種方式,我們可以避免帶初始大小和初始值的構造函數和區間構造函數之間的沖突,確保在調用vector<int> v(10, 10);
時,編譯器會正確選擇帶初始大小和初始值的構造函數,而不會誤調用區間構造函數。
4.小結
通過對C++中vector
類的深入探討,我們了解了其構造函數、迭代器、容量控制函數、元素訪問函數和修改函數的具體實現和使用方法。特別是對initializer_list
和memcpy
淺拷貝問題的分析,及解決迭代器區間構造函數與整數類型沖突的方法,讓我們對如何正確高效地使用和實現vector
有了更全面的理解。希望通過本文,大家能夠更好地掌握vector
的用法,在實際編程中發揮其強大功能。