目录
- 1. vector与顺序表
- 2. vector的使用
- 2.1 默认成员函数
- 2.2 vector的迭代器
- 2.3 容量相关成员函数
- 2.4 遍历访问方式
- 2.5 vector的修改操作
- 3. vector的自实现
- 3.1 自实现功能
- 3.2 vector的结构与自实现的逻辑
- 3.3 自实现vector功能接口
1. vector与顺序表
- 我们在初阶数据结构中学习了简单数据结构链表,这一数据结构的要求为底层物理空间连续,且支持头插尾插等插入操作,头删尾删等删除操作。在C++的STL中也有对这一简单数据结构的实现,将其模板化加入库中,不仅提高了泛用性还提供了便捷的接口,接下来,就让我们对这一容器vector进行掌握学习。
2. vector的使用
2.1 默认成员函数
1. 构造与拷贝构造
//默认构造,内容为空
vector<int> v1;
//指定数据内容与个数构造
vector<int> v2(3,100);
//迭代器区间构造
vector<int> v3(v2.begin(), v2.end());
//使用列表构造
vector<int> v4({1,2,3,4,5});
//拷贝构造
vector<int> v5(v4);
2. operator=赋值运算符重载
vector<int> v1(3, 100);
vector<int> v2;
//给已经存在的对象赋值
v2 = v1;
//当使用赋值运算符对还没有产生的对象赋值时,会调用拷贝构造
vector<int> v3 = v1;
2.2 vector的迭代器
//正向反向迭代器
vector<int> v1;
vector<int>::iterator it1 = v1.begin();
vector<int>::iterator it2 = v2.end();
vector<int>::reverse_iterator it3 = v3.rbegin();
vector<int>::reverse_iterator it4 = v4.rend();
//const修饰的正反向迭代器
vector<int>::const_iterator it5 = v5.cbegin();
vector<int>::const_iterator it6 = v6.cend();
vector<int>::const_reverse_iterator it7 = v7.crbegin();
vector<int>::const_reverse_iterator it7 = v8.crend();
2.3 容量相关成员函数
1. 容量大小
vector<int> v1(3, 100);
//当前存储的数据个数
cout << v1.size() << endl;
//理论上最多能够存储的数据个数,无参考意义
cout << v1.max_size() << endl;
//当前vector的容量
cout << v1.capacity() << endl;
//检测当前vector是否为空,无存储元素
cout << v1.empty() << endl;
2. 扩容与缩容
vector<int> v2(3,100);
//将当前vector扩容至指定大小,1.5被扩容
v2.reserve(10);
//将当前vector扩容至指定大小,再将扩容出的部分用指定数填满
//vector长度跟随增长,未指定时,默认使用0填充
v2.resize(20);
//缩容,将当前大于有效数据长度的部分缩减
//将缩小当前数据的长度
v2.shrink_to_fit();
2.4 遍历访问方式
vector<int> v1(5,100);
//operator[]运算符重载
//报警告
v1[0];
//抛异常
v1.at[1];
//返回vector的首尾元素
v1.front();
v.back();
2.5 vector的修改操作
1. 插入,赋值操作
vector<int> v1(5, 100);
vector<int> v2;
//assign
//指定用多少个什么数赋值
v2.assgin(4,100);
//使用迭代器区间赋值
v2.assgin(v1.begin(), v2.end());
//尾插
v2.push_back(10);
//随机插入
//在指定位置(迭代器表示),插入一个数
v2.insert(v2.begin(), 10);
//在指定迭代器位置,插入n个数
v2.insert(v2.begin(), 3, 10);
//在指定迭代器位置插入一段迭代器区间
v2.insert(v2.begin(), v1.begin(), v1.end());
- 补充:头插的效率很低,且可以通过随机插入代为实现头插,所以vector没有去实现头插
2. 删除操作
vector<int> v1(10, 100);
//尾删
v1.pop_back();
//删除指定迭代器位置的数据
v1.erase(v1.begin());
//删除指定迭代器区间的数据
v1.erase(v1.begin(), v1.begin() + 3);
//清空vector
v1.clear();
//补充:成员函数swap
vector<int> v2(10, 9);
v1.swap(v2);
- 补充:STL为开源项目,查看源代码的方式
<1> 不要进行一行一行的细节查看
<2> 查看整体框架
<3> 成员变量,构造函数,插入操作作为突破口
3. vector的自实现
3.1 自实现功能
- 默认成员函数:构造,拷贝构造,析构,operator=赋值运算符重载
- 遍历访问方式:iterator,operator[],front,back
- 容量相关成员函数:capacity,size,reserve,resize
- 插入删除操作:push_back,pop_back,insert,erase
- 补充:swap
3.2 vector的结构与自实现的逻辑
- 不再单独创建成员变量来记录容量与数据长度,采用三个迭代器的方式来进行信息的维护
- 我们可以通过合适的实现逻辑与复用完成对vector接口的实现
3.3 自实现vector功能接口
1. 成员变量与迭代器
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//迭代器
iterator begin()
{
return _start;
}
iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
iterator end() const
{
return _finish;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
2. 容量相关
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _endofstorage - _start;
}
3. 扩容与插入删除
- reserve
void reserve(size_t n)
{
if (n > capacity())
{
//开辟空间
T* tmp = new T[n];
int old = _finish - _start;
//检测是否需要赋值
if (_start)
{
for (int i = 0; i < old; i++)
{
tmp[i] = _start[i];
}
//释放旧空间
delete[] _start;
}
//直接调用capacity与size()会导致报错
//扩容后指针失效
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
}
- push_back
void push_back(const T& val)
{
if (_finish == _endofstorage)
{
int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_finish = val;
_finish++;
}
- resize
//C++中为内置类型也添加了构造
//double 0.0,char '\0',int 0
void resize(size_t n, T val = T())
{
if(n > size())
{
//扩容
reserve(n);
//填值
while(_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else
{
//缩容
_finish = _start + n;
}
}
- pop_back
void pop_back()
{
assert(size() > 0);
--_finish;
}
4. 默认成员函数
- 无参构造与拷贝构造
//构造
vector()
{}
//拷贝构造
vector(const vector<T>& v)
{
auto it = v.begin();
//范围for
while (it < v.end())
{
push_back(*it);
it++;
}
}
- swap与赋值重载
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;
}
- 迭代器区间构造
<1> 迭代器区间构造,支持使用其他容器初始化
<2> string,list,数组(数组的原生指针是天然的迭代器)
template<class Inputiterator>
vector(Inputiterator it1, Inputiterator it2)
{
//其他数据结构的内存空间可能不连续
while (it1 != it2)
{
push_back(*it1);
it1++;
}
}
- 用指定的n个数进行构造
<1> 当参数为两个int时,迭代器区间构造的调用优先级会比参数类型为size_t, T的构造函数高
<2> 重载如下第二个构造函数就可以避免上述情况的发生
//n个指定值构造
vector(size_t n, T val = T())
{
//直接复用resize
resize(n, val);
}
//两个参数都为int时,保证调用此构造
vector(int n, T val = T())
{
resize(n, val);
}
- 析构函数
~vector()
{
if (_start)
{
delete[] _start;
}
_start = _finish = _endofstorage = nullptr;
}
5. 迭代器失效,深浅拷贝与erase,insert
- insert(向指定迭代器位置插入一个元素)
//在pos位置插入一个数
iterator insert(iterator pos, T val)
{
assert(pos >= _start && pos <= _finish);
int old = pos - _start;
//扩容后迭代器失效
if (_finish == _endofstorage)
{
int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
//避免自定义类型浅拷贝,不能使用memmove等函数
//向后依次挪移,使用存储数据类型的赋值重载
//迭代器插入没有下标插入size_t类型的特殊处理
pos = _start + old;
auto end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end ;
end--;
}
*pos = val;
_finish++;
//防止迭代器失效,可以使用插入与++分离的方式
//不能保证不扩容
return pos;
}
扩容导致的迭代器失效:
- 补充1:使用memove只会进行浅拷贝,当存储数据是自定义类型时,进行赋值时,不会去析构旧空间,会导致空间泄漏。
- erase(删除指定迭代器位置元素)
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
//迭代器失效,memmove浅拷贝
auto it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
- 补充2:迭代器失效
<1> 不同平台对insert与erase的实现方式不同,可能会进行空间的扩容或缩容,这样就会导致原本迭代器的失效。
<2> 删除或插入后,原本迭代器指向元素的相对位置改变,插入删除操作与++操作分离。
- 补充3:string与vector自实现方式
<1> string的存储元素只是字符,所以没有深拷贝,可以直接进行strcpy系列的函数使用
<2> vector的存储数据可能是自定义类型,必须要进行深拷贝
<3> string挪动数据使用的是下标的方式,因为size_t的下标类型所以要考虑头插的特殊情况处理
<4> vector使用迭代器的方式实现数据挪动的操作,避免的string的类似问题
<5>string的成员变量与对数据的维护方式没有使用迭代器
<6> vector使用迭代器维护数据,进行数据操作,也因此导致迭代器失效这一情况的出现
6. operator[]的运算符重载
vector<T>& operator[](size_t pos)
{
assert(pos < _finish - _start);
return _start + pos;
}
const vector<T>& operator[](size_t pos) const
{
assert(pos < _finish - _start);
return _start + pos;
}