
目录
1. Vector的介绍
- vector表示可变大小数组的序列容器
- 与数组一样,vector采用的是使用连续空间来储存元素,那也就意味着,采用下标对vector进行访问和数组一样高效。但是相较于数组大小的固定,vector的大小是动态可变的,并且这个变化是由容器自动处理的
- 本质上讲,vector是动态分配数组大小实现的,当有新元素插入时,当需要扩容空间时,数组的大小将会重新分配。具体的做法是,重新开辟一块更大的内存,然后将原数组的元素拷贝过去(很明显是一个 代价很高的任务)
- vector的空间分配策略:每一次分配空间都是按照对数级增长(一般是1.2或者是1.5),形象地来讲就是一种囤地策略,每次重新分配都会多囤一点,这样的话插入时,有地方就话直接插入,如此每一次重新分配内存的高额代价就会被多囤地的免费插入摊平,总的来看,末尾插入的时间代价还是常数
- 总的来说,vector访问元素、在末尾添加删除元素比较高效,但是其他操作相对效率低
2. Vector的使用
vector在日常的使用非常广泛,我们应该熟悉它的常用接口。接下来我们从基础的接口开始,学会它的使用及模拟实现。
vector模拟实现的基本结构:
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//无参构造
vector()
:_start(nullptr) //数组首地址
, _finish(nullptr) //当前末尾元素的下一个地址
, _endofstoage(nullptr) //当前分配空间的下一地址
{
}
//资源管理
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstoage = nullptr;
}
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstoage - _start;
}
private:
iterator _start;
iterator _finish;
iterator _endofstoage;
};
2.1. vector的定义
构造函数声明constructor 接口说明 vector() (重点) 无参构造 vector (const vector& x); (重点) 拷贝构造 vector ( size_type n, const value_type& val = value_type() ) 构造并初始化 n 个 val vector (InputIterator first, InputIterator last); 使用迭代器进行初始化构造 //无参构造 vector() :_start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) {} //拷贝构造 void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstoage, v._endofstoage); } //vector(const vector& v) vector(const vector<T>& v) :_start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { vector tmp(v.begin(), v.end()); swap(tmp); } //初始化n个val vector(size_t n, const T& val = T()) :_start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { reserve(n); for (size_t i = 0; i < n; ++i) { push_back(val); } } //使用迭代化区间初始化 template <class InputIterator> vector(InputIterator first, InputIterator last) :_start(nullptr) , _finish(nullptr) , _endofstoage(nullptr) { while (first != last) { push_back(*first); ++first; } }
2.2. vector迭代器的使用
iterator的使用 接口说明 begin+end (重点) 获取第一个数据位置的 iterator/const_iterator , 获取最后一个数据的下一个位置的 iterator/const_iterator rbegin+rend(反向迭代器) 获取最后一个数据位置的 reverse_iterator ,获取第一个数据前一个位置的reverse_iterator iterator begin() { return _start; } iterator end() { return _finish; } const iterator begin() const { return _start; } const iterator end() const { return _finish; }
2.3. vector的空间增长问题
容量空间 接口说明 size 获取数据个数 capacity 获取容量大小 empt 判断是否为空 resize(重点) 改变 vector 的 size reserve(重点) 改变 vector 的 capacity void resize(size_t n, T val = T()) { if (n > capacity()) { reserve(n); } if (n > size()) { while (_finish <= _start + n) { *_finish = val; ++_finish; } } else { _finish = _start + n; } } void reserve(size_t n) { size_t sz = size(); if (n > capacity()) { T* tmp = new T[n]; if (_start) { for (int i = 0; i < sz; ++i) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; } _finish = _start + sz; _endofstoage = _start + n; }注意:
vector在扩容的时候有一个小细节,那就是vector的扩容在vs和gcc下是有区别的,vs下是1.5倍扩容,然而在在gcc下是2倍扩容。所以不能固话地认为vector的扩容就是2倍。具体增长的多少要根据需求定义。Vs是PJ盘本的STL,g++是SGI版本的STL。
我们可以在VS和Clion两个环境下试一下:
//vs下 int main() { vector<int> v; size_t sz = v.capacity(); for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } return 0; }VS中的结果:
Clion中gcc的结果:
3. vector中的增删改查
| vector 增删查改 | 接口说明 |
| push_back (重点) | 尾插 |
| pop_back (重点) | 尾删 |
| 查找。(注意这个是算法模块实现,不是 vector 的成员接口) | |
| 在 pos 之前插入 val | |
| 删除 pos 位置的数据 | |
| 交换两个 vector 的数据空间 | |
| operator[] (重点) | 像数组一样访问 |
3.1. push_back(尾插)
//尾插 void push_back(const T& x) { if (_finish == _endofstoage) { size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newcapacity); } *_finish = x; ++_finish; }
3.2. pop_back(尾删)
这个地方熟悉一点的同学都知道,尾删的逻辑是覆盖而并不是删除:
//尾删 void pop_back(const T& x) { if (_finish > _start) { --_finish; } }
3.3. insert
- 首先要判断pos是否合法
- 然后判断是否需要扩容,如果需要这里采用了一个相对量来保证pos的正确性
- 然后从后往前移动元素,前面的覆盖后面的
- 最后插入值,然后更新_finish,返回pos
iterator insert(iterator pos, const T& x) { assert(pos <= _finnsh && pos >= _start); if (_finish == _endofstoage) { //这里使用pos到首地址的偏移量 //因为扩容之后原先pos的失效了 size_t n = pos - _start; size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newcapacity); pos = _start + n; } iterator end = _finish - 1; while(end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; return pos; }PS:注意这里我们将pos声明为iterator,可能有人会问为什么不使用下标更加方便呢?当时在这里使用下标确实会更加方便,但是这个容器不是vector了,而是list或者map,就并不适配。
3.4 erase
- 判断pos是否合法
- 然后循环congpos开始,后一个覆盖前一个
- 更新_finish
- 返回pos
iterator erase(iterator pos) { assert(pos >= _start && pos <= _finish); iterator end = pos + 1; while (end < _finish) { *(end - 1) = *end; ++end; } --_finish; return pos; }
3.5 operator [ ]
这个函数比较简单,只要返回pos所在位置的元素即可:
T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; }
4. 测试
int main() {
// 1. 默认构造 + push_back
v::vector<int> v1;
for (int i = 1; i <= 5; i++) {
v1.push_back(i * 10);
}
cout << "v1 after push_back: ";
for (size_t i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << "\n";
// 2. 测试 insert
auto it = v1.begin() + 2;
v1.insert(it, 99);
cout << "v1 after insert 99 at pos 2: ";
for (size_t i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << "\n";
// 3. 测试 erase
v1.erase(v1.begin() + 3);
cout << "v1 after erase at pos 3: ";
for (size_t i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << "\n";
// 4. 测试 pop_back
v1.pop_back();
cout << "v1 after pop_back: ";
for (size_t i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << "\n";
// 5. 测试拷贝构造
v::vector<int> v2 = v1;
cout << "v2 (copy of v1): ";
for (size_t i = 0; i < v2.size(); i++) {
cout << v2[i] << " ";
}
cout << "\n";
// 6. 测试初始化构造 (5个元素,值全为 7)
vector<int> v3(5, 7);
cout << "v3 (5 elements = 7): ";
for (size_t i = 0; i < v3.size(); i++) {
cout << v3[i] << " ";
}
cout << "\n";
// 7. 测试迭代器区间构造
v::vector<int> v4(v1.begin(), v1.end());
cout << "v4 (constructed from v1): ";
for (size_t i = 0; i < v4.size(); i++) {
cout << v4[i] << " ";
}
cout << "\n";
return 0;
}

(本篇完)


1268

被折叠的 条评论
为什么被折叠?



