[关闭]
@Moritz 2019-01-31T07:37:15.000000Z 字数 25504 阅读 364

Chapter 9. Sequential Containers

C++primer C++ 课程学习 所有文稿


A container holds a collection of objects of a specified type. The sequential containers 顺序容器 let the programmer control the order in which the elements are stored and accessed. That order does not depend on the values of the elements. Instead, the order corresponds to the position at which elements are put into the container. By contrast, the ordered and unordered associative containers, which we cover in Chapter 11, store their elements based on the value of a key.

The library also provides three container adaptors 配适器, each of which adapts a container type by defining a different interface 接口 to the container’s operations. We cover the adaptors at the end of this chapter.


9.1 Overview of the Sequential Containers

The sequential containers all provide fast sequential access to their elements. However, these containers offer different performance tradeoffs 性能折中 relative to:

Sequential Container Types Description
vector Flexible-size array. Support fast random access 快速随机访问. Slow insert/delete other than back.
deque 双端队列Double-ended queue. Support fast random access. Fast insert/delete at front and back
list 双向链表Double linked list. Support only bidirectional sequential access 双向顺序访问. Fast insert/delete at any point.
forward_list 单向链表Singly linked list. Support only sequential access in one direction 单向顺序访问. Fast insert/delete at any point.
array Support fast random access. Cannot add or remove elements.
string Fast random access. Fast insert/delete at the back.

string and vector hold their elements in contiguous memory. Because elements are contiguous, it is fast to compute the address of an element from its index. However, adding or removing elements in the middle of one of these containers takes time:

The list and forward_list containers are designed to make it fast to add or remove an element anywhere in the container. An element can accessed only by iterating 遍历 through the container. The memory overhead 额外内存开销 for these containers is often substantial.

As with string and vector, adding or removing elements in the middle of a deque is a (potentially) expensive operation. However, adding or removing elements at either end of the deque is a fast operation, comparable to adding an element to a list or forward_list.

An array is a safer, easier-to-use alternative to built-in arrays.

A forward_list is intended to be comparable to the best handwritten, singly linked list. Consequently, forward_list does not have the size operation because storing or computing its size would entail overhead compared to a handwritten list. For the other containers, size is guaranteed to be a fast, constant-time operation 快速的常量时间的操作.

The new library containers are dramatically faster than in previous releases. Modern C++ programs should use the library containers rather than more primitive structures like arrays.

Deciding Which Sequential Container to Use

Tip
Ordinarily, it is best to use vector unless there is a good reason to prefer another container.

The details are a bit complicated. So I coped the whole paragraph.

Best Practices
If you’re not sure which container to use, write your code so that it uses only operations common to both vectors and lists: Use iterators, not subscripts, and avoid random access to elements. That way it will be easy to use either a vector or a list as necessary


9.2 Container Library Overview

In general, each container is defined in a header file with the same name as the type. That is, deque is in the deque header, list in the list header, and so on. The containers are class templates 容器均定义为模板类.

Constraints on Types That a Container Can Hold

Almost any type can be used as the element type of a sequential container. In particular, we can define a container whose element type is itself another container.

Although we can store almost any type in a container, some container operations impose requirements of their own on the element type 某些容器操作对元素类型有其自己的特殊要求.

The operations on the container types can be seen in tables in the book, which will not be listed here.

Container Operations

Type Aliases 类型别名
iterator Type of the iterator for this container type
const_iterator Iterator type that can read but not change its elements
size_type Unsigned integral type
difference_type Signed integral type big enough to hold the distance between two iterators
value_type Element type
reference Element's lvalue type 左值类型; synonym for value_type& 含义相同
const_reference
Construction 构造函数
C c; Default constructor 默认构造函数, empty container.
C c1(c2); Construct c1 as a copy of c2
C c(b,e); Copy elements from the range denoted by iterator b and e;
not valid for array
C c{a,b,c...}; List initialize c
Assignment and swap
c1=c2 Replace elements
c1={a,b,c...}; Replace elements; not valid for array
a.swap(b) Swap elements in a with b
swap(a,b) Equivalent to a.swap(b)
Size
c.size() Not valid for forward_list
c.max_size() Maximum number of elements c can hold
c.empty() false if c has any elements, true otherwise
Add/Remove Elements (not valid for array) Note: the interface 接口 for these operations varies by container type
c.insert(args) Copy element(s) as specified by args into c
c.emplace(inits) Use inits to construct an element in c (???)
c.erase(args) Remove element(s)
c.clear() Remove all elements from c; return void
Equality and Relational Operators
==,!= Valid for all container types
<,<=,>,>= Not valid for unordered associative containers 不支持无序关联容器
Obtain Iterators 获取迭代器
c.begin(), c.end() Return iterator to the first, one past the last element in c
c.cbegin(), c.cend() Return const_iterator
Additional Members of Reversible Containers 反向容器的额外成员 (Not valid for forward_list)
reverse_iterator
const_reverse_iterator
c.rbegin() c.rend() Return iterator to the lase, one past the first element in c
c.crbegin() c.crend()

9.2.1 Iterators

As with the containers, iterators have a common interface 公共接口: If an iterator provides an operation, then the operation is supported in the same way for each iterator that supplies that operation.

The container iterators support all the operations listed in table 3.6 in the book. One exception is that The forward_list iterators do not support the decrement -- operator. The iterator arithmetic operations listed in Table 3.7 apply only to iterators for string, vector, deque and array.

Iterator Ranges

The concept of an iterator range is fundamental to the standard library.

An **iterator range 迭代器范围**is denoted by a pair of iterators each of which refers to an element, or to one past the last element, in the same container. These two iterators, often referred to as begin and end—or (somewhat misleadingly) as first and last—mark a range of elements from the container.

The elements in the range include the element denoted by first and every element from first up to but not including last. This element range is called a left-inclusive interval 左闭合区间. The standard mathematical notation for such a range is [ begin, end).

Programming Implications 编程假定 of Using Left-Inclusive Ranges

The library uses left-inclusive ranges because such ranges have three convenient properties. Assuming begin and end denote a valid iterator range, then

These properties mean that we can safely write loops such as the following to process a range of elements.

9.2.2 Container Type Members 容器类型成员

size_type, iterator and const_iterator are three of container-defined types. Provided by most containers, a reverse iterator 反向迭代器 is an iterator that goes backward through a container and inverts the meaning of the iterator operations.

The remaining type aliases 类型别名 let us use the type of the elements stored in a container without knowing what that type is. If we need the element type, we refer to the container’s value_type. If we need a reference to that type, we use reference or const_reference. These element-related type aliases are most useful in generic programs 泛型编程. (I don't understand what this paragraph is talking about. Problems remain unsolved. -2019.1.26)

To use one of these types, we must name the class of which they are a member 显式使用其类型名:

  1. // iter is the iterator type defined by list<string>
  2. list<string>::iterator iter;
  3. // count is the difference_type type defined by vector<int>
  4. vector<int>::difference_type count;
  5. /*use the scope operator to say that we want the iterator member of the list<string> class and the difference_type defined by vector<int>*/

9.2.3 begin and end Members

The functions that do not begin with a c are overloaded 重载. That is, there are actually two members named begin. One is a const member that returns the container’s const_iterator type. The other is nonconst and returns the container’s iterator type. Similarly for rbegin, end and rend. As with pointers and references to const, we can convert a plain iterator to the corresponding const_iterator, but not vice versa 反之.

  1. // iterator or const_iterator depending on a's type of a
  2. auto it7 = a.begin(); // const_iterator only if a is const
  3. auto it8 = a.cbegin(); // it8 is const_iterator

Best Practices
When write access 写操作 is not needed, use cbegin and cend.

9.2.4 Defining and Initializing a Container

Every container type defines a default constructor (§ 7.1.4, p. 263). Haven't learned yet. -2019.1.27 With the exception of array, the default constructor creates an empty container of the specified type. Again excepting array, the other constructors take arguments that specify the size of the container and initial values for the elements. Don't understand.

Defining and Initializing Containers
C c; Default constructor. If C is array, then the elements in c are default-initialized 默认方式初始化; otherwise c is empty.
C c1(c2)
C c1=c2
Must have same type (in the case of array, must also have same size)
C c{a,b,c...}
C c={a,b,c...}
c is a copy of the elements in the initializer list. For array, the list must have same or fewer elements than the size, any missing elements are value-initialized 值初始化.
C c(b,e); Copy elements from the range denoted by iterator b and e;
not valid for array
(not including array) Constructors that take a size are valid for sequential containers only
C seq(n) seq has n value-initialized elements; this constructor is explicit. Not valid for string
C seq(n,t) seq has n elements with value t

Initializing a Container as a Copy of Another Container

To create a container as a copy of another container, the container and element types must match. When we pass iterators (denoting a range of elements to be copied), there is no requirement that the container types be identical. Moreover, the element types in the new and original containers can differ as long as it is possible to convert the elements we’re copying to the element type of the container we are initializing.

  1. // each container has three elements, initialized from the given initializers
  2. list<string> authors = {"Milton", "Shakespeare", "Austen"};
  3. vector<const char*> articles = {"a", "an", "the"};
  4. list<string> list2(authors); // ok: types match
  5. deque<string> authList(authors); // error: container types don't match
  6. vector<string> words(articles); // error: element types must match
  7. // ok: converts const char* elements to string
  8. forward_list<string> words(articles.begin(), articles.end());
  9. // copies up to but not including the element denoted by it
  10. deque<string> authList(authors.begin(), it);

List Initialization

Under the new standard C++11, we can list initialize 列表初始化a container. The container will have as many elements as there are initializers.

Sequential Container Size-Related Constructors 大小相关的构造函数

In addition to the constructors that sequential containers have in common with associative containers(??????), we can also initialize the sequential containers (other than array) from a size and an (optional) element initializer. If we do not supply an element initializer, the library creates a value-initialized one 值初始化器 for us.

  1. vector<int> ivec(10, -1); // ten int elements, each initialized to -1
  2. list<string> svec(10, "hi!"); // ten strings; each element is "hi!"
  3. forward_list<int> ivec(10); // ten elements, each initialized to 0
  4. deque<string> svec(10); // ten elements, each an empty string

We can use the constructor that takes a size argument if the element type is a built-in type or a class type that has a default constructor (§ 9.2, p. 329)???????. If the element type does not have a default constructor, then we must specify an explicit element initializer along with the size.

Note
The constructors that take a size are valid only for sequential containers; they are not supported for the associative containers

Library arrays Have Fixed Size

  1. array<int, 42> // type is: array that holds 42 ints
  2. array<string, 10> // type is: array that holds 10 strings
  3. /*To use an array type we must specify both the element type and the size*/
  4. array<int, 10>::size_type i; // array type includes element type and size
  5. array<int>::size_type j; // error: array<int> is not a type

The elements are default initialized just as are elements in a built-in array. When we list initialize an array, if there are fewer initializers than the size of the array, the initializers are used for the first elements and any remaining elements are value initialized 值初始化. In this cases, if the element type is a class type, the class must have a default constructor in order to permit value initialization:

  1. array<int, 10> ia1; // ten default-initialized ints
  2. array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9}; // list initialization
  3. array<int, 10> ia3 = {42}; // ia3[0] is 42, remaining elements are 0

Although we cannot copy or assign objects of built-in array types, there is no such restriction on array.

  1. int digs[10] = {0,1,2,3,4,5,6,7,8,9};
  2. int cpy[10] = digs; // error: no copy or assignment for built-in arrays
  3. array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};
  4. array<int, 10> copy = digits; // ok: so long as array types match

9.2.5 Assignment and swap

If the containers had been of unequal size, after the assignment both containers would have the size of the right-hand operand.

Assignment and Operations
c1=c2 Replace elements
c1={a,b,c...}; Replace elements; not valid for array
a.swap(b) Swap elements in a with b
swap(a,b) Equivalent to a.swap(b)
———— assign operation not valid for associative containers or array
seq.assign(b,e) Replace elements in seq with those in the range denoted by iterators b and e (b and e must not refer to eles in seq)
seq.assign(il) Replace eles in seq with those in the 初始化列表 initializer list il
seq.assign(n,t) Replace with n eles with value t

WARNING

Assignment related operations invalidate 无效化 iterators, references and pointers into the left-hand container. Aside from string they remain valid after a swap and (excepting arrays) the containers to which they refer are swapped. 将容器内容交换不会导致失效

Unlike built-in arrays, the library array type does allow assignment. The left-and right-hand operands must have the same type. Because the size of the right-hand operand might differ from the size of the left-hand operand, the array type does not support assign and it does not allow assignment from a braced list of values.

  1. array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9};
  2. array<int, 10> a2 = {0}; // elements all have value 0
  3. a1 = a2; // replaces elements in a1
  4. a2 = {0}; // error: cannot assign to an array from a braced list

Using assign (Sequential Containers Only)

The sequential containers (except array) also define a member named assign that lets us assign from a different but compatible type, or assign from a subsequence 子序列 of a container. The assign operation replaces all the elements in the left-hand container with (copies of) the elements specified by its arguments.

  1. list<string> names;
  2. vector<const char*> oldstyle;
  3. names = oldstyle; // error: container types don't match
  4. // ok: can convert from const char*to string
  5. names.assign(oldstyle.cbegin(), oldstyle.cend());

The arguments to assign determine how many elements and what values the container will have.

Warning
Because the existing elements are replaced, the iterators passed to assign must not refer to the container on which assign is called.

  1. // equivalent to slist1.clear();
  2. // followed by slist1.insert(slist1.begin(), 10, "Hiya!");
  3. list<string> slist1(1); // one element, which is the empty string
  4. slist1.assign(10, "Hiya!"); // ten elements; each one is Hiya !

Using swap

  1. vector<string> svec1(10); // vector with ten elements
  2. vector<string> svec2(24); // vector with 24 elements
  3. swap(svec1, svec2);

With the exception of arrays, swapping two containers is guaranteed to be fast—the elements themselves are not swapped; internal data structures are swapped. Excepting array, swap does not copy, delete, or insert any elements and is guaranteed to run in constant time. The fact that elements are not moved means that, with the exception of string, iterators, references, and pointers into the containers are not invalidated. They refer to the same elements as they did before the swap. However, after the swap, those elements are in a different contain. For example, had iter denoted the string at position svec1 [3] before the swap, it will denote the element at position svec2[3] after the swap. Differently from the containers, a call to swap on a string may invalidate iterators, references and pointers.

Unlike how swap behaves for the other containers, swapping two arrays does exchange the elements. As a result, swapping two arrays requires time proportional 正比 to the number of elements in the array. After the swap, pointers, references, and iterators remain bound to the same element they denoted before the swap. Of course, the value of that element has been swapped with the corresponding element in the other array.

9.2.6 Container Size Operations

With one exception, the container types have three size-related operations. The size member returns the number of elements in the container; empty returns a bool that is true if size is zero and false otherwise; and max_size returns a number that is greater than or equal to the number of elements a container of that type can contain. For reasons we’ll explain in the next section, forward_list provides max_size and empty, but not size.

9.2.7 Relational Operators

The right- and left-hand operands must be the same kind of container and must hold elements of the same type.

Comparing two containers performs a pairwise comparison of the elements. These operators work similarly to the string relationals :

Note
We can use a relational operator to compare two containers only if the appropriate comparison operator is defined for the element type.

For example, the Sales_data type that we defined in Chapter 7 does not define either the == or the < operation. Therefore, we cannot compare two containers that hold Sales_data elements.


9.3 Sequential Container Operations

The sequential and associative containers differ in how they organize their elements. These differences affect how elements are stored, accessed, added, and removed.

9.3.1 Adding Elements to a Sequential Container

Operations (That Add Elements to a Sequential Container)
c.push_back(t) Return void
c.emplace_back(args) Return void
c.push_front(t) Return void
c.emplace_front(args) Return void
c.emplace(p,args)
c.insert(p,t)
Create an element with the value t or constructed from args before the element denoted by the iterator p. Returns an iterator referring to the element that was added.
c.insert(p,n,t) Insert n elements with value t before p. Return an iterator to the first element inserted; if n is zero, return p.
c.insert(p,b,e) Range denoted by iterators b and e which may not refer to eles in c. Return the first element iterator; if the range is empty, return p.
c.insert(p,il) il is a braced 花括号 list of element values. Return first element iterator or p if list is empty.

These operations are not supported by array.

forward_list has special versions of insert and emplace. (9.3.4)

push_back and emplace_back are not valid for forward_list.

push_front and emplace_front are not valid for vector or string.

WARNING

Adding eles to a vector, string or deque potentially invalidate all existing iterators, references and pointers into the container.

The containers use different strategies for allocating elements and that these strategies affect performance. Adding elements anywhere but at the end of a vector or string, or anywhere but the
beginning or end of a deque, requires elements to be moved. Moreover, adding elements to a vector or a string may cause the entire object to be reallocated.

Using push_back

For string is a container of characters, we can use push_back to add characters to the end of the string.

  1. void pluralize(size_t cnt, string &word)
  2. {
  3. if (cnt > 1)
  4. word.push_back('s'); // same as word += 's'
  5. }

Key Concept: Container Elements Are Copies
When we use an object to initialize a container, or insert an object into a container, a copy of that object’s value is placed in the container, not the object itself. Just as when we pass an object to a nonreference parameter (§ 6.2.1, p. 209), there is no relationship between the element in the container and the object from which that value originated. Subsequent changes to the element in the container have no effect on the original object, and vice versa.

Using push_front

A deque guarantees constant-time insert and delete of elements at the beginning and end of the container. As withvector, inserting elements other than at the front or back of a deque is a potentially expensive operation.

Adding Elements at a Specified Point in the Container

Even though some containers do not have a push_front operation, there is no similar constraint on insert. We can insert elements at the beginning of a container without worrying about whether the container has push_front.

Warning
It is legal to insert anywhere in a vector, deque, or string. However, doing so can be an expensive operation.

Inserting a Range of Elements

When we pass a pair of iterators, those iterators may not refer to the same container as the one to which we are adding elements.

Under the new standard C++11, the versions of insert that take a count or a range return an iterator to the first element that was inserted. (In prior versions of the library, these operations returned void.) If the range is empty, no elements are inserted, and the operation returns its first parameter.

Using the Return from insert

We can use the value returned by insert to repeatedly insert elements at a specified
position in the container.

  1. list<string> 1st;
  2. auto iter = 1st.begin();
  3. while (cin >> word)
  4. iter = 1st.insert(iter, word); // same as calling push_front

Using the Emplace Operations
The new standard C++11 introduced three new members—emplace_front, emplace, and emplace_back—that construct rather than copy elements.

When we call an emplace member, we pass arguments to a constructor 构造函数 for the element type. The emplace members use those arguments to construct an element directly in space managed by the container.

  1. // construct a Sales_data object at the end of c
  2. // uses the three-argument Sales_data constructor
  3. c.emplace_back("978-0590353403", 25, 15.99);
  4. // error: there is no version of push_back that takes three arguments
  5. c.push_back("978-0590353403", 25, 15.99);
  6. // ok: we create a temporary Sales_data object to pass to push_back
  7. c.push_back(Sales_data("978-0590353403", 25, 15.99));

The call to emplace_back and the second call to push_back both create new Sales_data objects. In the call to emplace_back, that object is created directly in space managed by the container. The call to push_back creates a local temporary object that is pushed onto the container.

Note
The emplace functions construct elements in the container. The arguments to these functions must match a constructor for the element type.

9.3.2 Accessing Elements

Operations to Access Elements in a Sequential Container
c.back() Return a reference to the last element. Undefined if c is empty.Not valid for forward_list
c.front()
c[n] Undefined if n>=c.size().
Valid only for string, vector ,deque and array (provide fast random access)
c.at(n) If the index is out of range, throws an out_of_range exception.
Valid only for string, vector ,deque and array (provide fast random access)
  1. // check that there are elements before dereferencing an iterator or calling front or
  2. back
  3. if (!c.empty()) {...}

The Access Members Return References

If the container is a const object, the return is a reference to const. If the container is not const, the return is an ordinary reference that we can use to change the value of the fetched element.

  1. if (!c.empty()) {
  2. c.front() = 42; // assigns 42 to the first element in c
  3. auto &v = c.back(); // get a reference to the last element
  4. v = 1024; // changes the element in c
  5. auto v2 = c.back(); // v2 is not a reference; it's a copy of c.back()
  6. v2 = 0; // no change to the element in c
  7. }

As usual, if we use auto to store the return from one of these functions and we want to use that variable to change the element, we must remember to define our variable as a reference type.

Subscripting and Safe Random Access

  1. vector<string> svec; // empty vector
  2. cout << svec[0]; // run-time error: there are no elements in svec!
  3. cout << svec.at(0); // throws an out_of_range exception

9.3.3 Erasing Elements

erase Operations (on Sequential Containers)
c.pop_back() Undefined if empty. Return void.
c.pop_front() Undefined if empty. Return void.
c.erase(p) Removes the element denoted by iterator p and returns an iterator to the element after the one deleted or end. Undefined if p is off-the-end iterator.
c.erase(b,e) Removes range denoted by iterators b and e. Returns an iterator to the element after the last one deleted or end.
c.clear() Return void.

Not supported by array.

forward_list has a special version of erase and doesn't support pop_back.

pop_front not valid for vector and string.

Warning
The members that remove elements do not check their argument(s). The programmer must ensure that element(s) exist before removing them.

Removing eles anywhere but the beginning or end of a deque invalidates all iterators, references and pointers.

Iterators, references and pointers to eles after the erasure point in a vector or string are invalidated.

The pop_front and pop_back Members

Just as there is no push_front for vector and string, there is also no pop_front for those types. Similarly, forward_list does not have pop_back.

Removing an Element from within the Container

if j is the element following i, then erase(i) will return an iterator referring to j.

9.3.4 Specialized forward_list Operations

To add or remove an element, we need access to its predecessor 前驱 in order to update that element’s links. However, forward_list is a singly linked list. In a singly linked list there is no easy way to get to an element’s predecessor. For this reason, the operations to add or remove elements in a forward_list operate by changing the element after the given element. That way, we always have access to the elements that are affected by the change.

Operations to Insert or Remove Elements
lst.before_begin()
lst.cbefore_begin()
Iterator denoting the nonexistent element before the beginning which may not be dereferenced.
lst.insert_after(p,t)
lst.insert_after(p,n,t)
lst.insert_after(p,b,e)
lst.insert_after(p,il)
b and e denoting a range must not refer to lst. il is a braced 花括号 list.
Returns an iterator to the last inserted element. If the range is empty, return p. Undefined if p is the off-the-end iterator.
emplace_after(p,args) Uses args to construct an element. Return an iterator to the new element. Undefined if p is the off-the-end iterator.
lst.erase_after(p)
lst.erase_after(b,e)
Removes the element after the one denoted by p or the range of elements from the one after b up to but not including the one denoted by e.
Returns an iterator to the element after the one deleted, or off-the-end if there is no such element. Undefined if p denotes the last element in lst or is a off-the-end.

To support these operations, forward_list also defines before_begin, which returns an off-the-beginning 首前 iterator.

9.3.5 Resizing a Container

9.3.6 Container Operations May Invalidate Iterators


9.4 How a vector Grows


9.5 Additional string Operations

9.5.1 Other Ways to Construct strings

9.5.2 Other Ways to Change a string

9.5.3 string Search Operations

9.5.4 The compare Functions

9.5.5 Numeric Conversions


9.6 Container Adaptors


施工开始 -2019.1.26

暂停施工,进度已完成9.3.4 -2019.1.31

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注