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的强大功能,本篇博客将聚焦于另一个核心容器类——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
的用法,在实际编程中发挥其强大功能。
相关文章:

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.…...

QMetaObject::invokeMethod 简介
1. QMetaObject::invokeMethod的功能和用途 QMetaObject::invokeMethod是Qt框架中的一个功能强大的方法,它允许你以异步的方式调用QObject派生类的成员函数。这个功能特别有用,因为它允许你安全地在不同的线程之间调用方法,而不需要担心线程…...

2024-05-29 精神分析-孤独感-分析
摘要: 所谓的孤独感是种很笼统的感觉,可能包含了很多种不同的情绪。 比如,希望和他人建立联系,消除敌意,对他人愧疚,想要从他人那里获取关爱或者其他,也可能是感觉到自己的脆弱和无助,希望获得…...

开源与闭源AI模型的对决:数据隐私、商业应用与社区参与
引言 在人工智能(AI)领域,模型的发展路径主要分为“开源”和“闭源”两条。这两种模型在数据隐私保护、商业应用以及社区参与与合作方面各有优劣,是创业公司、技术巨头和开发者们必须仔细权衡的重要选择。那么,面对这些…...

[C语言]自定义类型详解:结构体、联合体、枚举
目录 🚀结构体 🔥结构体类型的声明 🔥结构的自引用 🔥结构体变量的定义和初始化 🔥结构体内存对齐 🔥结构体传参 🔥结构体实现位段(位段的填充&可移植性) &a…...

Vue3使用Composition API实现响应式
title: Vue3使用Composition API实现响应式 date: 2024/5/29 下午8:10:24 updated: 2024/5/29 下午8:10:24 categories: 前端开发 tags: Vue3CompositionRefsReactiveWatchLifecycleDebugging 1. 介绍 Composition API是Vue.js 3中新增的一组API,用于在组件中组…...

使用moquette mqtt发布wss服务
文章目录 概要一、制作的ssl证书二、配置wss小结 概要 moquette是一款不错的开源mqtt中间件,github地址:https://github.com/moquette-io/moquette。我们在发布mqtt服务的同时,是可以提供websocket服务器的,有些场景下需要用到&a…...

【笔记】软件架构师要点记录(2)
【笔记】软件架构师要点记录 20240523案例一案例二案例三案例四案例五案例六案例七案例十 20240523 基于前10个架构案例场景,对用到的专业术语进行整理,方便后续查看。 案例一 MVC架构风格组件交互方式 MVC是一种用来构建用户界面时采用的架构设计风格…...

56.野指针和悬空指针
一.野指针 野指针指的是指针指向的地址是未知的(随机的,不正确的地址)。 二.野指针出现的几种情况 1.定义指针未初始化 #include <stdio.h>int main(void) {int *p;*p 1;printf("*p is %d\n",*p); } 正确写法࿱…...

echarts-dataset,graphic,dataZoom, toolbox
dataset数据集配置数据 dataset数据集,也可以完成数据的映射,一般用于一段数据画多个图表 例子: options {tooltip: {},dataset: {source: [["product", "2015", "2016", "2017"],["test&q…...

AI界的“拼夕夕”登场,为上万张GPU寻找新使命
在AI领域,一个全新的竞争者已经悄然登场。 AI行业果真有着近乎颠覆性的魅力! 此次事件之后,AI界也许会迎来新一轮的血雨腥风! AI的潮流到底会怎样流转,天知道。 幻方量化,这家以量化投资闻名的公司&…...

STM32-13-MPU
STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 文章目录 STM32-12-MPU1. 内存保护单元MPU1. M…...

(超详细)字符函数和字符串函数【上】
前言 C 语言中对字符和字符串的处理很是频繁,但是 C 语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数 . 1.求字符串长度函数 strlen函数 我们要求一个字符串函数的长度…...

AUS GLOBAL 荣获 Brokersview 颁奖盛典多项殊荣
2024年1月31日在迪拜 Sheikh Zayed Rd - Trade Centre - Trade Centre 1 举行的 Brokersview 颁奖盛典上,AUS GLOBAL(澳洲环球)再次展现了其在金融行业的卓越实力,并荣获多项殊荣。 AUS GLOBAL 作为一家全球领先的金融服务提供商…...

Spring Aop 实现对mapper层入参进行重新赋值
需求描述: 需要对mapper查询的入参的某个属性值进行特殊处理后查询 不影响原来业务且方便扩展维护 1,自定义注解 import java.lang.annotation.*;/*** 针对 mapper层入参 按照一定规则进行特殊处理重新赋值*/ Target(ElementType.METHOD) Retention(Ret…...

朗读亭主要作用有哪些?
朗读亭的主要作用有以下几个方面: 1. 提供朗读服务:朗读亭是一个专门的场所,提供给人们朗读的环境和场地。人们可以在朗读亭中选择自己喜欢的书籍或文章,并通过朗读将其表达出来。这样可以帮助人们提高朗读能力,增强自…...

力扣:226. 翻转二叉树
226. 翻转二叉树 已解答 简单 相关标签 相关企业 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1]示例 2: 输入:…...

深入解析 JSONPath:从入门到精通
码到三十五 : 个人主页 在数据处理和交换领域,JSON已经成为了一种广泛使用的数据格式, 如何有效地查询和操作这些数据也变得越来越重要。在这种情况下,JSONPath 应运而生,成为了一种在JSON数据中定位和提取信息的强大工…...

Python算法设计与分析期末
Python算法设计与分析期末通常涉及对算法基础知识的理解和应用,包括但不限于以下几个方面: 算法基础:了解算法的定义、特性(确定性、有穷性、可行性等)以及算法的分类。 时间复杂度和空间复杂度:学会分析算…...

pg_lakehouse 与 datafusion
原理分析 pg_lakehouse 是 ParadeDB 推出的一个开源插件,支持对多种数据湖里的数据做分析计算。它的出现,使得 Postgres 能够像访问本地数据一样轻松访问 S3 等对象存储,轻松访问 Delta Lake 上的表格,具备数据湖分析能力。 pg_…...

基于51单片机的酒精浓度检测仪的设计
一.硬件方案 硬件部分为利用MQ3气敏传感器测量空气中酒精浓度,并转换为电压信号,经A/D转换器转换成数字信号后传给单片机系统,由单片机及其相应外围电路进行信号的处理,显示酒精浓度值以及超阈值声光报警。电路主要由51单片机最小…...

重生之 SpringBoot3 入门保姆级学习(02、打包部署)
重生之 SpringBoot3 入门保姆级学习(02、打包部署) 1.6 打包插件1.7 测试 jar 包1.8 application.properties 的相关配置 1.6 打包插件 官网链接 https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-starte…...

Java-常用模块
文章目录 日期时间stream流 日期时间 jdk8新的日期时间类 解析和格式化DateTimeFormatter类(线程安全) LocalDateTime类 Instant类 Duration类String time "2013-02-11 11:00:00";DateTimeFormatter dateTimeFormatter DateTimeFormatter.o…...

c++大作业 调整字幕的时间
作业及其需求 有时候人们能够下载一些感兴趣的视频但是发现并没有字幕,到字幕网站上查找到字幕文件,但是发现时间进度上不能完美配合,一个视频数据的例子来源于链接: BBC.巴塔哥尼亚:地球秘密乐园 https://www.aliyundrive.com/s/LmF2sgrQzMu/folder/612af030c6fa4bf4b7c…...

Nmap使用方法
Nmap 介绍 Nmap是一个免费开放的网络扫描和嗅探工具包,也叫网络映射器(Network Mapper)。该工具其基本功能有三个,一是探测一组主机是否在线;其次是扫描主机端口,嗅探所提供的网络服务;三是可…...

任务3.1:采用面向对象方式求三角形面积
面向对象编程(OOP)是一种将现实世界中的实体抽象为对象,并通过类和对象来模拟现实世界中的行为和属性的编程范式。在本实战任务中,我们通过创建一个Triangle类来模拟现实世界中的三角形,并使用面向对象的方法来求解三角…...

解读《互联网政务应用安全管理规定》网络和数据安全中的身份认证和审计合规建设
为保障互联网政务应用安全,由中央网络安全和信息化委员会办公室、中央机构编制委员会办公室、工业和信息化部、公安部制定的《互联网政务应用安全管理规定》近日印发,自2024年7月1日起施行。 规定共8章,包括总则、开办和建设、信息安全、网络…...

HTML-JavaWeb
目录 1.标题排版 2.标题样式 编辑 编辑 小结 3.超链接 4.正文排版 编辑编辑编辑5.正文布局 6.表格标签 7.表单标签 8.表单项标签 1.标题排版 ● 图片标签 :< img> src:指定图像的ur1(绝对路径/相对路径) width:图像的宽度(像素/相对于父元素的百…...

数组-检查数组内是否存在和为7的倍数的子序列
一、题目描述 二、解题思路 这里首先要分辨清楚是子序列还是子数组 原数组:[1,2,3,4,5] 子序列:元素和元素之间相对位置保持不变,但是在原数组中不一定连续,如:[1,3,4]; 子数组:元素元素之间保…...

【图像处理与机器视觉】图像处理概述与像素
什么是数字图像处理 改善图像信息,便于作出解释 方便对图像传输,储存,方便机器理解 什么是数字图像 (1)模拟图像:连续二维函数 f(x,y)表示,其中 x…...