STL Vector使用

http://blog.163.com/zhoumhan_0351/blog/static/399542272010225104536463

Vector 像一个快速的数组,其具有数组的快速索引方式。还具有动态地进行扩展。

Vector 的特点

1.索引和迭代器非常快,这个是结合了数组的特点,其低层就是一个指针动态分配的数组。

2.当数组容量不够时,它的操作是这样的。

⑴.分配一个更大的连续存储区

⑵.将旧内存中的东西拷贝到新内存中去。

⑶.销毁旧内存中的对象

⑷.释放旧内存。

那么对于复杂的对象就一个问题了,在进行上述“扩容”的过程中,就会带来很多构造函数,复制构造函数和析构函数的调用。所以就很浪费时间,所以有时候,我们会vetor保存它的指针,而不是对象。

3. 插入和删除

如果只在vector的后端插入元素,那么这个动作时很快的。但是我们要在中间插入一些元素,那么他的花费就是相当昂贵的,因为它就会像数组那样,挨个向后移动。对于复杂对象,会调用有些operator= 。当容量不够时也会扩容。

 

所以综上,用vector的绝佳条件是。

1. 开始时就知道元素的正确数量。(防止多次扩容带来的影响)

2. 对数据的索引和随即访问频繁(刚好利用了数组的特点)

3. 插入和删除大多数在表的尾端。(在中间的话会引起数组移动)

补充:对过于复杂的自定义对象,我们建议在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交换数据


http://blog.chinaunix.net/uid-20622737-id-3278413.html

注意,在使用前要包含vector对应的头文件:

#include<vector>

vector是同一种类型的对 象的集合,每个对象都有一个对应的整数索引值。和string对象一样,标准库负责管理存储元素的相关内存。我们把vector称为容器,是因为它可以包 含其他对象。一个容器中的所有对象都必须是同一种类型的。我们将在第9章更详细地介绍容器。

使用vector之前,必须包含相应的头文件。本书给出的例子,都是假设已作了相应的using声明:

#include <vector>

using std::vector;

vector 是一个类模板(class template)。模板允许程序员编写单个类或函数定义,这个类和函数定义可用于不同的数据类型上。因此,我们可以定义保存string对象的 vector,或保存int值的vector,又或是保存自定义的类类型对象(如Sales_item对象)的vector。将在第16章介绍如何定义程 序员自己的类模板。幸运的是,使用类模板时只需要简单了解类模板是如何定义的就可以了。

声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以vector为例,必须说明vector保存何种对象的类型,通过将类型放在类模板名称后面的尖括号中来指定类型:

vector<int> ivec;                 // ivec holds objects of type int

vector<Sales_item> Sales_vec;  // holds Sales_items

和其 他变量定义一样,定义vector对象要指定类型和一个变量的列表。上面的第一个定义,类型是vector<int>,该类型即是含有若干 int类型对象的vector,变量名为ivec。第二个定义的变量名是Sales_vec,它所保存的元素是Sales_item类型的对象。

vector不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector类型的每一种都指定了其保存元素的类型。因此,vector<int>和vector <string>都是数据类型。

3.3.1  vector对象的定义和初始化

vector类定义了好几种构造函数(2.3.3节),用来定义和初始化vector对象。表3-4列出了这些构造函数:

表3-4  几种初始化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个副本。

1. 创建确定个数的元素

若要创建非空的vector对象,必须给出初始化元素的值。当把一个vector对象复制到另一个vector对象时,新复制的vector中每一个元素都初始化为原vector中相应元素的副本。但这两个vector对象必须保存同一种元素类型:

vector<int> ivec1;                // ivec1 holds objects of type int

vector<int> ivec2(ivec1);       // ok: copy elements of ivec1 into ivec2

vector<string> svec(ivec1);    // error: svec holds strings, not ints

可以用元素个数和元素值对vector对象进行初始化。构造函数用元素个数来决定vector对象保存元素的个数,元素值指定每个元素的初始值:

vector<int> ivec4(10, -1);     // 10 elements, each initialized to -1

vector<string> svec(10, "hi!"); // 10 strings, each initialized to "hi!"

关键概念:vector对象动态增长                                                       

vector对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。因为vector增长的效率高,在元素值已知的情况下,最好是动态地添加元素。

正如 第4章将介绍的,这种增长方式不同于C语言中的内置数据类型,也不同于大多数其他编程语言的数据类型。特别地,如果读者习惯了C或Java的风格,由于 vector元素连续存储,可能希望最好是预先分配合适的空间。但事实上,为了达到连续性,C++的做法恰好相反,具体原因将在第9章探讨。

虽然可以对给定元素个数的vector对象预先分配内存,但更有效的方法是先初始化一个空vector对象,然后再动态地增加元素(我们随后将学习如何进行这样的操作)。

 

2. 值初始化

如果没有给出元素的初始化式,那么标准库将提供一个值初始化的(value initialized)元素初始化式。这个由库生成的初始值用于初始化容器中的每个元素。而元素初始化式的值取决于存储在vector中元素的数据类型。

如果vector保存内置类型(如int类型)的元素,那么标准库将用0值创建元素初始化值:

vector<string> fvec(10); // 10 elements, each initialized to 0

如果向量保存类类型(如string)的元素,标准库将用该类型的默认构造函数创建元素初始值:

vector<string> svec(10); // 10 elements, each an empty string

第12章将介绍一些有自定义构造函数但没有默认构造函数的类,在初始化这种类型的Vector对象时,程序员就不能仅提供元素个数,还需要提供元素初始值。

还有第三种可能性:元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。

习题                                                           

习题3.11  下面哪些vector定义不正确?

   (a) vector< vector<int> > ivec;

   (b) vector<string> svec = ivec ;

   (c) vector<string> svec(10,”null”);

习题3.12  下列每个vector对象中元素个数是多少?各元素的值是什么?

   (a) vector<int> ivec1;

   (b) vector<int> ivec2(10);

   (c) vector<int> ivec3(10,42);

   (d) vector<string> svec1;

   (e) vector<string> svec2(10);

   (f) vector<string> svec3(10,”hello”);

 

3.3.2  vector的操作

vector标准库提供许多类似于string对象的操作,表3-5列出了几种最重要的vector操作。

表3-5  vector操作

v.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类型的相关操作(3.2.3节)。成员函数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 them as elements in a vector

string word;

vector<string> text;        // empty vector

while (cin >> word) {

    text.push_back(word);  // append word to text

}

该循环从标准输入读取一系列string对象,逐一追加到vector对象的后面。首先定义一个空的vector对象text。每循环一次就添加一个新元素到vector对象,并将从输入读取的word值赋予该元素。当循环结束时,text就包含了所有读入的元素。

3. vector的下标操作

vector中的对象是没有命名的,可以按vector中对象的位置来访问它们。通常使用下标操作符来获取元素。vector的下标操作类似于string类型的下标操作(3.2.3节)。

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或Java编程的C++程序员可能会觉得难以理解,for循环的判断条件用!=而不是用<来测试vector下标值是否越界。C程序员难以理解 的还有,上例中没有在for循环之前就调用size成员函数并保存其返回的值,而是在for语句头中调用size成员函数。

C++程序员习惯于优先选用!=而不是<来编写循环判断条件。在上例中,选用或不用某种操作符并没有特别的取舍理由。学习完本书第二部分的泛型编程后,你将会明白这个习惯的合理性。

调用 size成员函数而不保存它返回的值,在这个例子中同样不是必需的,但这反映了一个良好的编程习惯。在C++中,有些数据结构(如vector)可以动态 增长。上例中循环仅需要读取元素,而不需要增加新的元素。但是,循环可以容易地增加新元素,如果确实增加了新元素的话,那么测试已保存的size值作为循 环的结束条件就会有问题,因为没有将新加入的元素计算在内。所以我们倾向于在每次循环中测试size的当前值,而不是在进入循环时,存储size值的副 本。

我们将在第7章学习到,C++中有些函数可以声明为内联(inline)函数。编译器遇到内联函数时就会直接扩展相应代码,而不是进行实际的函数调用。像size这样的小库函数几乎都定义为内联函数,所以每次循环过程中调用它的运行时代价是比较小的。

 

4. 下标操作不添加元素

初学C++的程序员可能会认为vector的下标操作可以添加元素,其实不然:

vector<int> ivec;   // empty vector

for (vector<int>::size_type ix = 0; ix != 10; ++ix)

     ivec[ix] = ix; // disaster: ivec has no elements

上述程序试图在ivec中插入10个新元素,元素值依次为0到9的整数。但是,这里ivec是空的vector对象,而且下标只能用于获取已存在的元素。

这个循环的正确写法应该是:

for (vector<int>::size_type ix = 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

试图获取不存在的元素必然产生运行时错误。和大多数同类错误一样,不能确保执行过程可以捕捉到这类错误,运行程序的结果是不确定的。由于取不存在的元素的结果是未定义的,因而不同的实现会导致不同的结果,但程序运行时几乎肯定会以某种有趣的方式失败。

本警告适用于任何使用下标操作的时候,如string类型的下标操作,以及将要简要介绍的内置数组的下标操作。

不幸的是,试图对不存在的元素进行下标操作是程序设计过程中经常会犯的严重错误。所谓的“缓冲区溢出”错误就是对不存在的元素进行下标操作的结果。这样的缺陷往往导致PC机和其他应用中最常见的安全问题。

//////////////////////

1.vector iterators incompatible

发现引发这个错误的代码如下:
for (VectorType::iterator it = someVector.begin();; it != someVector.end();++it;)
{
    if (*it== value)
    {
        someVector.erase(it);
    }
}
代码中,在erase操作后,没有修改it就继续循环,在与end()比较时,断言出现。
这里的主要问题是,vector可以用任意方法实现erase,不保证在erase一个元素后,后续的元素一定被移动到这个iterator所引用的位置(地址)。当然,这在几乎所有STL的实现中,都是对的,这也就是以前用VC6编译后运行没有问题的原因。但如果这里用的不是vector,而是list或是map,运行到这里,程序会毫不犹豫的崩溃。
正确的做法是这样的:
STL里所有的容器类的erase实现都会返回一个iterator,这个iterator指向了“当前删除元素的后继元素,或是end()”
因此,在遍历容器的所有元素过程中通过erase删除一个元素后,将erase的返回值赋给迭代变量:
for (VectorType::iterator it = someVector.begin();; it != someVector.end();)
{

    if (*it== value)
    {
        it = someVector.erase(it);
    }
    else
    {
        ++it;
    }
}
P.S. 可以看出,VS2005带的STL增加了更多的调试特性以避免出现STL的一些错误,有条件的话最好用VS2005的STL。如果没有VS2005,也可以使用STLport,STLport在调试特性上也非常出色。

如果你要略过这个检查,可以用#define _HAS_ITERATOR_DEBUGGING 0 不过建议还是检查线程的同步代码。


2.空Vector问题。不允许引用空vector的begin迭代器,因为vector是空的,自然不会有第一个项目,使用也会引发vector iterators incompatible。

 

3.vector同时读写引发。vc2005 对于迭代器的匹配是非常严格的,通常这种错误是因为两个不同的迭代器操作同一个 vector,或者是因为迭代器在遍历vector时,vector的链表改变了,就会引发这种错误,比如vector在遍历的途中,别的位置push_back()一个元素,这时迭代器就失效了,导致错误。可以使用临界区互斥访问。

 

4.迭代器越界,则相应会调用一个非法参数处理程序。
再次提醒,可以通过抛出一个越界异常来避免产生非法参数问题。在代码中加入#define value of _SECURE_SCL_THROWS,并把value值设为1,这样就不会调用非法参数处理程序,而是产生一个异常了。
也可以通过设置#defined value of _SECURE_SCL值为零,关闭此迭代器检查,通常默认情况下,此选项是打开的。

 

5.VS2005编译Release版STL的迭代器bug。经测试Unicode版的比较容易出这个问题,就是莫名其妙的迭代器错误。
微软的原文:The bug afflicted all Standard containers (vector, deque, list, set, multiset, map, multimap, and string) when _HAS_ITERATOR_DEBUGGING was disabled and _SECURE_SCL was enabled. Additionally, deque was afflicted when both were disabled.解决办法是

#ifndef _DEBUG

#define _SECURE_SCL 0

#endif

posted @ 2017-01-13 16:09  wolfplan  阅读(431)  评论(0编辑  收藏  举报