目录
概述
迭代器
数据结构
优点和缺点
接口介绍
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];
}
本篇内容就到这里啦