✨个人主页: Yohifo
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源
- The power of imagination makes us infinite.
- 想象力的力量使我们无限。
文章目录
vector 是表示可变大小数组的序列 容器,其使用的是一块 连续 的空间,因为是动态增长的数组,所以 vector 在空间不够时会扩容;vector 优点之一是支持 下标的随机访问,缺点也很明显,头插或中部插入效率很低,这和我们之前学过的 顺序表 性质很像,不过在结构设计上,两者是截然不同的

本文介绍的是 vector 部分常用接口
vector 的成员变量如上图所示,就是三个指针,分别指向:
_start 指向空间起始位置,即 begin()_finish 指向最后一个有效元素的下一个位置,相当于 end()_end_of_storage 指向已开辟空间的终止位置
vector 支持三种默认构造方式
0 的对象n 个元素值为 val 的对象string、vector、Date 等
int main()
{
vector<int> v1; //构造元素值为 int 的对象
vector<char> v2(10, 'x'); //构造10个值为'x'的对象
string s = "abcedfg";
vector<char> v3(s.begin(), s.end()); //构造 s 区间内的元素对象
return 0;
}

注:也可以直接通过 vector<int> v4 = {1, 2, 3} 的方式构造对象,不过此时调用了 拷贝构造 函数
vector<int> v4 = {1, 2, 3}; //这种构造方式比较常用,有点像数组赋初始值
拷贝构造:将对象 x 拷贝、构造出新对象 v,拷贝构造 函数的使用方法很简单,利用一个已经存在的 vector 对象,创建出一个值相同的对象

vector<int> x = { 1,2,3,4,5 };
vector<int> v(x); //利用对象 x 构造出 v

可以看到,对象 v 和对象 x 的值是一样的(copy)
注意: 调用拷贝构造时,两个对象类型需匹配,且被复制对象需存在
拷贝构造 和 赋值重载 有 深度拷贝 的讲究,在模拟实现 vector 时演示
析构函数,释放动态开辟的空间,因为 vector 使用的空间是连续的,所以释放时直接通过 delete[] _start 释放即可
析构函数 会在对象生命周期 结束时自动调用,平常在使用时无需关心

// ~vector 函数内部
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
拷贝构造 的目的是创建一个新对象,赋值重载 则是对一个老对象的值进行 改写

int arr[] = { 6,6,8 };
vector<int> v1(arr, arr + (sizeof(arr) / sizeof(arr[0]))); //迭代器区间构造
vector<int> v2; //创建一个空对象
v2 = v1; //将 v1 的值赋给老对象 v2

注意: v1 对象赋值给 v2 对象后,v1 本身并不受任何影响,改变的只是 v2
赋值重载 函数有返回值,适用于多次赋值的情况
vector<int> v3 = { 9,9,9 };
v2 = v1 = v3; //这样也是合法的,最终 v1、v2 都会受到影响
迭代器 是一个天才设计,它的出现使得各种各样的容器都能以同一种方式进行 访问、遍历 数据
vector 支持下标随机访问, 所以大多数情况下访问数据都是使用下标,但 迭代器 相关接口它还是有的

vector 和 string 的 迭代器 本质上就是原生指针,比较简单,但后续容器的 迭代器 就比较复杂了
复杂归复杂,但每种 容器 的迭代器使用方法都差不多,这就是 迭代器 设计的绝妙之处
注:string 和 vector 的迭代器都是 随机迭代器(RandomAccessIterator),可以随意走动,支持全局排序函数 sort

正向迭代器即 从前往后 遍历的 迭代器

利用迭代器正向遍历 vector 对象
const char* ps = "Hello Iterator!";
vector<char> v(ps, ps + strlen(ps)); //迭代器构造
vector<char>::iterator it = v.begin(); //创建该类型的迭代器
while (it != v.end())
{
cout << *it;
it++;
}
cout << endl;

注意:
vector<int>,嫌麻烦可以直接用 auto 推导it != v.end() 不能写成 <,因为对于后续容器来说,它们的空间不是连续的,判断小于无意义vector 是 随机迭代器,也支持这样玩
//auto 根据后面的类型,自动推导迭代器类型
auto it = v.begin() + 3; //这是随机的含义
反向迭代器常用来 反向遍历(从后往前)容器

反向遍历 vector 对象
const char* ps = "Hello ReverseIterator!";
vector<char> v(ps, ps + strlen(ps));
vector<char>::reverse_iterator it = v.rbegin(); //创建该类型的迭代器
while (it != v.rend())
{
cout << *it;
it++;
}
cout << endl;

反向迭代器的注意点与正向迭代器一致,值得注意的是 rbegin() 和 rend()

begin() 和 end() 适用于 正向迭代器
begin() 为对象中的首个有效元素地址end() 为对象中最后一个有效元素的下一个地址rbegin() 和 rend() 适用于 反向迭代器
rbegin() 为对象中最后一个有效元素地址rend() 为对象中首个有效元素的上一个地址注意: begin() 不能和 rend() 混用
上述 迭代器 都是用于正常 可修改 的对象,对于 const 对象,还有 cbegin()、cend() 和 crbegin()、crend(),当然这些都是 C++11 中新增的语法

注:对于
const对象,存在重载版本,如begin() const,也就是说,const修饰的对象也能正常使用begin()、end()、rbegin()和rend();C++11中的这个新语法完全没必要,可以不用,但不能看不懂
下面来看看 vector 容量相关函数和扩容机制
大小 size()
容量 capacity()
判空 empty()
这些函数对于我们太熟悉了,和 顺序表 的一模一样

直接拿来用一用
vector<int> v = { 1,2,3,4,5 };
cout << "size:" << v.size() << endl;
cout << "capacity:" << v.capacity() << endl;
cout << "empty:" << v.empty() << endl;

这几个函数都是直接拿来用的,没什么值得注意的地方
连续空间可扩容,像 string 一样,vector 也有一个提前扩容的函数:reserve()
输入指定容量即可扩容,常用来 提前扩容,避免因频繁扩容而导致的内存碎片
下面来通过一个小程序先来简单看看 PJ 版 和 SGI 版的 默认扩容机制
vector<int> v;
size_t capacity = v.capacity();
cout << "Default capacity:" << capacity << endl;
int i = 0;
while (i < 100)
{
v.push_back(i); //尾插元素 i
//如果不相等,证明出现扩容
if (capacity != v.capacity())
{
capacity = v.capacity();
cout << "New capacity:" << capacity << endl;
}
i++;
}

可以看出,PJ 版采用的是 1.5 倍扩容法,而 SGI 版直接采用 2 倍扩容法,待扩容量较小时,PJ 版会扩容更多次,浪费更多空间;但待扩容量越大时,变成 SGI 版浪费更多空间,总的来说,两种扩容方式各有各的优点
如果我们提前知道待扩容空间大小 n,可以直接使用 reserve(n) 的方式进行 提前扩容,这样一来,无论是哪种版本,最终容量大小都是一致的,且不会造成空间浪费
v.reserve(100); //提前开辟空间

此时是非常节约空间的,而且不会造成很多的内存碎片
注意: 当 n 小于等于 capacity() 时,reserve 函数不会进行操作
与提前扩容相似的大小调整,主要调整的是 _finish
在扩容的同时对新空间进行初始化,参数2 val 为缺省值,缺省为对应对象的默认构造值
int(),构造后为 0匿名构造,后续会经常简单(很方便)
vector<int> v1;
v1.resize(10); //使用缺省值
vector<int> v2;
v2.resize(10, 6); //使用指定值

区别在于:是否指定初始化值
resize 和 reserve:
resize 扩容的同时还能进行初始化,reserve 则不能resize 会改变 _finish,而 reserve 不会resize 此时会初始化 size() 至 capacity() 这段空间vector 中还提供一个了缩容函数,将原有容量缩小,但这完全没必要,以下是缩容步骤:
为了一个缩容而导致的是代价是很大的,因此 不推荐缩容,想要改变 size() 时,可以使用 resize 函数

这里就不演示这个函数了,就连官方文档上都有一个 警告标志
连续空间数据访问时,可以通过 迭代器,也可以通过 下标,这里还是更推荐使用 下标,因为很方便;作为 “顺序表”,当然也支持访问首尾元素
下标访问是通过 operator[] 运算符重载实现的

库中提供了两个重载版本,用以匹配普通对象和 const 对象
const char* ps = "Hello";
vector<char> v(ps, ps + strlen(ps)); //迭代器区间构造
const vector<char> cv(ps, ps + strlen(ps)); //迭代器区间构造
size_t pos = 0; //下标
while (pos < v.size())
{
cout << v[pos]; //普通对象
cout << cv[pos]; //const 对象
pos++;
}
cout << endl;

除了 operator[] 以外,库中还提供了一个 at 函数,实际就是对 operator[] 的封装
v.at(0);
v[0] //两者是完全等价的
注意: 因为是下标随机访问,所以要小心,不要出现 越界 行为
front() 获取首元素,back() 获取尾元素
vector<int> v = { 1,1,1,0,0,0 };
cout << "Front:" << v.front() << endl;
cout << "Back:" << v.back() << endl;

实际上,front() 就是返回 *_start,back() 则是返回 *_finish
vector 也可以随意修改其中的数据,比如尾部操作,也支持任意位置操作,除此之外,还能交换两个对象,亦或是清除对象中的有效元素
push_back() 和 pop_back() 算是老相识了,两个都是直接在 _finish 上进行操作

这两个函数操作都很简单,不再演示
注意: 如果对象为空,是不能尾删数据的
对于已有对象数据的修改,除了赋值重载外,还有一个函数 assign(),可以重写指定对象中的内容,使用方法很像默认构造函数,但其本质又和赋值重载一样

第一种方式是通过迭代器区间赋值,第二种是指定元素数和元素值赋值
任意位置插入删除是使用 vector 的重点,因为这里会涉及一个问题:迭代器失效,这个问题很经典,具体什么原因和如何解决,将在模拟实现 vector 中解答

简单演示一下用法:
int arr[] = { 6,6,6 };
vector<int> v = { 1,0 };
//在指定位置插入一个值
v.insert(find(v.begin(), v.end(), 1), 10); //10,1,0
//在指定位置插入 n 个值
v.insert(find(v.begin(), v.end(), 0), 2, 8); //10,1,8,8,0
//在指定位置插入一段迭代器区间
v.insert(find(v.begin(), v.end(), 8), arr, arr + (sizeof(arr) / sizeof(arr[0]))); //10,1,6,6,6,8,8,0
//删除指定位置的元素
v.erase(find(v.begin(), v.end(), 10)); //1,6,6,6,8,8,0
//删除一段区间
v.erase(v.begin() + 1, v.end()); //1
先浅浅演示一下 迭代器失效的场景
vector<int> v = { 1,2,3 };
auto it = v.end(); //利用迭代器模拟尾插
for (int i = 0; i < 5; i++)
{
v.insert(it, 10);
it++; //再次使用迭代器
i++;
}
运行(调试模式下)结果是这样的:

不止 insert 的迭代器会失效,erase 的迭代器也会失效
简单来说:插入或删除后,可能导致迭代器指向位置失效,此时没有及时更新,再次使用视为非法行为
因此我们认为 vector 在插入或删除后,迭代器失效,不能再使用,尤其是 PJ 版本,对迭代器失效的检查十分严格
至于其具体原因和方法,留在下篇文章中揭晓
还剩下两个简单函数,简单介绍下就行了

vector<int> v1 = { 1,2,3 };
vector<int> v2 = { 4,5,6 };
v1.swap(v2); //交换两个对象
v1.clear();
v2.clear(); //清理
std 中已经提供了全局的 swap 函数,为何还要再提供一个呢?
std::swap,std::swap 实际在交换时,需要调用多次拷贝构造和赋值重载函数,对于深拷贝来说,效率是很低的vector::swap 在交换时,交换是三个成员变量,因为都是指针,所以只需要三次浅拷贝交换,就能完美完成任务vector::swap 内部,还是调用了 std::swap,不过此时是高效的浅拷贝至于 clear 函数,实现很简单:
_finish 等于 _start,就完成了清理,不需要进行缩容,这样做是低效的关于 vector 更多、更详细的内容,欢迎移步 《C++ STL学习之【vector的模拟实现】》
光知道怎么使用是不够的,还需要将知识付诸于实践,切记纸上谈兵
下面是一些比较适合练习使用 vector 的试题,可以做做
以上就是本次关于 STL 之 vector 的全部讲解了,vector 相对来说函数比较少,也比较好理解,不过在实际使用中,会存在不少问题,需要对 vector 的不断使用以提高认知,如果对 vector 剩余函数感兴趣,可以阅读官方文档 vector
如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正

相关文章推荐
STL 之 string 类
C++ STL学习之【string类的模拟实现】
C++ STL 学习之【string】
===============
内存、模板
C++【模板初阶】
C/C++【内存管理】
===============
类和对象实操
类和对象实操之【日期类】
![]()
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po