vector 容器与iterator迭代器

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

使用 vector 之前,必须包含相应的头文件。
#include <vector>

using std::vector;

vector 是一个 类模板 ( class template )。模板允许程序员编写单个类或函数定义,这个类和函数定义可用于不同的数据类型上。因此,我们可以定义保存 string 对象的 vector ,或保存 int 值的 vector ,又或是保存自定义的类类型对象(如 Sales_item 对象)的 vector 。
声明从类模板产生的某种类型的对象,需要提供附加信息, 信息的种类取决于模板。以 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> 都是数据类型。

vector对象的定义和初始化

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

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 个副本。

  创建确定个数的元素

若要创建非空的 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 增长的效率高,在元素值已知的情况下,最好是动态地添加元素。 这种 增长方式不同于 C 语言 中的内置数据类型,也不同于大多数其他编程语言的数据类型。 特别地,如果读者习惯了 C 或 Java 的风格,由于 vector 元素连续存储,可能希望最好是预先分配合适的空间。但事实上,为了达到连续性, C ++ 的做法恰好相反 。

虽然可以对给定元素个数的 vector 对象预先分配内存,但更有效的方法是先 初始化一个空 vector 对象,然后再动态地增加元素。

 

值初始化

如果没有给出元素的初始化式,那么标准库将提供一个 值初始化的 ( 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

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

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

vector的操作

vector 标准库提供许多类似于 string 对象的操作,下表 列出了几种最重要的 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 。

!=, <, <=, >, >=

保持这些操作符惯有的含义。

 vector 对象的size

empty 和 size 操作类似于 string 类型的相关操作 。成员函数 size 返 回相应 vector 类定义的 size_type 的值。

使用 size_type 类型时,必须指出该类型是在哪里定义的。 vector 类型总是 包括 vector 的元素类型:

vector<int>::size_type        // ok

vector::size_type          // error

  向 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 就包含了所有读入的元素。

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 或 Java 编程的 C ++ 程序员可能会觉得难以理解, for 循 环的判断条件用 != 而不是用 < 来测试 vector 下标值是否越界。 C 程序员难以理解的还有,上例中没有在 for 循环之前就调用 size 成 员函数并保存其返回的值,而是在 for 语句头中调用 size 成员函数。 C ++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件。
调用 size 成 员函数而不保存它返回的值,在这个例子中同样不是必需的,但这反映了一个良好的编程习惯。在 C ++ 中,有些数据结构(如 vector )可以动态增长。上例中循环仅需要读取元素,而不需要增加新的元素。但是,循环可以容易地增加新元素,如果确实增加了新元 素的话,那么测试已保存的 size 值作为循环的结束条件就会有问题,因为没有将新加入的元素计算在内。所以我们倾向于在每次循环中测试 size 的 当前值,而不是在进入循环时,存储 size 值的副本。

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

 

下标操作不添加元素

初学 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 机和其他应用中最常见的安全问题。

                        vector迭代器

除了使用下标来访问 vector 对象的元素外,标准库还提供了另一种检 测元素的方法:使用 迭代器 ( iterator )。迭代器是一种允许程序员检查容器内元素,并实现元素遍历的数据类型。

标准库为每一种标准容器(包括 vector )定义了一种迭代器类型。迭代器类型提 供了比下标操作更一般化的方法:所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。因为迭代器对所有的容器都适用,现代 C++ 程 序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的 vector 类型也这样。

容 器的iterator 类型

每种容器类型都定义了自己的迭代器类型,如 vector :

vector<int>::iterator iter;

这条语句定义了一个名为 iter 的变量,它的数据类型是由 vector < int > 定义的 iterator 类型。每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。

不同的容器类定义了自己的 iterator 类型,用于访问容器内的元素。换句 话说,每个容器定义了一种名为 iterator 的类型,而这种类型支持(概念上的)迭代器的各种行为。

 

begin和end 操作

每种容器都定义了一对命名为 begin 和 end 的 函数,用于返回迭代器。如果容器中有元素的话,由 begin 返回的迭代器指向第一个元素:

vector<int>::iterator iter = ivec.begin();

上述语句把 iter 初始化为由名为 begin 的 vector 操作返回的值。假设 vector 不空,初始化后, iter 即 指该 元素为 ivec[0] 。

由 end 操作返回的迭代器指向 vector 的“末端元素的下一个”。通常称为 超出末端迭代器 ( off - the - end iterator ) ,表明它指向了一个不存在的元素。如果 vector 为空, begin 返回的迭代器与 end 返回的迭代器相同。

由 end 操 作返回的迭代器并不指向 vector 中任何实际的元素 , 相反 , 它 只是起一个哨兵 ( sentinel ) 的作用 , 表示我们已处理完 vector 中所有元素。

vector 迭代器的自增和解引用运算

迭代器 类型定义了一些操作来获取迭代器所指向的 元素,并允许程序员将迭代器从一个元素移动到另一个元素。

迭代器类型可使用 解引用操作符 ( * 操作 符)来访问迭代器所指向 r 元素:

*iter = 0;

解引用操作符返回迭代器当前所指向的元素。假设 iter 指向 vector 对象 ivec 的第一个元素,那么 *iter 和 ivec[0] 就是指向同一个元素。上面这个语句的效果就是把这个元素的值赋为 0 。

迭代器使用自增操作符 向前移动迭代器指向容器中 下一个元素。从逻辑上说,迭代器的自增操作和 int 型对象的自增操作类似。对 int 对象来说,操作结果就是把 int 型 值“加 1 ”,而对迭代器对象则是把容器中的迭代器“向前移动一个位置”。因此,如果 iter 指 向第一个元素,则 ++ iter 指向第二个元素。

由于 end 操 作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。

  迭代器的其他运算

另一对可执行于迭代器的操作就是比较:用 == 或 != 操作 符来 比较 两个 迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。

迭代器应用的程序示例

假设已声明了一个 vector < int > 型的 ivec 变量,要把它所有元素值重置为 0 ,可以用下标操作来完成:

// reset all the elements in ivec to 0

for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)

       ivec[ix] = 0;

上述程序用 for 循环遍历 ivec 的 元素, for 循环定义了一个索引 ix ,每循环迭代一次 ix 就自增 1 。 for 循环体将 ivec 的每个元素赋值为 0 。

更典型的做法是用迭代器来编写循环:

// equivalent loop using iterators to reset all the elements in ivec to 0

for (vector<int>::iterator iter = ivec.begin();

                         iter != ivec.end(); ++iter)

    *iter = 0;  // set element to which iter refers to 0

for 循环首先定义了 iter ,并将它初始化为指向 ivec 的第一个元素。 for 循环的条件测试 iter 是否与 end 操作返回的迭代器不等。每次迭代 iter 都自增 1 ,这个 for 循 环的效果是从 ivec 第一个元素开始,顺序处理 vector 中的每一元素。最后, iter 将 指向 ivec 中的最后一个元素,处理完最后一个元素后, iter 再增加 1 ,就会 与 end 操作的返回值相等,在这种情况下,循环终止。

for 循环体内的语句用解引用操作符来访问当前元素的值。和下标操作符一样,解引用操作符的返回值是一个左值,因此可以对它进行赋 值来改变它的值。上述循环的效果就是把 ivec 中所有元素都赋值为 0 。

通过上述对代码的详细分析,可以看出这段程序与用下标操作符的版本达到相同的操作效果:从 vector 的第一个元素开始,把 vector 中每个元素都置为 0 。
如果 vector 为空,程序是安全的。如果 ivec 为空,则 begin 返回的迭代器不指向任何元素,由于没有元素,所以它不能指向任何元素——在这种情况下,从 begin 操 作返回的迭代器与从 end 操作返回的迭代器的值相同,因此 for 语句中的测试条件立即失败。

const_iterator

前面的程序用 vector::iterator 改变 vector 中的元素值。每种容器类型还定义了一种名为 const_iterator 的类型,该类型只能访 问容器内元素,但不能改变其值。

当我们对普通 iterator 类型解引用时,得到对某个元素的非 const 引用 。而如果我们对 const_iterator 类型解引用时,则可以得到一个指向 const 对象的引用 ,如同任何常量一样,该对象不能进行重写。

例如 , 如果 text 是 vector < string > 类型 , 程序员想要遍历它 , 输出每个元素 , 可以这样编写程序 :

// use const_iterator because we won't change the elements

for (vector<string>::const_iterator iter = text.begin();

                                    iter != text.end(); ++iter)

    cout << *iter << endl; // print each element in text

除了是从迭代器读取元素值而不是对它进行赋值之外,这个循环与前一个相似。由于这里只需要借助 迭代器进行 读,不需要写, 这里把 iter 定义为 const _ iterator 类型 。当 对 const _ iterator 类型解引用时,返回的是一个const值。不允许用 const _ iterator 进行赋值:

for (vector<string>::const_iterator iter = text.begin();

                                   iter != text.end(); ++ iter)

    *iter = " ";     // error: *iter is const

使用 const_iterator 类型时 , 我 们可以得到一个迭代器 , 它自身的值可以 改变 , 但不能用来改变其所指向的元素的 值。可以对迭代器进行自增以及使用解引用操作符来读取值, 但不能对该元素值赋值。

不要把 const_iterator 对象 与 const 的 iterator 对象 混淆起来 。 声明一个 const 迭代器时,必须初始化迭代器。一旦被初 始化后,就 不能改变它的值:

vector<int> nums(10);  // nums is non const

const vector<int>::iterator cit = nums.begin();

*cit = 1;                 // ok: cit can change its underlying element

++cit;                    // error: can't change the value of cit

const vector<int> nines(10, 9);  // cannot change elements in nines

// error: cit2 could change the element it refers to and nines is const

const vector<int>::iterator cit2 = nines.begin();

// ok: it can't change an element value , so it can be used with a const vector<int>

vector<int>::const_iterator it = nines.begin();

*it = 10; // error: *it is const

++it;     // ok: it isn't const so we can change its value

// an iterator that cannot write elements

vector<int>::const_iterator

// an iterator whose value cannot change

const vector<int>::iterator

迭代器的算术操作

除了一次移动迭代器的一个元素的增量操作符外 , vector 的迭代器 (很少有其他标准库 容器迭代器 ) 也支持其他的算术操作。这些操作称为 迭代器算术操作 (iterator arithmetic ),包括:

l iter + n

  iter - n

可以对 迭代器 对象加上或减去一个整型值。这样做将产生一个新的 迭代器,其位置在 iter 所 指元素之前(加)或之后(减) n 个元素的位置。加或减之后的结果必须指向 iter 所指 vector 中的某个元素,或者是vector末端的后一个元素。加上或减去的值的类型应该是vector的 size_type 或 difference_type 类型 (参考下面的解释)。

l iter1 - iter2                                                       

该表达式用来计算两个 迭代器 对象的距离 , 该距离是名为 difference_type 的 signed 整数 类型的值 , 这里的 difference_type 类型类似于 size_type 类型 ,也 是 由 vector 定义的。 difference_type 是signed类型, 因为减法运算可能产生负数的结果。 该类型可以保证足够大以存储任何两个 迭代器对象间 的距离。 iter1 与 iter2 两者必须都指向同一 vector 中的元素,或者指向vector末端 之后的下一个元素 。

可以用迭代器算术操作来移动迭代器直接指向某个元素,例如,下面语句直接定位于 vector 的中间元素:

vector<int>::iterator mid = vi.begin() + vi.size()/2;

上述代码用来初始化 mid , 使其指向 vi 中最靠近正中间的元素。这种直接计算迭代器的方法,与用迭代器逐个元素自增操作到达中间元素的方法是等价的,但前者的效率 要高得多。

任何改变 vector 长度的操作都会使已存在的 迭代器 失效。例如,在调用 push_back 之 后,就不能再信赖指向 vector 的迭代器的值了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值