vector用法总结(转载)
一、vector的基本概念
vector是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。和string对象一样,标准库负责管理存储元素的相关内存。我们把vector称为容器,是因为它可以包含其他对象。一个容器中的所有对象都必须是同一种类型的。使用vector之前,必须包含相应的头文件。
#include<vector>
usingstd::vector;
vector是一个类模板(classtemplate),这个类和函数定义可用于不同的数据类型上。因此,我们可以定义保存string对象的vector,或保存int值的vector,又或是保存自定义的类类型对象(如Sales_item对象)的vector。声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以vector为例,必须说明vector保存何种对象的类型,通过将类型放在类模板名称后面的尖括号中来指定类型:
vector<int>ivec;//ivecholdsobjectsoftypeint
vector<Sales_item>Sales_vec;//holdsSales_items
和其他变量定义一样,定义vector对象要指定类型和一个变量的列表。上面的第一个定义,类型是vector<int>,该类型即是含有若干int类型对象的vector,变量名为ivec。第二个定义的变量名是Sales_vec,它所保存的元素是Sales_item类型的对象。
vector不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector类型的每一种都指定了其保存元素的类型。因此,vector<int>和vector<string>都是数据类型。
二、vector的基本操作
1)vector对象的定义和初始化
vector类定义了好几种构造函数,用来定义和初始化vector对象。如下示出几种初始化vector对象的方式:
vector<T>v1; |
vector保存类型为T的对象。默认构造函数v1为空。 |
vector<T>v2(v1); |
v2是v1的一个副本。 |
vector<T>v3(n,i); |
v3包含n个值为i的元素。 |
vector<T>v4(n); |
v4含有值初始化的元素的n个副本。 |
a、创建确定个数的元素
若要创建非空的vector对象,必须给出初始化元素的值。当把一个vector对象复制到另一个vector对象时,新复制的vector中每一个元素都初始化为原vector中相应元素的副本。但这两个vector对象必须保存同一种元素类型:
vector<int>ivec1;//ivec1holdsobjectsoftypeint
vector<int>ivec2(ivec1);//ok:copyelementsofivec1intoivec2
vector<string>svec(ivec1);//error:svecholdsstrings,notints
可以用元素个数和元素值对vector对象进行初始化。构造函数用元素个数来决定vector对象保存元素的个数,元素值指定每个元素的初始值:
vector<int>ivec4(10,-1);//10elements,eachinitializedto-1
vector<string>svec(10,"hi!");//10strings,eachinitializedto"hi!"
关键概念:vector对象动态增长
vector对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。因为vector增长的效率高,在元素值已知的情况下,最好是动态地添加元素。
虽然可以对给定元素个数的vector对象预先分配内存,但更有效的方法是先初始化一个空vector对象,然后再动态地增加元素。
b、值初始化
如果没有给出元素的初始化式,那么标准库将提供一个值初始化的(valueinitialized)元素初始化式。这个由库生成的初始值用于初始化容器中的每个元素。而元素初始化式的值取决于存储在vector中元素的数据类型。
如果vector保存内置类型(如int类型)的元素,那么标准库将用0值创建元素初始化值:
vector<string>fvec(10);//10elements,eachinitializedto0
如果向量保存类类型(如string)的元素,标准库将用该类型的默认构造函数创建元素初始值:
vector<string>svec(10);//10elements,eachanemptystring
c、vector的操作
empty() |
如果 v 为空,则返回 true, 否则返回 false 。 |
v . size () |
返回 v 中元素的个数。 |
v . push _ back ( t ) |
在 v 的末尾增加一个值为 t 的元素。 |
v [ n ] |
返回 v 中位置为 n 的元素。 |
v1 = v2 |
把 v1 的元素替换为 v2 中元素的副本。 |
v1 == v2 |
如果 v1 与 v2 相等,则返回 true 。 |
!=, <, <=, >, >= |
保持这些操作符惯有的含义。 |
(1)vector对象的size
empty和size操作类似于string类型的相关操作。成员函数size返回相应vector类定义的size_type的值。
使用size_type类型时,必须指出该类型是在哪里定义的。vector类型总是包括vector的元素类型:
vector<int>::size_type//ok
vector::size_type//error
(2)向vector添加元素
push_back()操作接受一个元素值,并将它作为一个新的元素添加到vector对象的后面,也就是“插入(push)”到vector对象的“后面(back)”:
//read words from the standard input and store the elements in a vector
stringword;
vector<string>text;//emptyvector
while(cin>>word){
text.push_back(word);//appendwordtotext
}
该循环从标准输入读取一系列string对象,逐一追加到vector对象的后面。首先定义一个空的vector对象text。每循环一次就添加一个新元素到vector对象,并将从输入读取的word值赋予该元素。当循环结束时,text就包含了所有读入的元素。
(3)vector的下标操作
vector中的对象是没有命名的,可以按vector中对象的位置来访问它们。通常使用下标操作符来获取元素。vector的下标操作类似于string类型的下标操作。
vector的下标操作符接受一个值,并返回vector中该对应位置的元素。vector元素的位置从0开始。下例使用for循环把vector中的每个元素值都重置为0:
//reset the elements in the vector to zero
for(vector<int>::size_type ix=0;ix!=ivec.size();++ix)
ivec[ix]=0;
和string类型的下标操作符一样,vector下标操作的结果为左值,因此可以像循环体中所做的那样实现写入。另外,和string对象的下标操作类似,这里用size_type类型作为vector下标的类型。
在上例中,即使ivec为空,for循环也会正确执行。ivec为空则调用size返回0,并且for中的测试比较ix和0。第一次循环时,由于ix本身就是0,则条件测试失败,for循环体一次也不执行。
关键概念:安全的泛型编程
C++程序员习惯于优先选用!=而不是<来编写循环判断条件。
(4)下标操作不添加元素
初学C++的程序员可能会认为vector的下标操作可以添加元素,其实不然:
vector<int>ivec;//emptyvector
for(vector<int>::size_typeix=0;ix!=10;++ix)
ivec[ix]=ix;//disaster:ivec has no elements
上述程序试图在ivec中插入10个新元素,元素值依次为0到9的整数。但是,这里ivec是空的vector对象,而且下标只能用于获取已存在的元素。
这个循环的正确写法应该是:
for(vector<int>::size_typeix=0;ix!=10;++ix)
ivec.push_back(ix);//ok:adds new element with value ix
必须是已存在的元素才能用下标操作符进行索引。通过下标操作进行赋值时,不会添加任何元素。
警告:仅能对确知已存在的元素进行下标操作
对于下标操作符([]操作符)的使用有一点非常重要,就是仅能提取确实已存在的元素,例如:
vector<int>ivec;//empty vector
cout<<ivec[0];//Error: ivec has no elements!
vector<int>ivec2(10);//vector with 10 elements
cout<<ivec[10];//Error:ivec has elements 0...9
试图获取不存在的元素必然产生运行时错误。
附:常用方法
1.push_back() 在数组的最后添加一个数据
2.pop_back() 去掉数组的最后一个数据
3.at() 得到编号位置的数据
4.begin() 得到数组头的指针
5.end() 得到数组的最后一个单元+1的指针
6.front() 得到数组头的引用
7.back() 得到数组的最后一个单元的引用
8.max_size() 得到vector最大可以是多大
9.capacity() 当前vector分配的大小
10.size() 当前使用数据的大小
11.resize() 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
12.reserve() 改变当前vecotr所分配空间的大小
13.erase() 删除指针指向的数据项
14.clear() 清空当前的vector
15.rbegin() 将vector反转后的开始指针返回(其实就是原来的end-1)
16.rend() 将vector反转构的结束指针返回(其实就是原来的begin-1)
17.empty() 判断vector是否为空
18.swap() 与另一个vector交换数据
三、vector的注意点
和标准C++运行库中的绝大部分东西一样,标准容器类是用类型来参数化的:你能创建一个std::vector<int>来容纳int类型的对象,创建一个std::vector<std::string>来容纳string对象,创建一个 std::vector<my_type>来容纳用户自定义类型的对象。
创建std::vector<int *>、std::vector<std::string *>或
std::vector<my_type *>也是完全合理的。容纳指针的容器很重要也很常见。
不幸地是,虽然是常见技术,容纳指针的容器对新手来说也是造成混乱的最常见的根源之一。几乎没有哪个星期在C++新闻组中不出现这样的贴子的:为什么这样的代码导致内存泄漏:
{
std::vector<my_type*> v;
for (int i = 0; i < N; ++i)
v.insert(new my_type(i));
...
} // v is destroyed here
这个内存泄漏是编译器的bug吗?std::vector的析构函数不是会销毁v的元素的吗?
如果你仔细想过std::vector<T>大体上是如何工作的,并且你了解其中对指针并没有特别的规则的话(也就是说,对vector来说,my_type *只不过是另外一个T)就不难明白为什么vector<my_type *>有这样的行为以及为什么这段代码有内存泄漏了。然而,vector<my_type *>的行为可能会令对旧的容器库更熟悉的人感到惊讶的。
这篇文章解释了容纳指针的容器的行为是怎么样的,什么时候容纳指针的容器会有用,和在需要执行比标准容器在内存管理上的默认行更多的任务时,该做些什么。
1、容器和所有权
标准容器使用值语义。举例来说,当你向一个vector附加一个变量x时:
v.push_back(x)
你实际正在做的是附加x 的一个拷贝。这个语句存储了x的值(的一个拷贝),而不是x的地址。在你将x加入一个vector后,你能对x做如何想做的事(比如赋给它一个新值或让它离开生存域而销毁)而不影响vector中的拷贝。一个容器中的元素不能是另外一个容器的元素(两个容器的元素必须是不同的对象,即使这些元素碰巧相等),并且将一个元素从容器中移除将会销毁这个元素(虽然具有相同的值的另外一个对象可能存在于别处)。最后,容器“拥有”它的元素:当一个容器销毁时,其中的所以元素都随它一起销毁了。
这些特性与平常的内建数组很相似,并且可能是太明显了而不值一提。我列出它们以清楚显示容器和数组有多么相似。新手使用标准容器时发生的最常见的概念是认为容器“在幕后”做了比实际上更多的事。
值语义不总是你所需要的:有时你需要在容器中存储对象的地址而不是拷贝对象的值。你能以和数组相同的方式,用容器实现引用语义:藉由显式要求。你能将任何类型的对象放入容器,而指针自己就是非常好的对象。指针占用内存;能被赋值;自己有地址;有能被拷贝的值。如果你需要存储对象的地址,就使用容纳指针的容器。不再是写:
std::vector<my_type> v;
my_type x;
...
v.push_back(x);
你能写:
std::vector<my_type*> v;
my_type x;
...
v.push_back(&x);
感觉上,没有任何变化。你仍然正在创建一个std::vector<T>;只不过现在T碰巧是一个指针类型,my_type *。vector仍然“拥有”它的元素,但你必须明白这些元素是什么:它们是指针,而不是指针所指向的东西。
拥有指针和拥有指针所指的东西之间的区别就象是vector与数组或局部变量。假如你写:
{
my_type* p = new my_type;
}
当离开代码域时,指针p将会消失,但它所指向的对象,*p,不会消失。如果你想销毁这对象并释放其内存,你需要自己来完成,显式地写delete p或用其它等价的方法。同样,在std::vector<my_type *>中没有任何特殊代码以遍历整个vector并对每个元素调用delete。元素在vector消失时消失。如果你想在那些元素销毁前发生另外一些事,你必须自己做。
你可能奇怪为什么std::vector和其它标准容器没有设计得对指针做些特别的动作。首先,当然,有一个简单的一致性因素:理解有一致语义的库比理解有许多特例的库容易。如果存在特例,很难划出分界线。你将iterator或用户自定义的handle类型等同于指针吗?如果在通用规则上对 vector<my_type *>有一个例外,应该对vector<const my_type *>再有一个例外的例外吗?容器如何知道什么时候用delete p,什么时候用delete [] p?
第二,并且更重要的是:如果std::vector<my_type *>确实自动地拥有所指向的对象,std::vector的用处就大为减少了。毕竟,如果你期望一个vector拥有一系列my_type的对象的话,你已经有vector<my_type>了。vector<my_type *>是供你需要另外一些不同的东西时用的,在值语义和强所有权不合适时。当你拥有的对象被多个容器引用时,或对象能在同一容器出现多次时,或指针开始时并不指向有效对象时,你可以用容纳指针的容器。(它们可能是NULL指针,指向原生内存的指针,或指向子对象的指针。)
想像一个特别的例子:
你正在维护一个任务链表,某些任务当前是活动的,某些被挂起。你用一个std::list<task>存放所有任务,用一个std::vector<task *>存放活动任务组成的任务子集。
你的程序有一个字符串表:std::vector<const char *>,每个元素p指向一个NULL结束的字符数组。依赖于你如何设计你的字符串表,你可能使用字符串文字,或指向一个巨大的字符数组内部,无论哪种方法,你都不能用一个循环遍历vector,并对每个元素调用delete p。
你正在做I/O multiplexing,并且将一个std::vector<std::istream *>传给一个函数。input stream是在别处打开的,将于别处关闭,并且,也许其中之一就是&std::cin。
如果容纳指针的容器多手多脚地delete了所指向的对象,上面的用法没一个能成为可能。
2、拥有所指向的对象
如果你创建了一个容纳指针的容器,原因通常应该是所指向的对象由别处创建和销毁的。有没有情况是有理由获得一个容器,它拥有指针本身,还拥有所指向的对象?有的。我知道的唯一一个好的理由,但也是很重要的一个理由:多态。
C++ 中的多态是和指针/引用语义绑定在一起的。假如,举例来说,那个task不只是一个类,而且它是一个继承体系的基类。如果p是一个task *,那么p可能指向一个task对象或任何一个从task派生的类的对象。当你通过p调用task的一个虚函数,将会在运行期根据p所指向的实际类型调用相应的函数。
不幸地是,将task作为多态的基类意味着你不能使用vector<task>。容器中的对象是存储的值;vector<task>中的元素必须是一个task对象,而不能是派生类对象。(事实上,如果你遵从关于继承体系的基类必须是抽象基类的忠告的话,那么编译器将不允许你创建task对象和vector<task>对象。)
面向对象的设计通常意味着在对象被创建到对象被销毁之间,你通过指针或引用来访问对象。如果你想拥有一组对象,除了容纳指针的容器外,你几乎没有选择。管理这样的容器的最好的方法是什么?
如果你正使用容纳指针的容器来拥有一组对象,关键是确保所有的对象都被销毁。最明显的解决方法,可能也是最常见的,是在销毁容器前,遍历它,并为每个元素调用delete语句。如果手写这个循环太麻烦,很容易作一个包装:
template <class T>
class my_vector : private std::vector<T*>
{
typedef std::vector<T> Base;
public:
using Base::iterator;
using Base::begin;
using Base::end;
...
public:
~my_vector() {
for (iterator i = begin(); i != end(); ++i)
delete *i;
}
};
这个技巧能工作,但是它比看起来有更多的限制和要求。
问题是,只改析构函数是不够的。如果你有一个列出所有的正要被销毁的对象的容器,那么你最好确保只要指针离开了容器那个对象就要被销毁,并且一个指针绝不在容器中出现两次。当你用erase()或clear()移除指针时,必须要小心,但是你也需要小心容器的赋值和通过iterator的赋值:象v1 = v2,和v[n] = p这样的操作是危险的。标准泛型算法,有很多会执行通过iterator的赋值的,这是另外一个危险。你显然不能使用std::copy()和 std::replace()这样的泛型算法;稍微不太明显地,你也不能使用std::remove()、std::remove_if(),和 std::unique()。
象my_vector这样的包装类能够解决其中一些问题,但不是全部。很难看出如何阻止用户以危险的方式使用赋值,除非你禁止所有的赋值,而那时,你所得到的就不怎么象容器了。
问题是每个元素都必须被单独追踪,所以,也许解决方法是包装指针而不是包装整个容器。
标准运行库定义了一个对指针包装的类std::auto_ptr<T>。一个auot_ptr对象保存着一个T *类型的指针p,其构造函数delete由p所指的对象。看起来这正是我们所要找的:一个包装类,其析构函数delete一个指针。自然会想到用 vector<auto_ptr<T> >取代vector<T *>。
这是很自然的主意,但它是错误的。原因呢,再一次,是因为值语义。容器类假设它们能拷贝自己的元素。举例来说,如果你有一个vector<T>,那么T类型的对象必须表现得和一个平常的数值一样。如果t1是一个T类型的值,你最好能够写:
T t2(t1)
并且得到一个t1的拷贝t2。
形式上,按C++标准中的说法,T要是Assignable的和CopyConstructible的。指针满足这些要求(你能得到指针的一个拷贝)但 auto_ptr不满足。auto_ptr的卖点是它维护强所用权,所以不允许拷贝。有一个形式上是拷贝构造函数的东西,但auto_ptr的“拷贝构造函数”实际上并不进行拷贝。如果t1是一个 std::auto_ptr<T>,并且你写:
std::auto_ptr<T> t2(t1)
然后t2将不是t1的一个拷贝。不是进行拷贝,而是发生了所有权转移(t2得到了t1曾经有着的值,而t1被改成一个NULL指针)。auto_ptr 的物件是脆弱的:你只不过看了它一下就能改变它的值。
vector用法总结(转载)的更多相关文章
- #include <vector>用法之我见
vector是一种顺序容器,事实上和数组差不多,但它比数组更优越.一般来说数组不能动态拓展,(何为动态拓展,即是说如果你知道你要存的数据的个数,你定义的存储数据的数组大小也就决定了,但是若你事先不知道 ...
- STL vector用法介绍
STL vector用法介绍 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和f ...
- C++-STL:vector用法总结
目录 简介 用法 1. 头文件 2. vector的声明及初始化 3. vector基本操作 简介 vector,是同一类型的对象的集合,这一集合可看作可变大小的数组,是顺序容器的一种.相比于数组,应 ...
- Python中super的用法【转载】
Python中super的用法[转载] 转载dxk_093812 最后发布于2019-02-17 20:12:18 阅读数 1143 收藏 展开 转载自 Python面向对象中super用法与MRO ...
- [转载]Vector用法(C++ Primer中文版)
转自:http://blog.sciencenet.cn/blog-261330-551086.html vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值.和 string 对 ...
- 转载-C++ vector 用法
转自:http://www.cnblogs.com/wang7/archive/2012/04/27/2474138.html 在c++中,vector是一个十分有用的容器,下面对这个容器做一下总结. ...
- C++ STL之vector用法总结
介绍 vector是表示可变大小数组的序列容器. 就像数组一样,vector也采用的连续存储空间来存储元素.也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效.但是又不像数组,它的大 ...
- AutoMapper用法(转载)
申明 本文转载自http://www.qeefee.com/article/automapper 作者:齐飞 配置AutoMapper映射规则 AutoMapper是基于约定的,因此在实用映射之前,我 ...
- linux之sed用法【转载】
sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法sed命令行格式为: sed ...
随机推荐
- --tags --follow-tags 的区别
--tags All refs under refs/tags are pushed, in addition to refspecs explicitly listed on the comm ...
- 在XP下基于VHD版XP 2003 win7制作的RAMOS心得
在XP下基于VHD版win7制作的RAMOS心得1.用DiskGenius创建1.85G的VHD固定磁盘文件,以win7prosen.vhd为例,然后进行分区格式化,格式化时启用NTFS压缩.2.为了 ...
- js里面获取三位不重复值
<html><body> <script type="text/javascript"> var d = new Date();var sz = ...
- 本地化Model Factory
即让其生成中文的测试数据 先简单介绍Model Factory两个常用方法: 进入tinker页面生成测试数据: Factory(User::class,10)->make():make()方法 ...
- 5.5 Selenium2中的元素定位
WebDriver的更加面向对象的方式大大降低了Selenium的入门门槛,对Web元素的操作也非常之简单易学.实际项目用起来,工作量最大的部分就是你如何解析定位到你的目标项目页面中的各种元素.好比你 ...
- Java中print、printf、println
Java中的System.out输出会用到print.println以及printf命令. 其中 print一般的标准输出,但是不换行. println对比print即在结尾处多了换行. eg:pr ...
- append appendChild appendTo区别
1.append和appendChild的区别 append是jquery文档操作用法 ① append() 方法在被选元素的结尾(仍然在内部)插入指定内容. $(selector).append(c ...
- 30 个有用的 HTML5 和 CSS3 表单设计
基本上表单是任何一个网站都必须要用到的元素,本文介绍的这 30 个设计方案供你参考,这些方案如果要单独下载完整可运行的文件则需要支付2-5美元的费用. 1. Fresh Forms 2. Pretty ...
- bootstrap的警告框
.alert 基础警告框 .alert-danger 红色警告框 .alert-dismissable 修饰警告框 alert-dismiss="alert" 触发警告框 // ...
- Qt之Timers
简述 QObject是所有Qt objects的基类,在Qt中提供了基础定时器的支持.使用QObject::startTimer(),你可以传递一个毫秒数间隔作为参数启动一个定时器.该函数返回一个唯一 ...