顺序容器的操作

C++ 的每种顺序容器都提供了一组有用的类型定义和以下操作:

  1. 在容器中添加元素。
  2. 在容器中删除元素。
  3. 设置容器大小。
  4. (如果有的话)获取容器内的第一个和最后一个元素。

//所有容器都提供下表所列出的类型

size_type         无符号整型,足以存储此容器类型的最大可能容器长度

iterator          此容器类型的迭代器类型

const_iterator      元素的只读迭代器类型

reverse_iterator     按逆序寻址元素的迭代器

const_reverse_iterator  元素的只读(不能写)逆序迭代器

difference_type      足够存储两个迭代器差值的有符号整型,可为负数

value_type         元素类型

reference         元素的左值类型,是 value_type& 的同义词

const_reference      元素的常量左值类型,等效于 const value_type&

简单地说,逆序迭代器从后向前遍历容器,并反转了某些相关的迭代器操作:

表中的最后 3 种类型使程序员无须直接知道容器元素的真正类型就能使用它。
需要使用元素类型时,只要用 value_type 即可。
如果要引用该类型,则通过 reference 和 const_reference 类型实现。
在程序员编写自己的泛型程序时,这些元素相关类型的定义非常有用。

使用容器定义类型的表达式看上去好像是很复杂的样子(其实看懂了就会发现也没什么):
// iter is the iterator type defined by list<string>
list<string>::iterator iter;

iter 所声明使用了作用域操作符,
以表明此时所使用的符号 :: 右边的类型名字是在符号左边指定容器的作用域内定义的。
其效果是将 iter 声明为 iterator 类型,而 iterator 是存放 string 类型元素的 list 类的成员。

begin 和 end 操作产生指向容器内第一个元素和最后一个元素的下一位置的迭代器,
这两个迭代器通常用于标记包含容器中所有元素的迭代器范围。

c.begin()     返回一个迭代器,它指向容器 c 的第一个元素

c.end()    返回一个迭代器,它指向容器 c 的最后一个元素的下一位置

c.rbegin()  返回一个逆序迭代器,它指向容器 c 的最后一个元素

c.rend()    返回一个逆序迭代器,它指向容器 c 的第一个元素前面的位置

上述每个操作都有两个不同版本:一个是 const 成员,另一个是非 const 成员。
这些操作返回什么类型取决于容器是否为 const。
如果容器不是 const,则这些操作返回 iterator 或 reverse_iterator 类型。
如果容器是 const,则其返回类型要加上 const_ 前缀,
也就是 const_iterator 和 const_reverse_iterator 类型。

所有顺序容器都支持 push_back 操作,提供在容器尾部插入一个元素的功能。

// read from standard input putting each word onto the end of container
string text_word;
while (cin >> text_word)
container.push_back(text_word);

调用 push_back 函数会在容器 container 尾部创建一个新元素,并使容器的长度加 1。
新元素的值为 text_word 对象的副本,而 container 的类型则可能是 list、vector 或 deque。
除了 push_back 运算,list 和 deque 容器类型还提供了类似的操作:push_front。
这个操作实现在容器首部插入新元素的功能。

关键概念:容器元素都是副本。

在容器中添加元素时,系统是将元素值复制到容器里。
类似地,使用一段元素初始化新容器时,新容器存放的是原始元素的副本。
被复制的原始值与新容器中的元素各不相关,
此后,容器内元素值发生变化时,被复制的原值不会受到影响,反之亦然。

//在顺序容器中添加元素的操作

c.push_back(t)    在容器 c 的尾部添加值为 t 的元素。返回 void 类型
            只适用于 list 和 deque 容器类型.

c.push_front(t)   在容器 c 的前端添加值为 t 的元素。返回 void 类型
            只适用于 list 和 deque 容器类型.

c.insert(p,t)    在迭代器 p 所指向的元素前面插入值为 t 的新元素。
            返回指向新添加元素的迭代器

c.insert(p,n,t)     在迭代器 p 所指向的元素前面插入 n 个值为 t 的新元素。
            返回 void 类型

c.insert(p,b,e)     在迭代器 p 所指向的元素前面插入由迭代器 b 和 e 标记的范围内的元素。
            返回 void 类型

insert 操作则提供了一组更通用的插入方法,实现在容器的任意指定位置插入新元素。

insert 操作有 3 个版本:

第 1 个版本需要一个迭代器和一个元素值参数,迭代器指向插入新元素的位置。
下面的程序就是使用了这个版本的 insert 函数在容器首部插入新元素:

vector<string> svec;
list<string> slist;
string spouse("Beth");

// equivalent to calling slist.push_front (spouse);
slist.insert(slist.begin(), spouse);

// no push_front on vector but we can insert before begin()
// warning: inserting anywhere but at the end of a vector is an expensive operation
svec.insert(svec.begin(), spouse);

新元素是插入在迭代器指向的位置之前。
迭代器可以指向容器的任意位置,包括超出末端的下一位置。
由于迭代器可能指向超出容器末端的下一位置,这是一个不存在的元素,
因此 insert 函数是在其指向位置之前而非其后插入元素。
这个版本的 insert 函数返回指向新插入元素的迭代器。
可使用该返回值在容器中的指定位置重复插入元素:

list<string> lst;
list<string>::iterator iter = lst.begin();
while (cin >> word)
iter = lst.insert(iter, word); // same as calling push_front

要彻底地理解上述循环是如何执行的,这一点非常重要——
特别是要明白我们为什么说上述循环等效于调用 push_front 函数

第 2 个版本提供在指定位置插入指定数量的相同元素的功能:
svec.insert(svec.end(), 10, "Anna");

上述代码在容器 svec 的尾部插入 10 个元素,每个新元素都初始化为 "Anna"。

第 3 个版本实现在容器中插入由一对迭代器标记的一段范围内的元素。
例如,给出以下 string 类型的数组:
string sarray[4] = {"quasi", "simba", "frollo", "scar"};

可将该数组中所有的或其中一部分元素插入到 string 类型的 list 容器中:

// insert all the elements in sarray at end of slist
slist.insert(slist.end(), sarray, sarray+4);
list<string>::iterator slist_iter = slist.begin();
// insert last two elements of sarray before slist_iter
slist.insert(slist_iter, sarray+2, sarray+4);

 

特别注意,向容器中添加元素可能会使迭代器失效!

在 vector 容器中添加元素可能会导致整个容器的重新加载,这样的话,
该容器涉及的所有迭代器都会失效。
即使需要重新加载整个容器,指向新插入元素后面的那个元素的迭代器也会失效。

任何 insert 或 push 操作都可能导致迭代器失效。
当编写循环将元素插入到 vector 或 deque 容器中时,程序必须确保迭代器在每次循环后都得到更新。


避免存储 end 操作返回的迭代器
在 vector 或 deque 容器中添加元素时,可能会导致某些或全部迭代器失效。
假设所有迭代器失效是最安全的做法。这个建议特别适用于由 end 操作返回的迭代器。
在容器的任何位置插入任何元素都会使该迭代器失效。

例如,考虑一个读取容器中每个元素的循环,对读出元素做完处理后,在原始元素后面插入一个新元素。
我们希望该循环可以处理每个原始元素,然后使用 insert 函数插入新元素,并返回指向刚插入元素的迭代器。
在每次插入操作完成后,给返回的迭代器自增 1,以使循环定位在下一个要处理的原始元素。
如果我们尝试通过存储 end() 操作返回的迭代器来“优化”该循环,将导致灾难性错误:

vector<int>::iterator     first = v.begin(),
last = v.end(); // cache end iterator
// Diaster: behavior of this loop is undefined
while (first != last) {
  // do some processing
  // insert new value and reassign first, which otherwise would be invalid
  first = v.insert(first, 42);
  ++first; // advance first just past the element we added
}

上述代码的行为是未定义的。在很多实现中,该段代码将导致死循环。
问题在于这个程序将 end 操作返回的迭代器值存储在名为 last 的局部变量中。
循环体中实现了元素的添加运算,添加元素会使得存储在 last 中的迭代器失效。
该迭代器既没有指向容器 v 的元素,也不再指向 v 的超出末端的下一位置。


添加或删除 deque 或 vector 容器内的元素都会导致存储的迭代器失效。
所以,不要存储 end 操作返回的迭代器
为了避免存储 end 迭代器,可以在每次做完插入运算之后重新计算 end 迭代器值:

// Safer: recalculate end on each trip whenever the loop adds/erases elements
while (first != v.end()) {
  // do some processing
  first = v.insert(first, 42); // insert new value
  ++first; // advance first just past the element we added
}

 

 

posted @ 2013-05-28 16:14  HandsomeDragon  阅读(325)  评论(0编辑  收藏  举报