1.vector基本介绍
1. vector是表示可变大小数组的序列容器。
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
2.vector的使用
关于vector的使用,详细可以查看文档vector的使用 ,以下是我自己的总结:
关于vector的常用的主要分为以下几个部分:
首先需要了解vector所在头文件,可以查阅文档。
其次要知道vector是STL中的容器,同时是类模板,使用时要显示实例化。
1.初始化方式:
//常用的初始化方式
#include <vector>
#include<initializer_list>
using namespace std;
int main()
{
vector<int> v1;//默认构造,有效元素个数为0
vector<int> v2 = {1,2,3,4,5,6};//使用initializer_list初始化,也是C++11的新增内容,可以用一个列表初始化容器
//可以包含头文件initializer_list或者iostream
vector<int> v3(v2);//拷贝构造
vector<int> v4(v2.begin(), v2.end());//迭代器区间构造
int arr[] = { 1,2,3 };
vector<int> v5(arr, arr + 3);//也可以这样隐式类型转换调用迭代器区间构造
}
2.增删查改
#include <vector> //头文件名称可在文档中查阅
#include <algorithm>
using namespace std; //STL的组件都在std的命名空间中,我为了方便演示展开命名空间
int main()
{
vector<int> v;//定义一个vector<int> 类型
//此时v的容量为0,有效数据个数为0,可以通过v.capacity以及v.size()查看
v.push_back(1);//尾插
v.pop_back();//尾删
v.push_back(1);
v.push_back(1);
v.insert(v.begin() + 1, 2);//指定位置之前插入
v.erase(v.begin() + 1);//指定位置删除
//注意:insert和erase均可能引起迭代器失效,不同的编译器可能报错或不报错,
//为了提高代码的移植性,建议统一在插入和删除操作后将迭代器通过这俩函数的返回值进行更新
auto it = find(v.begin(), v.end(),2);//STL并没有在vector中实现find(),而是在算法组件中实现
//了一个函数模板find(),
//使它通用于STL中任意提供随机迭代器的容器
return 0;
}
3.迭代器的使用
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int>::iterator it1 = v1.begin();//使用begin()接口获取起始位置的迭代器
vector<int>::iterator it2 = v1.end();//使用begin()接口获取末位置的迭代器(实际上取得的是有效位置的下一个位置)
//迭代器的可用范围是begin()<= it < end(),
//这里vector的迭代器是随机迭代器,支持比较大小和判断相等,以及加减
//其他的例如单向迭代器只能判断相等,而不能加减以及
cout << *it1 << endl;//通过*操作符解引用迭代器可以获得相应元素的值
while (it1 != v1.end())//常规的迭代器遍历用法
{
cout << *it1 << ' ';
}
cout << endl;
for(int i = 0;i<v1.size();++i)//常规的迭代器遍历用法
{
cout << *(v1.begin() + i) << ' ';//迭代器支持加减整数运算
//也可以++,--,自增自减
}
cout << endl;
while (it1 != v1.end())//常规的迭代器遍历用法
{
*it1++; //迭代器解引用后也可以修改,
//注意const对象生成的迭代器为 const_iterator,
//可以修改迭代器本身,但不能修改迭代器指向的值
}
cout << endl;
sort(v1.begin(), v1.end());//配合算法组件,通过传迭代器区间排序
}
4.下标 []访问(类似于访问数组元素)
#include <vector>
using namespace std;
int main()
{
vector<int> v1 = { 1,2,3,4,6,7,8,9 };
for (int i = 0; i < v1.size(); ++i)
{
cout << v1[i] << endl;//与数组的操作方式如出一辙
v1[i] = 1; //数组怎么用这里就怎么用
}
return 0;
}
3.vector的模拟实现
0.注意
在写类模板时,里面的函数模板的声明和定义不能分离在两个文件中,否则编译器无法识别,原因和模板的实例化方式有关,可以查阅相关资料了解。对此,解决方案是把分离开的声明和定义都放在同一 .h头文件中,这样就可以实现声明和定义分离并且不会报错。
1.vector的基本结构
template<class T>
class vector
{
public:
//函数声明
//迭代器
typedef T* iterator;//因为迭代器模拟的是指针的行为,
//vector的底层数据结构刚好又可以用指针来管理
//所以简单将迭代器定义成原生指针
typedef const T* const_iterator;
iterator begin();
const_iterator begin()const;
iterator end();
const_iterator end()const;
const_iterator cbegin()const;
const_iterator cend()const;
//基本的成员函数
vector();//默认构造
vector(int n, const T& value = T());//n个元素构造
template<class InputIterator>
vector(InputIterator first, InputIterator last);//迭代器区间构造
vector(const vector<T>& v);//拷贝构造
vector<T>& operator=(vector<T> v);//赋值运算符重载
~vector();//析构函数
//接口
size_t size()const;//获取有效元素个数
size_t capacity()const;//获取容量大小
void reserve(size_t n);//预定空间
void resize(size_t n, const T& value = T());//设定有效元素个数
T& operator[](size_t pos);//[]运算符重载
const T& operator[](size_t pos)const;
void push_back(const T& x);//尾插
void pop_back();//尾删
void swap(vector<T>& v);//交换
iterator insert(iterator pos, const T& x);//指定位置插入
iterator erase(iterator pos);//指定位置删除
private:
iterator _start;//指向数据开头
iterator _finish;//指向数据末尾(有效位置的下一个)
iterator _endOfStorage;//指向容量的末尾(有效位置的下一个)
};
2.构造,析构等
template<class T>
vector<T>::vector()//默认构造
:_start(nullptr)//用初始化列表
, _finish(_start)//按照声明顺序初始化
, _endOfStorage(_start)
{}
template<class T>
vector<T>::vector(int n, const T& value)//n个元素构造
:_start(new T[n])
, _finish(_start + n)
, _endOfStorage(_start + n)
{
T* cur = _start;
while (cur != _finish)
{
*cur = value;//依次赋值
++cur;
}
}
template<class T>
vector<T>::vector(const vector<T>& v)//拷贝构造
{
int size = v._finish - v._start;
int capacity = v._endOfStorage - v._start;
_start = new T[capacity];
_finish = _start;
_endOfStorage = _start + capacity;
for (const auto& e : v)//自动识别和遍历,在迭代器实现后才可以使用
//是C++11更新的内容
//可以查阅相关资料了解
{
push_back(e);//复用尾插接口
}
}
template<class T>
template<class InputIterator>
vector<T>::vector(InputIterator first, InputIterator last)//迭代器区间构造
{
InputIterator cpy = first;
while (cpy != last)
{
push_back(*cpy);//复用尾插接口
++cpy;
}
}
3.迭代器
//实现各种重载
//这里因为是直接typedef T* iterator
//迭代器接口的实现比较简单,所以也不再详细说明
template<class T>
typename vector<T>::iterator vector<T>::begin()
{
return _start;//返回起始位置迭代器
}
template<class T>
typename vector<T>::iterator vector<T>::end()
{
return _finish;//返回末尾位置迭代器(有效位置的下一个)
}
template<class T>
typename vector<T>::const_iterator vector<T>::begin()const
{
return _start;//返回只读的起始位置迭代器
}
template<class T>
typename vector<T>::const_iterator vector<T>::end()const
{
return _finish;//返回只读的末尾位置迭代器(有效位置的下一个)
}
template<class T>
typename vector<T>::const_iterator vector<T>::cbegin() const
{
return _start;//返回只读的起始位置迭代器
}
template<class T>
typename vector<T>::const_iterator vector<T>::cend() const
{
return _finish;//返回只读的末尾位置迭代器(有效位置的下一个)
}
4.基本接口
template<class T>
size_t vector<T>::size() const//获取有效元素个数
{
return _finish - _start;
}
template<class T>
size_t vector<T>::capacity()const//获取容量大小
{
return _endOfStorage - _start;
}
template<class T>
void vector<T>::swap(vector<T>& v)//交换(特殊实现,避免大量的深拷贝拉低效率)
{
T* tmp = v._start; //同类对象之间可以互相访问彼此的私有成员
v._start = _start; //交换指针
_start = tmp;
tmp = v._finish;
v._finish = _finish;
_finish = tmp;
tmp = v._endOfStorage;
v._endOfStorage = _endOfStorage;
_endOfStorage = tmp;
}
template<class T>
void vector<T>::resize(size_t n, const T& value)//设定有效元素个数
{
if (n > capacity())
{
reserve(n * 2);
}
_finish = _start + n;
}
template<class T>
void vector<T>::reserve(size_t n)//预定空间(可以提前开空间减少扩容的消耗)
{
if (n <= capacity())
return;
T* newv = new T[n];
size_t oldsize = size();
for (int i = 0; i < size(); ++i)
{
newv[i] = _start[i];
}
delete[] _start;
_start = newv;
_finish = _start + oldsize;
_endOfStorage = _start + n;
}
template<class T>
T& vector<T>::operator[](size_t pos)//[]运算符重载
{
return _start[pos];
}
template<class T>
const T& vector<T>::operator[](size_t pos)const//[]运算符重载const版本
{
return _start[pos];
}
template<class T>
void vector<T>::push_back(const T& x)//尾插
{
insert(begin() + size(), x);
}
template<class T>
void vector<T>::pop_back()//尾删
{
if (size() == 0)
return;
--_finish;
}
template<class T>
typename vector<T>::iterator vector<T>::insert(iterator pos, const T& x)//指定位置插入
{
assert(pos >= begin() && pos <= end());
int n = pos - _start;//记录pos和_start的位置关系
if (_finish == _endOfStorage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
pos = _start + n;//扩容后pos可能失效,需要更新
iterator mov = end() - 1;
while (pos <= mov)
{
*(mov + 1) = *mov;
--mov;
}
*pos = x;
++_finish;
return pos;
}
template<class T>
typename vector<T>::iterator vector<T>::erase(iterator pos)//指定位置删除
{
assert(pos >= _start && pos < _finish);
T* mov = pos;
while (mov < end())
{
*(mov) = *(mov + 1);
++mov;
}
--_finish;
return pos;//返回删除位置的下一个位置
}
5.总结
vector的底层数据结构是顺序表,因此它拥有顺序表的优点与缺点,他的扩容逻辑也和顺序表一致,同时,vector是一种泛型的顺序表,可以适用于任意数据类型的顺序表存储。实现时要把握模板的通用特点,具体实现中要强调“通用”,可以在实现中自己体会。
需要重点注意的是insert函数和push_back函数实现中的扩容会导致原本传入的迭代器失效,需要更新,否则会导致逻辑错误(可参考我的代码以及标注理解)。