在C++标准模板库(STL)中,vector是最常用的序列式容器之一。作为一个动态数组,vector提供了高效的随机访问能力,同时支持动态扩容的特性。理解vector的内部实现机制,对于深入掌握C++内存管理和容器设计原理具有重要意义。
vector的核心特性可以概括为以下三点:
vector通常使用三个指针来管理其内存和元素:
cpp复制T* _start; // 指向数组首元素
T* _finish; // 指向最后一个有效元素的下一个位置
T* _end_of_storage; // 指向分配内存的末尾
这种设计有几个关键优势:
提示:这三个指针的命名遵循了STL的命名惯例,_start对应begin(),_finish对应end(),这种一致性设计值得学习
最简单的构造函数创建一个空vector:
cpp复制vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
这种实现将三个指针都初始化为nullptr,符合C++最佳实践:
更实用的构造函数允许指定初始大小和填充值:
cpp复制vector(size_t n, const T& val = T()) {
reserve(n);
for(size_t i = 0; i < n; ++i) {
push_back(val);
}
}
这里有几个关键点:
支持通过迭代器范围构造是STL容器的通用做法:
cpp复制template <class InputIterator>
vector(InputIterator first, InputIterator last) {
// 计算距离预分配空间
reserve(std::distance(first, last));
while(first != last) {
push_back(*first);
++first;
}
}
这种实现需要注意:
C++11引入的初始化列表语法糖:
cpp复制vector(std::initializer_list<T> il) {
reserve(il.size());
for(const auto& elem : il) {
push_back(elem);
}
}
这种构造方式让代码更直观:
cpp复制vector<int> v = {1, 2, 3, 4}; // 清晰明了
reserve是vector性能的关键:
cpp复制void reserve(size_t n) {
if(n > capacity()) {
T* new_start = new T[n];
size_t sz = size();
// 拷贝元素
for(size_t i = 0; i < sz; ++i) {
new_start[i] = _start[i]; // 调用赋值操作符
}
delete[] _start;
_start = new_start;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
关键注意事项:
resize调整容器大小:
cpp复制void resize(size_t n, const T& val = T()) {
if(n < size()) {
_finish = _start + n; // 缩小
} else {
reserve(n); // 确保容量足够
while(_finish != _start + n) {
*_finish = val; // 填充新值
++_finish;
}
}
}
resize与reserve的区别:
operator[]实现:
cpp复制T& operator[](size_t pos) {
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const {
assert(pos < size());
return _start[pos];
}
边界检查的两种方式:
尾部操作实现:
cpp复制void push_back(const T& val) {
if(_finish == _end_of_storage) {
reserve(capacity() ? 2 * capacity() : 1);
}
*_finish = val;
++_finish;
}
void pop_back() {
assert(!empty());
--_finish;
}
性能优化点:
insert实现需要考虑扩容:
cpp复制iterator insert(iterator pos, const T& val) {
assert(pos >= _start && pos <= _finish);
if(_finish == _end_of_storage) {
size_t offset = pos - _start;
reserve(capacity() ? 2 * capacity() : 1);
pos = _start + offset; // 重新计算pos
}
for(iterator it = _finish; it != pos; --it) {
*it = *(it - 1);
}
*pos = val;
++_finish;
return pos;
}
迭代器失效的两种情况:
erase实现:
cpp复制iterator erase(iterator pos) {
assert(pos >= _start && pos < _finish);
for(iterator it = pos; it + 1 != _finish; ++it) {
*it = *(it + 1);
}
--_finish;
return pos;
}
erase后迭代器失效规则:
深拷贝实现:
cpp复制vector(const vector& other) {
reserve(other.capacity());
for(size_t i = 0; i < other.size(); ++i) {
_start[i] = other._start[i];
}
_finish = _start + other.size();
}
关键点:
拷贝交换惯用法:
cpp复制vector& operator=(vector other) {
swap(other);
return *this;
}
void swap(vector& other) noexcept {
std::swap(_start, other._start);
std::swap(_finish, other._finish);
std::swap(_end_of_storage, other._end_of_storage);
}
这种实现的好处:
扩容策略比较:
实测表明,2倍增长在内存使用和性能间取得较好平衡。
memcpy的危险场景:
cpp复制vector<vector<int>> v1(10, vector<int>(5));
vector<vector<int>> v2 = v1; // 如果使用memcpy会出问题
自定义类型必须使用:
三个级别的异常安全:
vector的关键操作应至少提供基本保证,重要操作提供强保证。
建议测试的关键场景:
我们的实现与标准库的差异:
理解这些差异有助于更好地使用标准库。
在实际项目中,建议优先使用标准库的vector,这个模拟实现主要用于学习目的。通过自己实现一遍,可以更深入地理解动态数组的工作原理和设计考量,这对提高C++编程能力大有裨益。