引言:
vector与string区别:
string:动态类型的顺序表--->只能存储char
vector:动态类型的顺序表--->任意类型的元素都可以存储
vector的使用
1.vector构造和遍历
#include<iostream>
using namespace std;
#include<vector>
void TestVector1()
{
vector<int> v1; //1.构造一个空的
vector<int> v2(10, 5); //2.用10个值为5的元素构造vector
int array[] = { 1, 2, 3, 4, 5 };
vector<int> v3(array, array + sizeof(array) / sizeof(array[0])); //3.
string s("hello");
vector<char>v4(s.begin(), s.end()); //4.
vector<int>v5(v3); //5.拷贝构造
//在c++11中,vector还增加了列表方式的构造
vector<int>v6{ 1, 2, 3, 4, 5, 6 };
//vector的遍历
//方式1:for下表 ---推荐
for(size_t i = 0; i < v2.size(); i++)
{
cout << v2[i] << " ";
}
cout << endl;
//方式二:范围for循环
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
//方式三:采用迭代器
vector<char>::iterator it = v4.begin(); //可以用 auto it =v4.begin(); 替换
while (it != v4.end())
{
cout << *it << " ";
}
cout << endl;
//反向迭代器
vector<int>::reverse_iterator rit = v5.rbegin(); //auto rit = v5.rbegin();
while (rit != v4.rend())
{
cout << *rit << " ";
}
cout << endl;
}
2.resize
void TestVector2() { vector<int> v{ 1, 2, 3, 4, 5, 6 }; v.reserve(20); cout << v.size() << endl; cout << v.capacity() << endl; v.resize(10, 7); //元素新增到10个多出用7填充 ---->无需扩容 v.resize(30, 8); //元素新增到30个多出用8填充 -----》需要扩容 v.resize(50); //元素个数缩小 v.resize(40); v.resize(30); v.resize(20); v.resize(10); }
假设vector中旧的元素个数为oldsize,当前容量为capacity
当元素增多时:
1.多出的元素需要使用val进行填充
2.当newsize>capacity时,resize需要进行扩容
3.当元素缩减时:仅仅只是将有效元素个数缩减了,底层空间大小不会发生改变
3.reserve
void TestVector3() { vector<int> v{ 1, 2, 3, 4, 5 }; v.reserve(10); v.reserve(20); v.reserve(50); v.reserve(70); v.reserve(45); v.reserve(23); v.reserve(16); v.reserve(10); v.reserve(5); }
当空间reserve增大时空间会随之扩增,但是当减小时底层空间不会因此减小
4.元素访问
void TestVector4() { vector<int> v{ 1, 2, 3, 4, 5 }; cout << v[0] << endl; //cout << v[5] << endl; //越界了 //cout << v.at(5) << endl; //at和[]一样也越界了,会抛出一个out_of_range异常 cout << v.front() << endl; cout << v.back()<< endl; }
5. 插入
void TestVector5() { vector<int> v; size_t cap = v.capacity(); for (size_t i = 0; i < 100; ++i) { v.push_back(i); if (cap != v.capacity()) { cap = v.capacity(); cout << cap << endl; } } }
1.push_back尾插
2.演示:vector的扩容方式:vs中是按照1.5倍方式扩容 linux中按照2倍
3.一般情况下尽量避免:一边插入一边阔扩容 因为效率太低了---->a.申请新空间 b.拷贝元素 c.释放旧空间
推荐:如果能够预估vector中大概要放多少个元素可以提前将底层空间大小设置好void TestVector6() { vector<int> v; v.reserve(10); for (size_t i = 0; i < 100; ++i) { v.push_back(i); } //时间复杂度:o(N) v.insert(v.begin(), 100);// begin及其后续所有元素都需要向后搬移 //在第一个5的位置插入10个8 auto pos = find(v.begin(), v.end(), 5); if (pos != v.end()) { v.insert(pos, 10, 8); } //插入一段区间 int array[] = { 100, 200, 300, 400, 500 }; //将数组中的元素插入到vector的末尾 v.insert(v.end(), array, array + sizeof(array) / sizeof(array[0])); }
6.删除
void TestVector7() { vector<int>v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; v.erase(v.begin()); v.erase(v.begin(), v.begin() + 5); //删除1-6 v.clear(); //v.erase(v.begin(),v.end()); }
vector迭代器失效的问题
迭代器的本质:迭代器实际上就是指针 ||对指针进行了封装
比如:对于vector来说,它的迭代器实际就是typedef T*iterator;
什么是迭代器失效:
由于迭代器本质是指针,所谓的迭代器失效指的就是迭代器对应的指针失效。
指针失效:无非就是指针指向的空间不存在或者指针成为了一个野指针则该指针才会失效。
vector什么情况下会迭代器失效?
对于vector来说:
1.所有可能会导致扩容的操作都可能会导致迭代器失效
所有插入的方法:push_back(data) insert(v.begin(),data)
resize(newsize,data)
reserve(newcapacity)
assign
示例:
//迭代器失效 void TestVector8() { vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; auto it = v.begin(); //v.insert(v.begin(), 10, 5); //在起始位置插入10个值为5的元素 //这个过程需要扩容导致原来的空间被释放了 迭代器指向原来的空间被释放了 导致迭代器失效了 while (it != v.end()) { cout << *it << " "; it++; } cout << endl; }
迭代器失效后果:
如果迭代器已经失效了,继续对迭代器使用造成的后果就是程序崩溃了
预防:
在所有可能导致迭代器失效的操作之后,给迭代器从新进行赋值就可以了。
2.第二类导致迭代器失效的原因
//2.erase(pos) :删除之后pos继续后续的元素都失效了 void TestVector9() { vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; auto it = find(v.begin(), v.end(), 5); if (it != v.end()) { //erase的返回值:返回删除位置下一个位置的元素 v.erase(it); } cout << *it << endl; }
预防:在所有可能导致迭代器失效的操作之后,给迭代器从新进行赋值就可以了。
vector构造二维数组
void TestVector()
{
//需要构造5行6列的二维数组
vector<vector<int>> vv;
vv.resize(5);
for (size_t i = 0; i < vv.size(); ++i)
{
vv[i].resize(6, 8); //每行有6个元素每个元素都为8
}
vector<vector<int>> vv2;
vv2.resize(5, vector<int>(6, 8));
vector<vector<int>>vv3(5, vector<int>(6, 8));
}
二维vector构造原理:
每行元素个数不同
比如:杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
void TestVector2(int n)
{
vector<vector<int>>vv(n);
for (size_t i = 0; i < vv.size(); ++i)
{
vv[i].resize(i + 1, 1);
//修改每行的元素:非第0列 以及 对角线上的元素
for (size_t j = 1; j < i; ++j)
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
vector的模拟实现
vector的示意图:
代码实现:
#include<iostream>
using namespace std;
#include<vector>
#include<assert.h>
namespace zilong
{
template<class T>
class vector
{
public:
typedef T* iterator;
public:
//1.
vector()
:start(nullptr)
, finish(nullptr)
, endofstorage(nullptr)
{}
//2.
vector(size_t n, const T& val)
{
start = new T[n];
for (int i = 0; i < n; i++)
{
start[i] = val;
}
finish = start + n;
endofstorage = finish;
}
//用一段区间的元素构造
template<class Iterator>
vector(Iterator first, Iterator last)
{
auto it = first;
//统计【first last】区间中元素的个数
size_t n = 0;
while (it != last)
{
++it;
++n;
}
start = new T[n];
finish = starat;
while (first != last)
{
*finish = *first;
++first;
++finish;
}
endifstorage = finish;
}
//拷贝构造
vector(const vector<T>& v)
{
size_t n = v.size();
strat = new T[n];
for (size_t i = 0; i < n; i++)
{
start[i] = v[i];
}
finish = start + n;
endofstorage = finish;
}
vector<T>& operator=(vector<T> v)
{
this->swap(v);
return *this;
}
~vector()
{
if (start)
{
delete[] start;
start = finish = endofstorage = nullptr;
}
}
//
//迭代器
iterator begin()
{
return starat;
}
iterator end()
{
return finish;
}
/
//容量
size_t size()const
{
return finish - start;
}
size_t capacity()const
{
return endofstorage - start;
}
bool empty()const
{
return start == finish;
}
void resize(size_t newsize, const T& val = T())
{
size_t oldsize = size();
//将有效元素个数缩减到newsize
if (newsize <= oldsize)
{
finish = start + newsize;
}
else
{
//将有效元素个数增加到newsize
size_t cap = capacity();
if (newsize > cap)
reserve(newsize);
//多出来的元素使用val填充
for (size_t i = oldsize; i < newsize; ++i)
{
start[i] = val;
}
finish = start + newsize;
}
}
void reserve(size_t newcapacity)
{
size_t oldCap = capacity();
if (newcapacity > oldCap)
{
//开辟新空间
T* temp = new T[newcapacity];
if (start)
{
//拷贝元素:memcpy是浅拷贝
//如果vector中放置的元素中涉及到资源管理时候,采用memcpy的浅拷贝是会出问题
//memcpy(temp, start, sizeof(T)*size());
for (size_t i = 0; i < size(); ++i)
{
temp[i] = start[i];
}
//释放旧空间
//free(start);
delete[] start;
}
}
size_t sz = size();
start = temp;
finish = start + sz;
endofstorage = start + newcapacity;
}
///
//元素访问
T& front()
{
return *start;
}
const T& front()const
{
return *start;
}
T& back()
{
return *(finish-1);
}
const T& back()const
{
return *(finish - 1);
}
//下标运算符的重载
T& operator[](size_t index)
{
assert(index < size());
return start[index];
}
const T& operator[](size_t index)const
{
assert(index < size());
return start[index];
}
//at
T& at(size_t index)
{
if (index >= size())
{
throw out_of_range("vector at method:out_of_range");
}
return start[index];
}
const T& at(size_t index)const
{
if (index >= size())
{
throw out_of_range("vector at method : out_of_range");
}
return start[index];
}
/
//修改
void push_back(const T& val)
{
//先检测空间是否足够
if (finish == endofstorage)
reserve(capacity()*1.5 + 3);
*finish = val;
++finish;
}
void pop_back()
{
if (empty())
{
return;
}
--finish;
}
//任意位置插入删除
//时间复杂度:O(N)
iterator insert(iterator pos, const T& val)
{
//检测pos是否合法:pos必须在[begin(),end()]
if (pos<begin() || pos>end())
return end();
if (finish == endofstorage)
reserve(capacity() * 2);
auto it = finish - 1; //最后一个元素位置
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
*pos = val;
finish++;
return pos; //返回刚刚插入元素所在的位置
}
iterator erase(iterator pos)
{
if (empty())
return end();
if (pos < begin() || pos >= end())
return end();
auto it = pos + 1;
while (it != end())
{
*(it - 1) = *it;
it++;
}
finish--;
return pos;
}
//删除一段区间的元素
iterator erase(iterator first, iterator last)
{
auto it = first;
auto pos = last;
size_t n = last - first;
while (pos != end())
{
*it = *pos;
++it;
++pos;
}
finish -= n;
return first;
}
private:
iterator start;
iterator finish;
iterator endofstorage;
};
}
当存在自定义类型时,就会存在的一些问题:
1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。
2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且 自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是 浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。