扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
🏐1.什么是vector👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍
学习了string之后,学习其他模板将会更容易上手,一起来看一下vector吧!
vector是表示大小可以变化的数组的序列容器。
作为类模板来说,vector只能显式实例化
void test_vector1()
{vectorv1;
vectorv2(10, 1);
vectorv3(v2);//类模板必须显示实例化,要说明类型为int
}
🏀2.2访问遍历vector访问遍历有大概三种方式
⚽2.2.1下标+[]相比于string来说,[]返回的不仅仅是char类型,返回的是reference,也就是pos位置数据的引用
void test_vector2()
{vectorv1;
v1.push_back(0);
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
// 下标+[]
for (size_t i = 0; i< v1.size(); ++i)
{ v1[i]++;
}
for (size_t i = 0; i< v1.size(); ++i)//相比于string来说,[]返回的不只是char,返回的是pos位置数据元素的引用
{ cout<< v1[i]<< " ";
}
cout<< endl;
}
⚽2.2.2迭代器void test_vector2()
{vectorv1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
// 迭代器
vector::iterator it = v1.begin();
while (it != v1.end())
{ (*it)--;
cout<< *it<< " ";
++it;
}
cout<< endl;
}
⚽2.2.3范围forvoid test_vector2()
{vectorv1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
for (auto e : v1)
{ cout<< e<< " ";
}
cout<< endl;
}
范围for的底层也是迭代器,可以迭代器就可以用范围for
🏀2.3.vector容量问题 ⚽2.3.1size和capacity通过size函数获取当前容器中的有效元素个数,通过capacity函数获取当前容器的大容量。
#include#includeusing namespace std;
int main()
{vectorv(10, 2);
cout<< v.size()<< endl; //获取当前容器中的有效元素个数
cout<< v.capacity()<< endl; //获取当前容器的大容量
return 0;
}
⚽2.3.2reserve和resize通过reserse函数改变容器的大容量,resize函数改变容器中的有效元素个数。
reserve规则:
1、当所给值大于容器当前的capacity时,将capacity扩大到该值。
2、当所给值小于容器当前的capacity时,什么也不做。
resize规则:
1、当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。(也就是将扩容的空间进行了初始化
2、当所给值小于容器当前的size时,将size缩小到该值。
void TestVectorExpand()
{size_t sz;
vectorv;
//v.resize(100);//resize是不止开辟了空间,还插入了100个数据,这地方不能用
//v.reserve(100);//空间提前开好,提高效率
sz = v.capacity();
cout<< "making v grow:\n";
for (int i = 0; i< 100; ++i)
{ v.push_back(i);
if (sz != v.capacity())
{ sz = v.capacity();
cout<< "capacity changed: "<< sz<< '\n';
}
}
}
如果在某一特定值位置进行插入或删除,需要用到find函数。不然直接指定位置插入
void test_vector4()
{vectorv1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector::iterator pos = find(v1.begin(), v1.end(), 3);//嫌长也可以用auto
if (pos != v1.end())//为什么说!=而不是<,因为不一定像string那样是连续的存储空间,比如树
{ v1.insert(pos, 567);
}
}
注意:insert如果插入位置>capacity,那么将会在最后一个位置的下一个位置进行插入,并不会报错(不要有侥幸心理),erase就不一样了,如果删除位置>capacity就会报错。
⚽3.4.2erasevoid test_vector4()
{vectorv1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector::iterator pos = find(v1.begin(), v1.end(), 3);//嫌长也可以用auto
pos = find(v1.begin(), v1.end(), 300);//进行erase的时候会报错,没有300这个数的位置(虽然这个时候返回的end位置,但是仍不在capacity内,end表示的是最后一个位置的下一个位置
if (pos != v1.end())
{ v1.erase(pos);
}
}
⚽3.4.3findvector不提供find,像vector,list等容器,find是通用的,都是在一段迭代器区间去找,所以直接写在了algorithm中,就是个函数模板
void test_vector4()
{vectorv1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector::iterator pos = find(v1.begin(), v1.end(), 3);//嫌长也可以用auto
if (pos != v1.end())
{v1.insert(pos, 567); //在3的位置插入567
}
pos = find(v.begin(), v.end(), 3); //获取值为3的元素的迭代器
if (pos != v1.end())
{ v1.erase(pos); //删除3
}
}
注意:
以前我们常用qsort函数来实现排序,这次我们看一下
🏀push_back
我们来充分理解一下这里的参数**(为什么要有const和&)**,一般来说我们插入数据我们有这么几种方式:
vectorstr;
第一种
string str1;
str1="小彭";
str.push_back(str1);
第二种
str.push_back(string("老彭");
第三种
str.push_back("彭彭");
引用&是有必要的,第一种如果没有引用,那就会进行拷贝构造,开辟空间是个深拷贝,过程代价大,所以避免多次开空间,加&。
我们可以看到const的使用也是有必要的,比如第二种我们使用了匿名对象,像匿名对象,临时对象这种不能改变的量我们都需要加上const,为了避免出现权限放大的问题。
为什么支持第三种写法呢,也是因为有const,我们看到string的构造函数string(const charstr){}*,这里面有隐式类型的转换,会产生临时变量。(这里涉及到右值引用)
如下
double d=1.1;
int i=d;//这样赋值完全没问题,隐式类型转换
看下面
double d=1.1;
int& i=d;//这样就是错误的,引用的不是d,而是中间的临时变量,而临时变量具有常性,但我们加个const就没有问题了,
再看下面
double d=1.1;
const int& i=d;
看完这里,你估计就明白了
关于范围for加const &也是一样,假如我们要把上面插入的数据输出
for(auto e:str)
{cout<
在这个过程中,底层会发生的是迭代器将每个数据拷贝给范围变量e,又是拷贝!!!所以我们直接写成下面这种
for(const auto& e:str)
{cout<
🏐vector的实现(含迭代器失效问题)我们尝试写一下vector的一些接口
🏀insert这个地方跟string相比,不是用下标,不用考虑无符号有符号的边界问题(用指针的优势
void insert(iterator pos, const T& x)
{ assert(pos >= _start);
assert(pos<= _finish);
if (_finish == _end_of_storage)
{ size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
// 挪动数据
iterator end = _finish - 1;
while (end >= pos)
{ *(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
当我们用自己实现的接口进行操作时我们发现会出现迭代器失效的问题,(迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。) 经检查发现是扩容的问题,因为扩容会导致开辟新的空间,把数据拷贝过去,那么就自然的出现了原来指针指向的空间没有数据的现象,也就是迭代器失效。
为解决迭代器失效问题,在上面接口中是这么解决的,我们要做的就是更新pos位置,那么我们可以a.算好pos的相对位置计算出len长度,更新,当然还可以b.重新find一遍数据。 推荐b方法,因为用第一种,还会出现插入数据后不能访问的问题,形参的改变不能改变实参,函数接口里pos位置是改变了,但是函数外并没有改变。不如用find在函数外找一遍,继续对数据进行相关操作。
要点:更新迭代器位置
stl 规定erase返回删除位置下一个位置迭代器
iterator erase(iterator pos)
{ assert(pos >= _start);
assert(pos< _finish);
iterator begin = pos + 1;
while (begin< _finish)
{ *(begin - 1) = *begin;
++begin;
}
--_finish;
//if (size()< capacity()/2)
//{ // // 缩容 -- 以时间换空间
//}
return pos;
}
可以看到接口的实现决定了是否会出现迭代器失效的问题,c++和stl并没有规定怎么去实现这些接口,只是一个规范,所以看具体实现。
我们就认为用insert和erase后不要再直接访问pos位置的数据了
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流