目录
概述
迭代器
数据结构
优点和缺点
接口介绍
begin
end
rbegin
rend
resize
reseve
insert
erase
其他一些接口
模拟实现
框架
获取迭代器
深浅拷贝
赋值重载
reseve
resize
拷贝构造
构造
析构
insert
erase
其他
概述
vector是STL的容器之一。vector的底层结构类似于数组——在内存中开辟一块连续的空间。与数组不同的是vector可以动态的改变空间的大小(扩容或缩容)。
vector一般不会缩容,而是会经常的扩容——扩容的大小总比用户需要的多,这和vector的扩容机制有关。
vector不支持原地扩容,会新开辟一块更大的空间。将旧空间的值浅拷贝给新空间,然后释放旧空间。vector的空间的动态改变是有代价的,尤其是数据量特别大时,不会轻易缩容。
不同平台的扩容原则是一样的,但扩容的细节并不相同。VS下是根据旧空间的1.5倍扩容,g++是根据旧空间的2倍扩容。
迭代器
vector的迭代器不需要像list的那样把普通指针进行封装,详见:http://t.csdnimg.cn/76tVf
因为vector维护的是一段线性空间,它的底层是一块连续的空间。普通的指针刚好能完成数据的随机访问,空间的遍历,数据的存取等。下面是迭代器的源码定义。
templat <class T, class Alloc = alloc>
class vector
{public:
typedef T value_type;
typedef value_type* iterator; //......
}
T*就是vector的迭代器。如果vector存的是int类型的数据,vector的迭代器就是int*类型的指针。存的如果是string类(自定义类型)类型的数据,vector的迭代器就是string*类型的指针。
迭代器失效
扩容会引发迭代器失效。首先获取了一个空间的迭代器,然后这个空间发生了扩容,此时这个迭代器是指向旧空间的,旧空间会被归还给操作系统。这种情景下的迭代器失效可以理解为野指针问题。VS环境下会强制报错。如下图
数据结构
vector管理线性空间用了三个迭代器,如下是源代码定义
templat <class T, class Alloc = alloc>
class vector
{
//......
protected:
iterator start;
iterator finish;
iterator end_of_storage;
//......
}
start——指向空间的开头
finish——指向有效数据的下一个位置
end_of_storage——指向有效空间的下一个位置
优点和缺点
优点:支持随机访问,排序消耗的时间复杂度比其他容器低
有如下代码,验证三大容器vector, list, deque,在相同数据量,相同数据,数据的顺序也相同的情况下,用vector容器的排序有多大优势。在release环境下验证
#include<vector>
#include<list>
#include<deque>
#include<algorithm>
#include<time.h>
#include<iostream>void Test()
{
//数据量int N = 100000;//十万//int N = 1000000;//百万//int N = 10000000;//千万//int N = 100000000;//一亿 std::vector<int> v; //三大容器std::list<int> l;std::deque<int>d;for (int i = N; i > 0; i--) //插入相同的数据,数据顺序也相同{int e = rand(); v.push_back(e);l.push_back(e);d.push_back(e);};clock_t begin1 = clock(); //时间函数sort(v.begin(), v.end()); clock_t end1 = clock(); clock_t begin2 = clock(); l.sort();clock_t end2 = clock(); clock_t begin3 = clock(); sort(d.begin(), d.end()); clock_t end3 = clock(); printf("vector的用时是%d毫秒\n", end1 - begin1); printf("list的用时是%d毫秒\n", end2 - begin2); printf("deque的用时是%d毫秒\n", end3 - begin3);}int main()
{Test();return 0;
}
结果展示
即使排了一亿个数据,快排加vector容器也只用了4秒。list的底层是链表用了将近两分钟,效率低下。deque是一个类似于vector和list的结合体的容器,用了20秒。vector最引以为豪的优势——排序的效率高。原因在于vector的底层是连续的空间,支持随机访问,只需O(1)复杂度便可访问任意位置的数据。
在空间足够的情况下,尾部插入,尾部删除的效率为O(1)。
缺点:头部插入,头部删除,随机位置插入,随机位置删除,效率为O(N)。原因很简单:这些操作都需要挪动数据。如果数据量大,并且要频繁的头插头删,这便是堪比一个O(N^2)的算法。在标准库的接口中,并没有直接给头插,头删的接口。
接口介绍
begin
获取第一个数据位置的迭代器或const迭代器 |
end
获取最后一个有效数据的下一个位置的迭代器或const迭代器 |
rbegin
获取最后一个有效数据的迭代器或const迭代器 |
rend
获取第一个有效数据的前一个位置的迭代器 |
resize
改变vector容器有效数据的个数,改变为n个数据。如果n小于有效空间,删数据。n大于有效空间,扩容,并把多余的有效数据初始化为val |
reseve
改变容器的有效空间。n小于有效空间时,不做处理。n大于有效空间时,把空间开至n或更大。 |
insert
在迭代器position之前插入val |
在迭代器position之前插入n个val |
在迭代器position之前插入迭代器区间first到last的元素 |
erase
删除迭代器position位置的元素,或删除迭代器区间first到last的元素 |
其他一些接口
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
push_back | 尾插 |
pop_back | 尾删 |
operator[] | 像数组一样访问 |
模拟实现
框架
namespace bit
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}
获取迭代器
iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}
深浅拷贝
过vector容器存的是自定义类型,它们的数据可能会指向某一块空间。当我们想要新的vector容器拷贝旧的vector容器数据时,新的vector容器是否要额外开空间储存自定义类型指向的空间的数据,便涉及深浅拷贝问题。
如下示意图
上文已经提到扩容时是浅拷贝,当 vector 需要扩容时,它会分配一个新的更大的内存块,然后将原来的元素拷贝到新的内存中,并释放原来的内存。这确保了在扩容时,原有元素的地址不会改变,从而避免了深拷贝的开销。
而在拷贝构造函数和赋值运算符中,vector 会执行深拷贝,即它会复制其中的每个元素,而不是简单地复制指向内存的指针。这样,每个vector 对象都有自己独立的内存存储其元素,互不影响,也避免了浅拷贝可能带来的问题。
有了上述了解,模拟实现一下赋值重载
赋值重载
现在写法
void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}
reseve
void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * sz);for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}
resize
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;}}}
拷贝构造
vector(const vector<T>& v){_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T)*v.size());for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i]; //赋值重载}_finish = _start + v.size();_endofstorage = _start + v.capacity();}
构造
vector(size_t n, const T& val = T()){resize(n, val);}
析构
~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}
insert
iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);// 解决pos迭代器失效问题pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}
erase
iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}--_finish;return pos;}
其他
void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}size_t capacity() const{return _endofstorage - _start;}size_t size() const{return _finish - _start;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}
本篇内容就到这里啦