C++ vector類

目錄

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的強大功能,本篇博客將聚焦于另一個核心容器類——vectorvector類在現代C++編程中扮演著至關重要的角色,其靈活的動態數組特性、豐富的成員函數以及高效的內存管理,使其成為開發者處理可變大小數組的首選工具。本文將詳細介紹vector類的基本概念、使用方法、內部實現以及常見問題的解決方案,幫助讀者全面掌握這一重要的容器類。

1.vector介紹

(本部分內容參考:vector - C++ Reference (cplusplus.com))

vector是C++標準模板庫(STL)中的一個序列容器,表示可以動態改變大小的數組。

與數組類似,vector使用連續的存儲位置來存儲其元素,這意味著可以使用常規指針的偏移量來高效地訪問其元素。不同于數組,vector的大小可以動態改變,其存儲空間由容器自動管理。

內部工作原理

內部來說,vector使用一個動態分配的數組來存儲其元素。當插入新元素時,這個數組可能需要重新分配以增加大小,這涉及到分配一個新的數組并將所有元素移動到新數組中。這是一個相對耗時的操作,因此vector不會在每次添加元素時都重新分配內存。

為了高效管理存儲,vector容器可能會預先分配一些額外的存儲空間,以應對可能的增長。因此,容器的實際容量可能大于嚴格存儲其元素所需的容量(即其大小)。庫可以實現不同的增長策略,以在內存使用和重新分配之間取得平衡,但無論如何,重新分配應僅在容量按對數增長的間隔時發生,從而確保在vector末尾插入單個元素的操作可以提供攤銷常數時間復雜度。

因此,與數組相比,vector消耗更多內存以換取能夠高效地管理存儲和動態增長的能力。

與其他動態序列容器的比較

與其他動態序列容器(如dequelistforward_list)相比,vector在訪問其元素方面非常高效(就像數組一樣),在從末尾添加或刪除元素方面也相對高效。對于涉及在末尾以外的位置插入或刪除元素的操作,vector的性能不如其他容器,并且其迭代器和引用的穩定性也不如listforward_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,使用范圍構造函數創建。
  • fourththird的副本,使用拷貝構造函數創建。
  • fifth是通過從數組myints中復制元素創建的vector,同樣使用范圍構造函數。

2.2 迭代器(Iterator)

迭代器是一個重要的工具,使我們能夠遍歷和操作容器中的元素。vector類提供了多種迭代器,以便在不同的場景中靈活地操作元素。以下是幾種主要的迭代器及其使用方法:

2.2.1?beginend

  • 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;
}

在這個示例中,beginend迭代器用于遍歷vector中的所有元素,并輸出它們的值。

2.2.2?rbeginrend

  • 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;
}

在這個示例中,rbeginrend反向迭代器用于反向遍歷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;
}

代碼解釋:

  • 第一次調用resizevector的大小從5調整為3,移除了多余的元素。
  • 第二次調用resizevector的大小調整為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;
}

代碼解釋:

  • 交換firstsecond的內容。

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_指向分配的內存末尾
}

在這個構造函數中:

  1. size_t n = l.size();:首先獲取初始化列表的大小,即需要存儲的元素數量。
  2. _start = new T[n];:分配一個大小為n的數組,作為vector的存儲空間。
  3. auto it = l.begin();:獲取初始化列表的起始迭代器。
  4. for (size_t i = 0; i < n; i++) { _start[i] = *it; it++; }:使用循環將初始化列表中的元素逐個復制到vector中。迭代器it從初始化列表的開頭開始,依次訪問每個元素,并將其賦值到_start數組中對應的位置。
  5. _finish = _start + n;:設置_finish指向最后一個元素的下一個位置,這樣可以表示當前vector的實際大小。
  6. _end_of_storage = _start + n;:設置_end_of_storage指向分配的內存末尾,表示當前vector的容量。

通過這種方式,我們可以使用初始化列表來方便地構造vector對象,使代碼更加簡潔和直觀。

3.2.2 memcpy的淺拷貝問題

在實現vectorreserve函數時,如果我們直接使用memcpy來拷貝內存,而不是逐個元素復制,會引發一些問題,尤其是當vector的元素類型是std::string或其他復雜類型時。為了更好地理解這個問題,我們首先需要了解memcpy和逐個元素復制的區別。

memcpy與逐個元素復制的區別

memcpy是一種高效的內存拷貝方法,用于復制原始字節流。但是,它只是簡單地將內存塊從一個位置復制到另一個位置,而不考慮元素的構造和析構。這意味著,對于像std::string這樣具有內部動態內存管理和析構函數的類型,memcpy不會正確地處理這些類型的內部資源。

相反,逐個元素復制(例如使用賦值運算符或拷貝構造函數)會確保每個元素的正確構造和析構。對于std::string等復雜類型,這種方法會確保其內部資源(如動態分配的內存)被正確管理。

reserve函數的實現

vectorreserve函數中,我們需要為新的容量分配內存,并將現有元素復制到新分配的內存中。下面是一個正確實現的示例:

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只是簡單地復制內存,而不會調用這些類型的拷貝構造函數。這可能導致:

  1. 淺拷貝問題:對于std::stringmemcpy只會復制指向內部字符數組的指針,而不會復制字符數組本身。這會導致多個std::string對象共享同一塊內存,當其中一個對象被修改或銷毀時,可能會影響其他對象,導致未定義行為。

  2. 資源泄漏:如果源對象擁有動態分配的資源(如std::string的內部字符數組),使用memcpy進行復制后,新的對象不會擁有這些資源的所有權,可能會導致資源泄漏或重復釋放。

  3. 破壞對象的狀態:一些類型可能在對象構造和析構時進行狀態管理,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;}// ... 其他成員函數 ...
};

代碼解釋

  1. 帶初始大小和初始值的構造函數

    • template<typename U = T>:使用一個默認模板參數U,默認值為T
    • typename std::enable_if<std::is_integral<U>::value>::type* = 0:僅當U是整型時,這個構造函數才有效。
  2. 區間構造函數

    • template<class InputIterator>:接受兩個迭代器參數。
    • typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0:僅當InputIterator不是整型時,這個構造函數才有效。

通過這種方式,我們可以避免帶初始大小和初始值的構造函數和區間構造函數之間的沖突,確保在調用vector<int> v(10, 10);時,編譯器會正確選擇帶初始大小和初始值的構造函數,而不會誤調用區間構造函數。

4.小結

通過對C++中vector類的深入探討,我們了解了其構造函數、迭代器、容量控制函數、元素訪問函數和修改函數的具體實現和使用方法。特別是對initializer_listmemcpy淺拷貝問題的分析,及解決迭代器區間構造函數與整數類型沖突的方法,讓我們對如何正確高效地使用和實現vector有了更全面的理解。希望通過本文,大家能夠更好地掌握vector的用法,在實際編程中發揮其強大功能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/15623.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/15623.shtml
英文地址,請注明出處:http://en.pswp.cn/web/15623.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

十、通配符和正則表達式

10.1 通配符 通配符是由shell處理的, 它只會出現在 命令的“參數”里。當shell在“參數”中遇到了通配符 時&#xff0c;shell會將其當作路徑或文件名去在磁盤上搜尋可能的匹配&#xff1a;若符合要求的匹配存在&#xff0c;則進 行代換(路徑擴展)&#xff1b;否則就將該通配…

python安裝依賴

創建 requirement.txt 文件并填充內容 flask2.0.0 pandas1.3.3 numpy1.21.2 安裝模塊 pip install -r requirement.txt

Spring boot使用集群方式、支持ssl連接redis的方法

1、需求背景 項目需要提供一個管理界面給內部人員操作用戶信息&#xff0c;需要在修改用戶信息后刪除用戶的redis緩存。用戶所在的區域不同&#xff0c;其redis服務地址也不相同&#xff0c;因此需要管理多個redis連接&#xff0c;且redis要求以集群方式并支持ssl進行連接。 2…

Qt for android 獲取USB設備列表(一)Java方式 獲取

簡介 QtActivity 作為 Qt 應用程序的入口點&#xff0c;負責啟動和配置 Qt 應用程序的信息&#xff0c; 后面我們繼承 QtActivity 做自定義控制&#xff0c;了解一下 Activity 生命周期概念&#xff0c; 因為 QtActivity 繼承自Android的activity&#xff0c;使用周期函數完成我…

java8新特性——函數式編程詳解

目錄 一 概述1.1 背景1.2 函數式編程的意義1.3 函數式編程的發展 Lambda表達式1.1 介紹1.2 使用Lambda的好處1.3 Lambda方法1.3.1 Lambda表達式結構1.3.2 Lambda表達式的特征 1.4 Lambda的使用1.4.1 定義函數式接口1.4.2 Lambda表達式實現函數式接口1.4.3 簡化Lambda表達式1.4.…

C++學習/復習4--與類相關的概念/默認成員函數/運算符重載/Date類實現案例

一、類和對象 1.本章概要 2.C中的結構體(struct與class) 升級為類 &#xff08;1&#xff09;類及成員函數的兩種定義方式 聲明與定義分離 &#xff08;2&#xff09;權限 注意1&#xff1a;struct/class在權限上的區別 &#xff08;3&#xff09;封裝 &#xff08;4&#x…

AI學習指南數學工具篇-凸優化之對偶性與拉格朗日對偶

AI學習指南數學工具篇-凸優化之對偶性與拉格朗日對偶 在凸優化中&#xff0c;對偶性是一個非常重要的概念。通過對偶性&#xff0c;我們可以將原始問題轉化為對偶問題&#xff0c;從而更容易求解。其中&#xff0c;拉格朗日對偶問題是對偶性的一個重要應用&#xff0c;通過拉格…

《Ai學習筆記》自然語言處理 (Natural Language Processing):機器閱讀理解-基礎概念解析01

自然語言處理 (Natural Language Processing)&#xff1a; NLP四大基本任務 序列標注&#xff1a; 分詞、詞性標注 分類任務&#xff1a; 文本分類、情感分析 句子關系&#xff1a;問答系統、對話系統 生成任務&#xff1a;機器翻譯、文章摘要 機器閱讀理解的定義 Machi…

LangChain - 建立代理

本文翻譯整理自&#xff1a;Build an Agent https://python.langchain.com/v0.2/docs/tutorials/agents/ 文章目錄 一、說明概念 二、定義工具1、TavilyAPI參考&#xff1a; 2、RetrieverAPI參考&#xff1a;API參考&#xff1a; 3、工具 三、使用語言模型四、創建代理五、運行…

《安富萊嵌入式周報》第337期:超高性能信號量測量,協議分析的開源工具且核心算法開源,工業安全應用的雙通道數字I/O模組,低成本腦機接口,開源音頻合成器

周報匯總地址&#xff1a;http://www.armbbs.cn/forum.php?modforumdisplay&fid12&filtertypeid&typeid104 視頻版&#xff1a; https://link.zhihu.com/?targethttps%3A//www.bilibili.com/video/BV1PT421S7TR/ 《安富萊嵌入式周報》第337期&#xff1a;超高性…

【Spring Boot】分層開發 Web 應用程序(含實例)

分層開發 Web 應用程序 1.應用程序分層開發模式&#xff1a;MVC1.1 了解 MVC 模式1.2 MVC 和三層架構的關系 2.視圖技術 Thymeleaf3.使用控制器3.1 常用注解3.1.1 Controller3.1.2 RestController3.1.3 RequestMapping3.1.4 PathVariable 3.2 將 URL 映射到方法3.3 在方法中使用…

用戶數據報協議UDP實現可靠傳輸的思路

一、UDP協議的特點 按照報文來分割發送。不需要建立連接和維護連接。不需要接收確認。速度較快。不確保接收的順序和發送順序一樣。 二、用UDP實現可靠通信的思路 (一)接收時發送一個確認報文 實現接收確認的機制。 (二)每個報文騰出空間放置序號 發送時設置序號&#xff0c…

如何安裝虛擬機Wmware,并且在虛擬機中使用centos系統

1. 前言 大家好&#xff0c;我是jiaoxingk 本篇文章主要講解如何安裝虛擬機&#xff0c;并且在虛擬機中安裝centos系統&#xff0c;讓windows電腦也能夠使用Linux系統 2. 虛擬機的介紹 在安裝Vmware之前&#xff0c;我們先做虛擬機的介紹 虛擬機&#xff1a;通過軟件虛擬出來的…

Docker拉取鏡像報錯:x509: certificate has expired or is not yet v..

太久沒有使用docker進行鏡像拉取&#xff0c;今天使用docker-compose拉取mongo發現報錯&#xff08;如下圖&#xff09;&#xff1a; 報錯信息翻譯&#xff1a;證書已過期或尚未有效。 解決辦法&#xff1a; 1.一般都是證書問題或者系統時間問題導致&#xff0c;可以先執行 da…

用HAL庫改寫江科大的stm32入門例子-6-2 定時器外部時鐘

實驗目的&#xff1a; 熟悉外部時鐘的應用。 實驗步驟&#xff1a; 創建項目參照前面的文章&#xff0c;集成oled(沒有oled,用uart串口傳遞也可以)選擇外部時鐘源時鐘源參數設置編寫代碼&#xff1a; 5.1聲明全局變量&#xff0c;如果發生定時器中斷的時候&#xff0c;在回調…

SW 零件插入零件的重合配合

重合配合有時候會失效,可以先用距離配合代替,之后修改距離盡量接近

AI網絡爬蟲-自動獲取百度實時熱搜榜

工作任務和目標&#xff1a;自動獲取百度實時熱搜榜的標題和熱搜指數 標題&#xff1a;<div class"c-single-text-ellipsis"> 東部戰區臺島戰巡演練模擬動畫 <!--48--></div> <div class"hot-index_1Bl1a"> 4946724 </div> …

【bash】統計服務器信息腳本

起因 寫一個bash腳本統計服務器的機器名、內網IP、CPU使用率、內存使用率、List{GPU使用率、顯存} 腳本 #!/bin/bash# 主機名 hostname$(hostname) # 內網ip ip$(ip addr | grep inet | grep -v 127.0.0.1 | awk {print $2} | cut -d/ -f1) ip$(echo "$ip"|tr \n…

Excel表格在線解密:輕松解密密碼,快速恢復數據

忘記了excel表格密碼&#xff1f;教你簡單兩步走&#xff1a;具體步驟如下。首先&#xff0c;在百度搜索中鍵入“密碼帝官網”。其次&#xff0c;點擊“立即開始”&#xff0c;在用戶中心上傳表格文件即可找回密碼。這種方法不用下載軟件&#xff0c;操作簡單易行&#xff0c;適…