一. 动态数组和vector
数据结构刚开篇就是一种最基础的存储结构线性结构, 线性结构中每一个结点都只有一个前驱后继(除去首尾)
线性结构是逻辑上连续的存储结构
物理空间上连续存储的线性结构可以是数组, 动态数组等
物理空间不连续的线性结构是链表
C++STL中的vector(动态数组)就是一个线性结构
二. 自己实现简单的Vector
1. Vector 成员的声明和默认成员函数
template<class T>
class Vector{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin(){
return _start;
}
iterator end(){
return _finish;
}
const_iterator cbegin() const{
return _start;
}
const_iterator cend() const {
return _finish;
}
public:
Vector() : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){}
~Vector(){
if (_start){
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
//Vector v1(v2);
Vector(const Vector<T>& v) : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){
Reverse(v.Size());
for (size_t i = 0; i < v.Size(); i++){
this->push_back(v[i]);
}
}
// v1 = v2
Vector<T>& operator=(Vector<T> v){
Swap(v);
return *this;
}
void Swap(Vector<T>& v){
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
private:
iterator _start; // 指向数据起始
iterator _finish; // 指向数据末尾
iterator _endOfStorage; // 指向空间末尾
};
SGI版STL库中的vector实现的成员变量和数据结构中讲的 (指针 size capacity) 不同,
vector中用了三根指针
三个指针实现的效果和 (指针 size capacity 相同)
还有比较重要的一点是迭代器, vector的迭代器很简单, 底层就是一个原生指针, 迭代器的作用是让上层不必要关心底层的实现直接使用迭代器操作容器.
这里只实现了一种无参的默认构造函数, 将三个指针置空, 我们平时使用STL中的vector时, 常用的操作
vector v;
v.push_back(1); v.push_back(2);
所以就只实现了一种构造
拷贝构造和重载=, 因为要进行深拷贝, 必须自己手动实现,
重载= 使用了交换的 方式实现
三. 常用接口实现
下面是 常用的接口的实现, push_back, pop_back, operator[], Resize(), Reserver()
push_back, 和 pop_back 是 O(1)的接口, 所以平时使用vector时要少用中间插入(搬移数据)
operator[] 因为物理空间是连续的所以vector支持随机访问, 也是一个O(1)的接口
template<class T>
class Vector{
public:
T& operator[](size_t pos){
assert(pos < Size());
return _start[pos];
}
const T& operator[](size_t pos) const {
assert(pos < Size());
return _start[pos];
}
size_t Size() const{
return _finish - _start;
}
size_t Capacity() const{
return _endOfStorage - _start;
}
void push_back(const T& x){
if (_finish == _endOfStorage){
int newCapacity = Capacity() == 0 ? 4 : 2 * Capacity();
Reverse(newCapacity);
}
*_finish = x;
++_finish;
}
void pop_back() {
assert(_finish > _start);
--_finish;
}
void Resize(int n, const T& x = T()){
if (n <= Size()){
_finish = _start + n;
}else {
Reverse(n);
while(_finish != _start + n){
*_finish = x;
_finish++;
}
}
}
void Reverse(int n){
if (n > Capacity()){
T* tmp = new T[n];
int size = Size();
if (_start){
for (int i = 0; i < size; i++){
tmp[i] = _start[i];
}
}
delete [] _start;
_start = tmp;
_finish = _start + size;
_endOfStorage = _start + n;
}
}
};
push_back 首先要检查空间是否够用(是否初始化空间) 然后使用Reverse增容
四. erase, insert 和 迭代器失效
void Insert(iterator pos, const T& x){
assert(pos <= _finish);
if (_finish == _endOfStorage){
size_t n = pos - _start;
size_t newCapacity = Capacity() == 0 ? 4 : 2 * Capacity();
Reverse(newCapacity);
pos = _start + n;
}
iterator begin = _finish;
while(begin != pos){
*begin = *(begin - 1);
begin--;
}
*pos = x;
++_finish;
}
iterator Earse(iterator pos){
assert(pos < _finish);
iterator begin = pos;
while(begin != _finish - 1){
*begin = *(begin + 1);
++begin;
}
--_finish;
return pos;
}
使用vector的insert 和 earse 时, 会有一种迭代器失效的现象, 具体情况一般是这样:
1. earse必定失效(由编译器决定是否中断程序)
2. insert中如果发生扩容迭代器失效
迭代器失效一般发生在这种场景下:
vector v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) v.erase(it) ++it; // 编译器一般会直接报错, 中断程序 } pos = find(v.begin(), v.end(), 3); v.insert(pos, 30); cout << *pos; //增容会导致此处非法访问
我们可以用我们的Vector来研究一下迭代器失效的场景:
首先是insert的分析
可以看出insert迭代器失效是因为内部的扩容, pos仍然指向旧的空间
然后是earse导致迭代器失效的分析:
我们了解了迭代器失效的原理, 在今后写代码的过程中要避免迭代器失效.
vector earse() 和 insert() 接口都需要搬移数据, 所以如果需要频繁在中间插入删除时, 不应该选用vector