一、构造函数
#include<iostream>
#include<vector>
using namespace std;
int main()
{
//无参构造
vector<int> v1;
//n个相同对象构造(10个5)
vector<int> v2(10, 5);
//迭代器区间构造(利用v2的迭代器区间)
vector<int> v3(v2.begin(), v2.end());
//拷贝构造
vector<int> v4(v2);
return 0;
}
迭代器可以互用:
利用数组构造:
二、排序算法(这个不是vector的内容,所有容器都可以使用)
sort的排序区间是左内闭右开的
排序要传迭代空间,第三个参数是greater或less类型的模板
没有第三个参数,默认是升序
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>//排序算法的头文件
using namespace std;
int main()
{
int a[10] = { 2,7,3,4,1,6,8,2,9,5 };
vector<int> v1(a, a + 10);
for (auto& e : v1)
{
cout << e << " ";
}
//升序
sort(v1.begin(), v1.end());
//gless<int> le;<
//sort(v1.begin(), v1.end(),le);
for (auto& e : v1)
{
cout << e << " ";
}
//降序
greater<int> gt;//>
sort(v1.begin(), v1.end(), gt);
//上面两行可以用匿名对象浓缩成一行
//sort(v1.begin(), v1.end(), greater<int>);
for (auto& e : v1)
{
cout << e << " ";
}
return 0;
}
三、迭代器
正向,反向,const,非const
升序排序用反向迭代器就是降序
四、容量部分
大致与string一样,下面有个易错点
这个写法会报错:[ ]会对vector进行越界断言,而这个断言是按照size的大小进行的,当i超过size时,便会报越界的错。使用reserve后,虽然capacity是10,但是size却是初始化时的0,使用进行遍历填值时,越界就报错了。
如果想这样访问的话,就应该使用resize,直接把size设置好(开辟了但没有数据的位置默认是0)
如果你硬要用reserve,就用push_back进行插入
要记住:reserve仅仅只是开空间,resize是开空间并初始化
五、访问部分
注意[ ]与at都会进行越界检查(以0,size为范围),[ ]是断言,at是抛异常
看一看data
与c_str很像,返回vector数组的指针
六、修改部分
提供尾插尾删,头插与头删用insert与eares实现(insert是在pos之前插入,eares删除pos及之后位置) 越界删除会报错
接下来是一个易错点:如何删除一个vector对象中的多个相同元素?
vector中没有find这个函数,但是在算法头文件中有
这是一个模板,可以给各个容器使用。find的区间是左闭右开的,如果没有找到,返回last(最后一个数据的下一个数据的地址所在处),找到了,就返回找到的地址。返回的是一个指针,需要接收
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
int a[10] = { 1,3,2,3,5,8,3,4,3,0 };
vector<int> v1(a,a+10);
vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
while (pos != v1.end())
{
v1.erase(pos);
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
这样写似乎是对的,但实际上会报错,原因是迭代器失效(这个后面细讲)
每次从头找的话就没问题,但是效率很低
clear与swap与之前一样,assign是对之前的值清了之后重新赋值(用的很少)
下面是vector的扩容逻辑:
vs下差不多是1.5倍扩容,linux下是2倍扩容
七、vector的vector(用于实现二维数组)
八、vector的模拟实现
#include<iostream>
#include<assert.h>
using namespace std;
namespace djl
{
//声明模板类型T
template<class T>
class vector
{
public:
//重命名
typedef T* iterator;
typedef const T* const_iterator;
//无参构造函数
vector()
:_start(nullptr)
,_finish(nullptr)
,_endoftorage(nullptr)
{
}
//n个相同对象的构造函数
vector(size_t n, const T& val = T())
{
resize(n, val);
}
//拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endoftorage(nullptr)
{
_start = new T[v.capacity()];
//这个写法有局限性,只能进行浅拷贝
//memcpy(_start, v._start, sizeof(T) * v.size());
//利用赋值运算符重载进行数据深拷贝
for (int i = 0; i < v.size();i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_endoftorage = _start + v.capacity();
}
//另一种写法
/*vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endoftorage(nullptr)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
*/
//析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endoftorage = nullptr;
}
}
//capacity
size_t capacity()
{
return _endoftorage - _start;
}
size_t capacity()const
{
return _endoftorage - _start;
}
//size
size_t size()
{
return _finish - _start;
}
size_t size()const
{
return _finish - _start;
}
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * size());
//利用赋值运算符重载进行数据深拷贝
for (int i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
//下面这个顺序写法是错误的,size()是通过_finish-_start计算的,
// 但这里改变了原来的_start,导致size()计算出错,
// 从而使finish计算错误
/*_start = tmp;
_finish = tmp + size();*/
//下面这个顺序才是正确的(先算要用到size()的_finish,再计算_start)
_finish = tmp + size();
_start = tmp;
_endoftorage = _start + n;
}
}
//尾插
void push_back(const T& x)
{
if (_finish == _endoftorage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
//迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//[ ]运算符重载
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
//插入
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endoftorage)
{
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;
}
//删除
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos;
while (it != _finish)
{
*(it) = *(it + 1);
it++;
}
_finish--;
return pos;
}
//尾删
void pop_back()
{
erase(--end());
}
//resize
//开空间并初始化
//T()代表的是T这个类型的默认构造函数
//在C++中,内置类型也有默认构造函数
//这里的参数,因为不知道T的类型,采用匿名对象的参数
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++;
}
}
}
//这个函数配合赋值运算符重载函数使用
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endoftorage, v._endoftorage);
}
//赋值运算符重载
//采用传值形参,避免浅拷贝
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endoftorage;
};
}
九、insert迭代器失效问题
先看一下,下面这个insert函数
看上去似乎没有什么错误,但会发生偶然性数据插入错误,一但发生扩容,就会出问题,这是因为迭代器失效
扩容的逻辑是删除旧空间,开辟新空间并拷贝,这会导致原来的pos不对(start与finish都指向了新的空间,但pos指向的是旧的空间,是个野指针)
解决方法,保存原来pos与star的距离,当扩容后,更新pos
扩容引发野指针的问题就是迭代器失效的一种
内部的迭代器失效解决了,但外部的没有解决
在外部对迭代器进行修改时,有时也会发生错误,因为insert是传值传参,函数内部pos的改变不会影响外部
这里的逻辑是插入300时发生扩容,内部pos会被新pos取代,但是因为是传值传参,外部的pos不发生改变(指向的是内部函数扩容前的空间),然后在外部对p进行+=10的操作实际上是对旧空间中的pos的位置进行操作的,这样的操作肯定是错误的
解决方法:因为各个平台的insert机制是不一样的,所以我们一般遵循以下规则
库里面的insert带了一个迭代器的返回值,来避免这个问题
自己在自己写的insert中返回pos并且改一下返回值类型就好
十、erase迭代器失效问题
看这个删除函数
这个写法是没有问题的,但是会发生迭代器失效
迭代器失效的原因是扩容,erase删除没有问题,但是再次使用这个迭代器就发生问题了
所以也说eras迭代器失效
vs和g++对这个问题有不同的解决方法
vs下:
erase以后,迭代器失效了,不能访问,vs进行强制检查,访问会直接报错
g++下:
一种躺平的状态,一般不报错,但是也会发生迭代器失效
为了保持在不同的平台下代码的可移植性,库中作出以下处理:返回一个位置,这个位置是刚刚删除的那个位置的下一个位置
十一、 T为自定义类型的问题
这个是拷贝构造函数
进行下面这个用例时,一切正常
发生隐式类型转换(单个参数的函数支持隐式类型转换)
但是,四个的时候好好的,五个的时候就崩溃了(初步考虑是扩容的问题,因为第五个节点是扩容的节点)
这本质上是隐藏的深拷贝问题
当需要扩容的时候,便会调用reserve函数,开辟新的空间,把原空间的内容拷贝到tmp中,在修改_start等指针。再对对象进行拷贝时,由于vector的每一个元素是自定义类型,就会导致拷贝的时候要调用string的拷贝构造函数,而这个函数在这个类中是没有的,只好调用默认拷贝构造函数进行浅拷贝,这就导致新空间和原空间中的对象指向同一块空间
最主要的是,下一步会调用delete对原空间进行释放,delete对指定类型的空间(vector中的string类对象)先调用析构函数,再释放空间 (vector的空间),这就使得原空间中每个string对象的地址被释放,而tmp拷贝了一个寂寞(拷贝的是已经被释放的野空间)
综上:当T是一个需要深拷贝的自定义类型的对象时,扩容就会有问题
那么,应该如何解决呢?
这个是reserve函数内部,把memcpy这个函数换一下,这里调用的是string的=重载(及T这个类型的赋值运算符重载)
就会变成上面这样的拷贝
还有就是拷贝构造函数的memcpy也需要换一下