C++中的vector容器对象学习笔记
C++中数组很坑,有没有类似Python中list的数据类型呢?类似的就是vector!vector是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。和string对象一样,标准库将负责管理与存储元素相关的内存。我们把vector称为容器,是因为它可以包含其他对象。一个容器中的所有对象都必须是同一种类型的。
vector对象的定义和初始化
同样的,使用前,导入头文件#include<vector>可以使用using声明:usingstd::vector;
vector是一个类模板(classtemplate)。使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。因此,我们可以定义保存string对象的vector,或保存int值的vector,又或是保存自定义的类类型对象(如Sales_items对象)的vector。
声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以vector为例,必须说明vector保存何种对象的类型,通过将类型放在类型放在类模板名称后面的尖括号中来指定类型:
vector<T>v1;
保存类型为T对象。默认构造函数v1为空。
vector<T>v2(v1);
v2是v1的一个副本。
vector<T>v3(n,i);
v3包含n个值为i的元素。
vector<T>v4(n);
v4含有值初始化的元素的n个副本。
【注意:1、若要创建非空的vector对象,必须给出初始化元素的值;2、当把一个vector对象复制到另一个vector对象时,新复制的vector中每一个元素都初始化为原vectors中相应元素的副本。但这两个vector对象必须保存同一种元素类型;3、可以用元素个数和元素值对vector对象进行初始化。构造函数用元素个数来决定vector对象保存元素的
个数,元素值指定每个元素的初始值】
vector对象动态增长:
vector对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。
【注意:因为
vector增长的效率高
,在元素值已知的情况下,最好是动态地添加元素。】
值初始化:
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行,具体值为何,取决于存储在vector中元素的数据类型。
如果为int型数据,那么标准库将用0值创建元素初始化式;
如果vector保存的是含有构造函数的类类型(如string)的元素,标准库将用该类型的默认构造函数创建元素初始化式;
元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。
#include<iostream> #include<string> #include<vector> intmain() { std::vector<int>a; std::vector<int>b(a); std::vector<int>c(10,23); std::vector<std::string>svec(10,"null"); std::vector<std::string>svec2(10,"hi!"); std::vector<std::string>svec3(10); return0; }
注意,没有=号!
vector对象操作方法
和string类似!
.v.empty()
Returnstrueifvisempty;otherwisereturnsfalse如果v为空,则返回true,否则返回false。
.v.size()
Returnsnumberofelementsinv返回v中元素的个数。
【注意:1、返回相应vector类定义的size_type的值,和string类似。2、使用size_type类型时,必须指出该类型是在哪里定义的。vector类型总是包括总是
包括vector的元素类型vector<int>::size_type
】
v.push_back(t) Addselementwithvaluettoendofv在v的末尾增加一个值为t的元素。以下为例子: #include<iostream> #include<string> #include<cctype> #include<vector> intmain() { //readwordsfromthestandardinputandstorethemaselementsinavector std::stringword; std::vector<std::string>text;//emptyvector while(std::cin>>word) { text.push_back(word);//appendwordtotext for(std::vector<int>::size_typeix=0;ix!=text.size();++ix) std::cout<<"Nowtext["<<ix<<"]is:"<<text[ix]<<std::endl; } return0; }
结果为:
Hello Nowtext[0]is:Hello world! Nowtext[0]is:Hello Nowtext[1]is:world!
注意:
1、不可以直接输出vector对象!和list差别太大了。。。
2、下标操作可以改变已有元素:例如上例,可以在最后加上:text[0]="elements";
3、当然和list一样,肯定不能text[100]="elements";在Python中这样操作list回报下标越界,C++中编译不会报错,运行自动退出!【数组操作时这个会坑死你,不会报错,不会退出!理所当然,缓冲区溢出了,黑客们太喜欢了!】
4、由于动态增长,不能先测试长度,而是循环中动态测试!否则会出现莫名其妙的BUG!有人会担心效率?别担心!代价很小【内联函数】。
v[n]
Returnselementatpositionninv返回v中位置为n的元素。
(1)v1=v2[/code]
Replaceselementsinv1byacopyofelementsinv2把v1的元素替换为v2中元素的副本。
(2)v1==v2[/code]
Returnstrueifv1andv2areequal如果v1与v2相等,则返回true。
(3)!=,<,<=,>,and>=
Havetheirnormalmeanings保持这些操作符惯有的含义。
一个简单的例子
读入一段文本到vector对象,每个单词存储为vector中的一个元素。把vector对象中每个单词转化为大写字母。输出vector对象中转化后的元素,每八个单词为一行输出。
假设文本为:inthevector.transformeachwordintouppercaseletters.Printthetransformedelementsfromthevector,printingeightwordstoaline.
#include<iostream> #include<string> #include<vector> std::stringdeal_word(std::stringword) { std::stringWORD;//创建空字符串 for(std::string::size_typeix=0;ix!=word.size();++ix) { if(notispunct(word[ix])) { WORD+=toupper(word[ix]);//连接非标点字符到字符串 } } returnWORD; } intmain() { std::stringword;//缓存输入的单词 std::vector<std::string>text;//emptyvector std::cout<<"Pleaseinputthetext:"<<std::endl;//提示输入 while(std::cin>>wordandword!="INPUTOVER")//INPUTOVER用于标示输入结束,也可以ctrl+z停止输入 { word=deal_word(word);//单词处理 text.push_back(word);//appendwordtotext } for(std::vector<int>::size_typeix=0,j=0;ix!=text.size();++ix,++j) { if(j==8)//8个单词一行 { std::cout<<std::endl;//换行 j=0;//重新计数 } std::cout<<text[ix]<<"";//加空格! } return0; }
结果为:
Pleaseinputthetext: inthevector.transformeachwordintouppercaseletters.Printthetransformedelementsfromthevector,printingeightwordstoaline.INPUTOVER INTHEVECTORTRANSFORMEACHWORDINTOUPPERCASE LETTERSPRINTTHETRANSFORMEDELEMENTSFROMTHEVECTOR PRINTINGEIGHTWORDSTOALINE
vector.resize与vector.reserve
reserve是容器预留空间,但并不真正创建元素对象,在创建对象之前,不能引用容器内的元素,因此当加入新的元素时,需要用push_back()/insert()函数。
resize是改变容器的大小,并且创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。
再者,两个函数的形式是有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小,第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。下面是这两个函数使用例子:
vector<int>myVec; myVec.reserve(100);//新元素还没有构造, //此时不能用[]访问元素 for(inti=0;i<100;i++) ...{ myVec.push_back(i);//新元素这时才构造 } myVec.resize(102);//用元素的默认构造函数构造了两个新的元素 myVec[100]=1;//直接操作新元素 myVec[101]=2;
初次接触这两个接口也许会混淆,其实接口的命名就是对功能的绝佳描述,resize就是重新分配大小,reserve就是预留一定的空间。这两个接口即存在差别,也有共同点。下面就它们的细节进行分析。
为实现resize的语义,resize接口做了两个保证:
一是保证区间[0,new_size)范围内数据有效,如果下标index在此区间内,vector[indext]是合法的。
二是保证区间[0,new_size)范围以外数据无效,如果下标index在区间外,vector[indext]是非法的。
reserve只是保证vector的空间大小(capacity)最少达到它的参数所指定的大小n。在区间[0,n)范围内,如果下标是index,vector[index]这种访问有可能是合法的,也有可能是非法的,视具体情况而定。
resize和reserve接口的共同点是它们都保证了vector的空间大小(capacity)最少达到它的参数所指定的大小。
因两接口的源代码相当精简,以至于可以在这里贴上它们:
voidresize(size_typenew_size) { resize(new_size,T()); } voidresize(size_typenew_size,constT&x) { if(new_size<size()) erase(begin()+new_size,end());//erase区间范围以外的数据,确保区间以外的数据无效 else insert(end(),new_size-size(),x);//填补区间范围内空缺的数据,确保区间内的数据有效 }