前言:
本文主要讲在C++ STL库中vector容器的使用方法和底层的模拟实现~
成员变量的定义:
对于vector容器,我们首先采用三个成员变量去进行定义,分别是:
private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的 下一个 位置!!!iterator _endOfStorage; // 指向存储容量的尾
迭代器的书写:
因为vector底层存储空间是连续的,我们可以直接用指针作为迭代器,分别是const和非const类型。注意我们需要用类模板去定义
// Vector的迭代器是一个原生指针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;}
构造函数:
我们使用初始化列表进行初始化。
vector():_start(nullptr), _finish(nullptr),_endOfStorage(nullptr){}
拷贝构造函数:
对于自定义类型,并且需要深拷贝的情况下,需要自己书写拷贝构造函数,编译器默认生成的
拷贝构造函数只适用于浅拷贝,或者内置类型。
//深拷贝需要自己写一份拷贝构造函数vector(const vector<T>& v){_start = new T[v.capacity()];memcpy(_start, v._start, v.size() * sizeof(T));_finish = _start + v.size();_endOfStorage = _start + v.capacity();}
赋值重载函数:
赋值重载同样也需要深拷贝,这里我们采用现代的写法,比较简洁。
vector<T>& operator= (vector<T> v){swap(v);return *this;}
析构函数:
利用delete去释放空间,并且将内部成员变量赋值为空指针。
~vector(){if (_start){delete[] _start;_start = _finish = _endOfStorage = nullptr;}}
常用的size() 和 capacity()函数接口
size_t size() const{return _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}
reserve函数:
reserve函数主要是用来扩容开空间,并且对新开辟的空间不做初始化。
我们一般需要将原来的空间进行析构,在析构前需要将原本位置的值拷贝给新的空间
void reserve(size_t n){size_t len = _finish - _start;if (n > _endOfStorage - _start){T* tmp = new T[n];//memcpy(tmp, _start, sizeof(T) * len); 注意memcpy和memove是浅拷贝for (int i = 0; i < len; i++){//tmp[i] = _start[i];*(tmp + i) = *(_start + i);}delete[] _start;//注意要将原来的析构_start = tmp;_finish = _start + len;_endOfStorage = _start + n;}}
resize函数:
resize函数适用于开空间和缩容,一般是用来扩容,那么这个函数和reserve有什么区别呢?
最大的区别就是resize会将新开辟的空间进行初始化,而reserve对于新开辟的空间不做任何处理。
void resize(size_t n, const T& value = T()){if (n > size()){//先判断是否需要扩容if (n > capacity()){reserve(n);}for (int i = 0; i < n-size(); i++){*(_finish + i) = value;}_finish += n - size();}else{_finish = n + _start;}}
push_back()函数
此函数接口主要用来尾插。实现与用法比较简单。
void push_back(const T& x){//判断是否需要扩容if (_finish == _endOfStorage){size_t capacity = _endOfStorage - _start;reserve(capacity == 0 ? 4 : capacity * 2);}*_finish = x;//注意解引用_finish++;}
pop_back()函数
这个函数与push_back()相对应,进行尾删。
void pop_back(){assert(_finish > _start);_finish--;}
swap交换函数
顾名思义就是将两个vector容器内容以及大小进行交换,这里我们直接借用std标准库的swap函数实现。
void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}
insert()
插入函数,我们可以在任意位置进行插入,注意函数的第一个参数pos并不是下标值,而是迭代器!
这里的插入如果需要扩容会导致怕pos的迭代器失效问题!
iterator insert(iterator pos, const T& x)//注意第一个参数是迭代器!{assert(pos >= _start);assert(pos < _finish);size_t capacity = _endOfStorage - _start;//先判断扩容if (_finish == _endOfStorage){size_t len = pos - _start;size_t newcapacity = capacity == 0 ? 4 : capacity * 2;reserve(newcapacity);pos = len + _start;//需要重新确定pos的位置}//不要浅拷贝//memove(_start + pos + 1, _start + pos, sizeof(T) * (size() - pos));iterator tmp = _finish;while (tmp != pos){*tmp = *(tmp - 1);tmp--;}*pos = x;++_finish;return pos;}
erase函数
这个函数主要用来将任意位置的值进行删除,这里的第一个参数同样也是迭代器!
//注意参数是迭代器iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator tmp = pos;while (tmp < _finish){*tmp = *(tmp + 1);tmp++;}_finish--;return pos;}
关于vector容器使用和实现的注意点:
1、迭代器失效
如果调用insert函数,并且扩容的话,原先pos迭代器就会失效,因为pos迭代器指向的是原来空间的pos位置,但是经过扩容之后,原先的空间会被销毁,生成新的空间,所以我们需要先记录迭代器在空间的相对位置,这样方便在扩容后创建的新空间不会失效。
所以我们尽量不要使用insert和erase过后的迭代器 insert和erase 形参pos都可能会失效
2、reserve 不能使用memcpy/memove的原因 (深层次的深拷贝的问题)
memcpy 和 memove都是浅拷贝!!!对于自定义类型的要求的深拷贝,memcpy和memove只将值拷贝了下来,我们要用赋值就是深拷贝。
对于内置类型,或者对于只需要浅拷贝的自定义类型,我们使用memove没有任何问题。
但是针对于自定义类型需要深拷贝的情况,我们不能使用memcpy或者memove,因为这两个都只能解决浅拷贝的问题。
比如这里的_str如果只是浅拷贝。没有创建空间,那么在delete之后,这段空间就已经销毁了,所以此时的tmp就是野指针,因此我们需要用赋值进行深拷贝!