目录
1.vector的概述
2.vector常用接口
2.1 构造函数
2.2 迭代器的使用:
2.3 修改的接口
push_back
pop_back
insert
erase
find
reverse
2.4 关于容量接口
resize
reverse
2.5 数据的访问
3 vector的迭代器
迭代器失效的问题
4. vector的模拟实现
4.1reserve接口的实现
4.2 resize
4.3 insert
4.4 push_back
4.5 erase
4.6 pop_back
4.7 构造函数
1.vector的概述
vector本质是数据结构中的线性表,它是动态空间的数组,如果vector中的空间满了以后,它会先开辟一块更大的新空间,然后将旧空间的数据拷贝给新空间,再把旧空间的给释放掉。vector开辟的新空间的容量一般是旧空间的容量的两倍或者1.5倍,不同的编译器增容方式可能是不同的。例如再g++中vector是以两倍增长的,vs中vector是以1.5倍增长的。
vector可以储存任意类型的数据,包括自定义类型,当定义vector的时候,需要指明vector存储的数据是什么类型的,定义格式:
vector<类型> 名字;
std::vector<int> v1;//v1是存储int类型的数据
std::vector<string> v2;//v2存储的是string类型的数据
std::vector<char> v3;//v3是存储char类型的数据
2.vector常用接口
vector的接口有很多,在这里我就不一 一讲解,我只讲一些我们经常会用到的接口,如果遇到哪个接口不懂的话,自己可以去查一下文档:vector - C++ Reference
2.1 构造函数
vector | 无参的构造函数 |
vector
(
size_type n, const value_type& val = value_type()
)
| 构造并初始n个val |
vector (const vector& x);
| 拷贝构造 |
vector (InputIterator fifirst, InputIterator last);
| 利用迭代器进行初始化构造 |
std::vector<int> v1;//无参的构造函数
std::vector<int> v2(10, 2);//构造并初始化10个2
std::vector<int> v3(v2.begin(), v2.end());//利用v2的迭代器初始化v3
vector<int> v4(v2);//拷贝构造
2.2 迭代器的使用:
正向迭代器:begin+end 返回的的类型是iterator/const_iterator
反向迭代器:rbegin+rend 返回的类型是reverse_iterator/const_reverse_iterator
利用迭代器遍历vector v1,v1中包含元素有 1 ,2 ,3,4,5
void test(vector<int> v)
{
vector<int>::iterator it = v.begin();
cout << "正向迭代器遍历:";
while (it != v.end())//正向迭代器遍历
{
cout << *it << ' ';
it++;
}
cout << endl;
vector<int>::reverse_iterator rt = v.rbegin();
cout << "反向迭代器遍历:";
while (rt != v.rend())//反向迭代器遍历
{
cout << *rt << ' ';
rt++;
}
cout << endl;
}
输出:
正向迭代器遍历:1 2 3 4 5
反向迭代器遍历:5 4 3 2 1
2.3 修改的接口
push_back | 在末尾插入数据 |
pop_back | 删除末尾的数据 |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
find | 查找的某个数据的位置 |
operator[ ] | 像数组一样去访问vector |
swap | 与另一个vector进行交换 |
reverse | 将vector中的数据进行逆置 |
push_back
void push_back (const value_type& val);
push_back接口的参数只有一个,它可以在vector的末尾插上一个数据。
pop_back
void pop_back();
pop_back接口没有参数,它直接删除vector末尾的一个数据。
insert
insert有3种传参的方式。
方式一:
iterator insert (iterator position, const value_type& val);
在position之前插入一个val。position是迭代器类型
方式二:
void insert (iterator position, size_type n, const value_type& val);
在position之前插入n个val。positon是迭代器类型
方式三:
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
position 和first,last都是迭代器类型。
将一个vector的[first,last)区间的数据插入到另一个相同类型的vector的position位置之前。
pisition不会随着数据的移动而移动。
erase
erase有两种传参的方式:
iterator erase (iterator position);
方式一:删除position位置的数据、
iterator erase (iterator first, iterator last);
方式二:删除迭代器区间 [ first , lasr ) 中的元素。
find
template <class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val);
在[ first , last )区间查找val,找到了就返回相应的迭代器,找不到就返回last。
find不是vector中的中的接口,是算法中的函数,只要有迭代器都可以通过find进行查找。
reverse
template <class BidirectionalIterator>
void reverse (BidirectionalIterator first, BidirectionalIterator last);
传vector的迭代区间,将vector的迭代区间中的元素进行逆置,例如:将一个vector中1 2 3 4 5 进行逆置就成 5 4 3 2 1
std::vector<int> v1;//无参的构造函数
v1.push_back(1);//尾插
v1.push_back(2);//尾插
v1.push_back(3);//尾插
v1.push_back(4);//尾插
v1.push_back(5);//尾插
//查找3的位置,并将返回的迭代器存储在pos
vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
v1.insert(pos, 30);//在pos位置之前插入30
reverse(v1.begin(), v1.end());//逆置vector
//查找3的位置,并将返回的迭代器存储在it
vector<int>::iterator it = find(v1.begin(), v1.end(), 4);
v1.erase(it);//删除it位置的数据
2.4 关于容量接口
size | 获取数据个数 |
capacity | 获取容量的大小 |
empty | 判断vector是否为空 |
reserve | 改变vector的capacity |
resize | 改变vector的size
|
resize
void resize (size_type n, value_type val = value_type());使size到n个,如果不足本身的size不足n,则用val补齐vector到n个数据。如果本身的size大于n,则不需要进行任何操作。
reverse
void reserve (size_type n);
使vector有n个容量空间,如果原本的capacity大于n,则不会缩容。
std::vector<int> v;
cout << v.capacity() << endl;//输出0
cout << v.size() << endl;//输出0
v.reserve(10);//预留10个capacity的空间大小
cout << v.capacity() << endl;//输出10
cout << v.size() << endl;//输出0
v.resize(20,10);//预留20个数据
cout << v.capacity() << endl;// 输出20
cout << v.size() << endl;//输出20
2.5 数据的访问
operator[ ] | 可以像数组下标去访问vector中的元素 |
front | 返回vector中的第一个数据 |
back | 返沪vector中最后一个数据 |
3 vector的迭代器
vector是一个连续的空间,它的迭代器就是原生指针,因为vector中的原生指针可以满足迭代器 operator*,operator[ ],operator++,operator--,operator+=,operator-=的操作,所以vector的迭代器就只需要给原生指针起个别名就够了。如下:
template<class T>
class vector
{
public:
typedef T* iterator; //vector的迭代器就是原生指针
typedef const T* const_iterator;...
}
迭代器失效的问题
在我们用vector中,经常会碰到迭代器失效的问题,那么什么是迭代器失效呢?
我们先来看一段代码,如下:
void test2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(10);
v.push_back(4);
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
v.insert(pos,10);
v.insert(pos, 20);
}
这段代码在vs运行下会奔溃掉,为什么呢?
当我们删除掉2后,pos指向的位置的值就变为10,那么我们pos的意义也就改变了,此时的pos就迭代器就已经失效了,当我们再去使用它的时候,vs编译器就检查出来。
那么我再来看一下下面这个场景:
上面pos已经是野指针,因为旧空间被释放掉所以不知道它指向的是什么值。所以该指针旧已经失效了。
所以迭代器失效有两种方式:
一种是使用insert,erase使迭代器指向的数值发生改变,此时的迭代器就失去意义。这种有
些编译器会检查例如vs,有些编译器不会检查,例如g++
另一种是插入数据的时候,容量满的时候会开辟新空间,旧空间被释放,导致pos指向的数据
不知道是什么,此时的pos成了野指针,这个任何编译器都会报错。
4. vector的模拟实现
namespace sjp
{
template<class T>
class vector
{
public:
typedef T* iterator; //vector的迭代器是可以修改的
typedef const T* const_iterator;
iterator begin()//返回vector迭代区间的头
{
return _first;
}
iterator end()//返回vector的迭代区间的尾
{
return _end;
}
const_iterator begin()const
{
return _first;
}
const_iterator end()const
{
return _end;
}
size_t size()const//返回vector的数据个数
{
return _end - _first;
}
size_t capacity()const//返回vector的容量
{
return _end_of_storage - _first;
}
bool empty()const//判断vector是否为空
{
return _first == _end;
}
const T& operator[](size_t i)const
{
return *(_first + i);
}
T& operator[](size_t i)
{
return *(_first + i);
}
void clear()//清空vector
{
_end = _first;
}
const T& back()const//返回vector最后一个元素
{
return *(back - 1);
}
const T& front()const//返回vector第一个元素
{
return *(front);
}
private:
iterator _first;//表示目前使用空间的头
iterator _end;//表示目前使用空间的尾
iterator _end_of_storage;//表示目前可用空间的尾
};
在上面接口中,我们发现
1.有些接口在后面需要加上const,例如 empty ,size, capacity这些接口,因为调用这些接口的时候不会修改它的成员变量,所以我们加上const可以防止它的成员变量被修改,并且可以让const对象和非const对象都可以调用这些接口(注意非const对象是可以调用const的接口,但const对象不能调用非const的接口)
2.有些接口需要实现const和非const的版本,例如begin ,end,operator[]这些接口,
非const对象调用非const的接口,返回的对象是可读可写的,const对象调用的是const的接口
返回的对象是只能读。例如,const对象返回的也是const的迭代器,就是不能通过迭代器去修改它指向的值。
4.1reserve接口的实现
void reserve(size_t n)
{
size_t sz = size();//先保存原本的size
if (capacity() < n)
{
T* tmp = new T[n];//重新开一块空间
if (_first)//如果_first不为空指针
{
for (int i = 0; i < size(); i++)//将原空间的值拷贝给新空间
{
tmp[i] = _first[i];
}
delete[] _first;//释放旧空间
}
//让迭代器指向新空间
_first = tmp;
_end = _first + sz;
_end_of_storage = _first + n;
}
}
这里有一个问题,上面的原空间的值拷贝给新空间中的:
for (int i = 0; i < size(); i++)
{
tmp[i] = _first[i];
}
可不可用memcpy(tmp, _first, sz*sizeof(T));来替代
memcpy进行的浅拷贝,当vector存储的像string开辟动态空间的之类就会出错
也就是说memcpy只能拷贝string中的指针,没有将拷贝string的存储字符串的空间,当旧空间的中的存储的指针被释放掉后,同时指针指向的子符串也会被释放掉。
那这段代码为什么可以呢?
for (int i = 0; i < size(); i++)
{
tmp[i] = _first[i];
}
4.2 resize
void resize(size_t n,T val=T())
{
if (n > size())
{
if (n > capacity())//如果预留数据个数大于容量则需要增容
{
reserve(n);
}
size_t sz = n - size();//将数据补齐到n个
for (int i = 0; i < sz; i++)
{
push_back(val);
}
}
}
如果n大于size的时候就插入val,再进行判断,如果数据大于capacity的时候就需要进行增容,最后将val值补齐使vector的数据个数为n个。
4.3 insert
void insert(iterator pos,const T& x)
{
assert(pos >= _first && pos <= _end);
if (_end == _end_of_storage)//判断容量是否满了
{
size_t lenth = pos - _first;//如果换新空间,需要保留pos在vector的位置
int newcapacity = capacity() == 0 ? 5 : capacity() * 2;
reserve(newcapacity);//预留新空间
pos =_first+lenth;//跟新pos
}
//将x插入到pos位置的前面
iterator cur = end();
while (cur != pos)
{
*(cur) = *(cur-1);
cur--;
}
*pos = x;
_end += 1;//数据个数再加一
}
上面提到了insert迭代器会失效的问题,那么我们可不可以对insert适当修改一下使我们使用insert
不会发生迭代器失效的问题?
只需要在上面的代码的基础,在pos迭代器加上引用&,然后插入数据后,pos加1,这样插入数据后,pos就可以一直指向原本那个数据。
4.4 push_back
void push_back(const T& x)//尾插,直接在最后一个位置插入数据即可
{
insert(_end, x);
//if (_end == _end_of_storage)
//{
// int newcapacity = capacity() == 0 ? 5 : capacity() * 2;
// reserve(newcapacity);
//}
//*_end = x;
//++_end;
}
4.5 erase
iterator erase(iterator pos)
{
assert(pos < _end);
iterator cur = pos;//将pos位置后的所有数据向前挪一步
while (cur != _end - 1)
{
*cur = *(cur + 1);
cur++;
}
_end--;//数据个数再减一
return pos;
}
4.6 pop_back
void pop_back()
{
--_end;
}
4.7 构造函数
vector(size_t n, T val)//构造n个val值的vector
{
reserve(n);//先预留n个数据的空间
for (int i = 0; i < n; i++)//再将val存满这块空间
{
push_back(val);
}
}
vector()//无参的拷贝构造
:_first(nullptr),_end(nullptr),_end_of_storage(nullptr)
{
}
vector(iterator first, iterator end)//利用迭代器区间构造vector
:_first(nullptr), _end(nullptr), _end_of_storage(nullptr)
{
while (first != end)
{
push_back(*first);
first++;
}
}
//v2(v1);
vector(const vector<T>& v)//拷贝构造函数
:_first(nullptr),_end(nullptr),_end_of_storage(nullptr)
{
reserve(v.capacity());//预留v1相同的空间
for ( auto e: v)//然后将v1中的数据一个一个的推进v2
{
push_back(e);
}
}
//v2=v1
vector<T>& operator=(const vector<T>& v)//赋值构造函数
{
vector<T> tmp(v);
swap(_first, tmp._first);//不能用用=赋值进行赋值,tmp出函数作业域或被析构掉。
swap(_end, tmp._end);
swap(_end_of_storage, tmp._end_of_storage);
return *this;
}
v1=v2,将v1赋值给v2,需要将v1的空间大小和值跟v2是一模一样的,在赋值过程中,先拷贝构造一个跟v1一模一样的tmp,然后将tmp和v2中_first,_end, _end_of_storage交换过去,出函数作用域的时候,tmp会被析构函数给释放掉。此时v1的空间大小和值跟v2是一模一样的。
总结:在上面中的push_back,pop_back,insert,erase,clear等接口中在后面不需要加上const,因为const对象本身不能被修改的,const的vector中的数据个数和数据都不能被改变,所以说const对象不能调用这些接口。
好了,今天的知识就分享到这里,喜欢的小伙伴们可以给博主点个赞 ,或者点个关注,大家一起努力学习,共同进步。