本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:cpp_primer:9_containers [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:9_containers [2024/12/14 13:07] (当前版本) – [顺序容器的特性] codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======顺序容器====== | ||
+ | C++ Primer 笔记 第九章\\ | ||
+ | ---- | ||
+ | **容器**(// | ||
+ | * 元素按顺序添加进容器 | ||
+ | * 元素的添加顺序与其在容器里的位置一一对应 | ||
+ | ====顺序容器概述==== | ||
+ | 顺序容器主要按以下的两个方面来作出取舍: | ||
+ | - 添加/ | ||
+ | - 随机访问(非顺序访问)的花销 | ||
+ | ===顺序容器的特性=== | ||
+ | 根据不同存储的策略,不同的容器的特性也不同: | ||
+ | * Vector & String 支持浮动的大小,但添加删除容器中间的元素会很慢 | ||
+ | * list / forward_list 支持快速添加删除元素,但舍弃了快速的随机访问。Forward_list 没有求 size 的操作。 | ||
+ | * Deque 支持快速随机访问。添删首尾元素快,中间元素慢 | ||
+ | * Array 是新标准的容器,不支持添删和改变容器大小。相较于 Build-in Aarray 更安全 | ||
+ | {{ : | ||
+ | <WRAP center round tip 100%> | ||
+ | 新标准下的 STL 容器比旧版本下的快上许多,在可以使用的地方尽可能的使用。 | ||
+ | </ | ||
+ | |||
+ | ===顺序容器的选择=== | ||
+ | 通常,选择顺序容器遵循以下的策略: | ||
+ | - 如果没有特殊需要,**尽可能的选择** // | ||
+ | - 如果有很多**小元素**,而且**空间**的 overhead 很重要,那么请**不要**使用 //list// or // | ||
+ | - 如果容器要求大量的随机访问,使用 //vector// or // | ||
+ | - 如果需要经常在容器**中部**进行添删操作,请选择 //list// ro // | ||
+ | - 如果程序需要在容器**前后**进行添删操作,请选择 // | ||
+ | - 如果程序需要在输入数据的时候向容器**中部**插入数据,而后需要**随机访问**的话: | ||
+ | - 首先,看是否真的需要容器中部添加元素。如果而**只是**想队列**有序**,使用 //vector// + '' | ||
+ | - 其次,如果必须要在容器中间添加元素,可以考虑在用 //list// 做写入的载体,完成写入以后再把 //list// 拷贝到 //vector// 中。 | ||
+ | 总的来说,牵涉到复合要求的情况下,优先**按主要操作的需求**来选择容器。 | ||
+ | <WRAP center round tip 100%> | ||
+ | 如果你并不确定使用哪种容器,那么使用 // | ||
+ | </ | ||
+ | |||
+ | ====标准库容器概况==== | ||
+ | STL 提供了一些泛型的操作供容器使用。总的来说: | ||
+ | * 某些操作可以用到所有容器上。 | ||
+ | * 一些操作只能应用到顺序容器上,不能用于关联容器(// | ||
+ | * 还有一些操作只适用于一小部分容器。 | ||
+ | <WRAP center round help 100%> | ||
+ | 如果需要使用容器,只用包含名为容器名字的头文件就可以了。 | ||
+ | </ | ||
+ | |||
+ | 操作的详细分类如下表: | ||
+ | \\ \\ {{ : | ||
+ | {{ : | ||
+ | 总的来说,**容器都是类模板**(// | ||
+ | <code cpp> | ||
+ | list< | ||
+ | deque< | ||
+ | </ | ||
+ | ===容器对保存类型的限制=== | ||
+ | 总的来说,基本所有的类型的对象都可以被顺序容器保存,容器的元素甚至可以是容器: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | 但某些容器的操作会受到元素类型的限制。比如依靠默认初始化的初始化方法不能用于没有默认初始化的对象: | ||
+ | <code cpp> | ||
+ | // assume noDefault is a type without a default constructor | ||
+ | vector< | ||
+ | vector< | ||
+ | </ | ||
+ | |||
+ | ===迭代器=== | ||
+ | * 当应用于容器的时候,迭代器的操作是通用的 | ||
+ | * 例外是 //Forward list//, 不能使用**自减操作** | ||
+ | * 以下的迭代器运算只能用于 // | ||
+ | * '' | ||
+ | * '' | ||
+ | * ''>, | ||
+ | |||
+ | ==迭代器的范围== | ||
+ | 迭代器的范围使用一对迭代器来表示的;'' | ||
+ | 两点要求: | ||
+ | * 这两个迭代器必须指向同一个容器 | ||
+ | * '' | ||
+ | ==一些使用上述概念的假定== | ||
+ | * begin = end:容器为空 | ||
+ | * begin != end:容器不为空,begin 指向第一个元素 | ||
+ | * begin 可以通过自增最后与 end 相等。 | ||
+ | 遵循上述假定而写出来的循环是安全的: | ||
+ | <code cpp> | ||
+ | while (begin != end) { | ||
+ | *begin = val; | ||
+ | ++begin; | ||
+ | } | ||
+ | </ | ||
+ | ==使用容器类型成员== | ||
+ | 前面介绍过几种容器的成员类型,其中: | ||
+ | * 大多数 iterator 都有自己的 reverse iterator | ||
+ | * 这些类型实际上都是 type alias | ||
+ | 如果要显式的指定容器的成员类型: | ||
+ | <code cpp> | ||
+ | container type< | ||
+ | //e.g. | ||
+ | vector< | ||
+ | </ | ||
+ | ''::'' | ||
+ | ===容器成员 begin & end=== | ||
+ | 容器提供了两个成员函数 begin / end ,用于建立迭代的范围。同时提供的还有他们的 const / reverse 版本: | ||
+ | * c 开头的是 const 版本,比如 // | ||
+ | * r 开头的是 reverse 版本, 比如 // | ||
+ | 没有以 c 开头的版本均存在**函数重载**: | ||
+ | * 一个版本接收 const 对象,返回 // | ||
+ | * 另外一个版本接收 non-const 对象,返回 // | ||
+ | C++ 11 中提供了 auto 关键字,允许编译器对迭代器的类型进行推断: | ||
+ | <code cpp> | ||
+ | auto it7 = a.begin(); // const_iterator only if a is const | ||
+ | auto it8 = a.cbegin(); // it8 is const_iterator | ||
+ | </ | ||
+ | |||
+ | ===初始化 / 定义容器=== | ||
+ | 除 array 以外,基本上所有的容器都拥有两个构造函数: | ||
+ | * 默认的构造函数,创建空的容器 | ||
+ | * 带 size / 初始值的构造函数 | ||
+ | |||
+ | ==容器的拷贝初始化== | ||
+ | 容器的拷贝初始化分为两种: | ||
+ | * **直接拷贝**:要求容器与元素的**类型必须一致** | ||
+ | * **迭代器拷贝**:对容器的类型没有要求。允许使用存在隐式转换的元素进行初始化 | ||
+ | <code cpp> | ||
+ | // each container has three elements, initialized from the given initializers | ||
+ | list< | ||
+ | vector< | ||
+ | |||
+ | list< | ||
+ | deque< | ||
+ | vector< | ||
+ | |||
+ | // ok: converts const char* elements to string | ||
+ | forward_list< | ||
+ | </ | ||
+ | 鉴于迭代器初始化是通过指定范围实现的,使用迭代器拷贝可以初始化源容器的子集: | ||
+ | <code cpp> | ||
+ | // copies up to but not including the element denoted by it | ||
+ | deque< | ||
+ | </ | ||
+ | ==容器的列表初始化== | ||
+ | C++ 11 的新标准允许列表初始化容器: | ||
+ | <code cpp> | ||
+ | list< | ||
+ | </ | ||
+ | 该方法在显式指定元素的同时,也隐含了容器的 size 信息。 | ||
+ | ==指定大小的初始化== | ||
+ | **顺序容器** 可以通过指定 size 和 默认值 来进行初始化。初始化会得到与 size 相同数量的元素,每个元素的值都是初始值: | ||
+ | <code cpp> | ||
+ | list< | ||
+ | </ | ||
+ | 如果不指定初始值,则初始值会进行**值初始化**: | ||
+ | <code cpp> | ||
+ | forward_list< | ||
+ | deque< | ||
+ | </ | ||
+ | 需要注意的是,这种初始方法要求元素是 build-in tpye,或者拥有**默认构造函数**。 | ||
+ | <WRAP center round info 100%> | ||
+ | 该初始化方法**只**适用于**顺序容器**,不支持关系容器。 | ||
+ | </ | ||
+ | |||
+ | ==标准库 array 类型== | ||
+ | 和 build-in array 相同,STL array 也是 // | ||
+ | <code cpp > | ||
+ | array< | ||
+ | array< | ||
+ | </ | ||
+ | <color # | ||
+ | 因为大小是 //array// 类型的一部分。指定 size 的容器构造函数会隐式或者显式的决定容器的大小;允许用户决定 array 的大小可能会有很多潜在的问题。\\ \\ | ||
+ | |||
+ | < | ||
+ | //array// 的固定大小导致其初始化会受到一些限制: | ||
+ | * //array// 的默认初始化并不会创造一个空的容器。 | ||
+ | * 默认初始化中,元素个数与 array 的 size 相同,所有元素都将进行**默认初始化**。 | ||
+ | * 如果初始化元素个数小于 array size, 那没有对应初始值的元素会进行**值初始化**。 | ||
+ | <code cpp> | ||
+ | array< | ||
+ | array< | ||
+ | array< | ||
+ | </ | ||
+ | |||
+ | <color # | ||
+ | STL //array// 可以进行**直接拷贝和赋值**,只要 //size// 和 //type// 都匹配即可: | ||
+ | <code cpp> | ||
+ | int digs[10] = {0, | ||
+ | int cpy[10] = digs; // error: no copy or assignment for built-in arrays | ||
+ | array< | ||
+ | array< | ||
+ | </ | ||
+ | ===赋值与 swap=== | ||
+ | 容器的赋值分为三个类型的操作。具体操作可以参考下图: | ||
+ | \\ \\ {{ : | ||
+ | ==使用 assign operator 赋值== | ||
+ | 赋值操作符 '' | ||
+ | < | ||
+ | c1 = c2; // replace the contents of c1 with a copy of the elements in c2 | ||
+ | c1 = {a,b,c}; // after the assignment c1 has size 3 | ||
+ | </ | ||
+ | 使用 '' | ||
+ | <code cpp> | ||
+ | array< | ||
+ | array< | ||
+ | a1 = a2; // replaces elements in a1 | ||
+ | a2 = {0}; // error: cannot assign to an array from a braced list | ||
+ | </ | ||
+ | ==使用 assign 函数赋值== | ||
+ | 该种方法是 '' | ||
+ | <code cpp> | ||
+ | list< | ||
+ | slist1.assign(10, | ||
+ | </ | ||
+ | ==使用 swap 函数赋值== | ||
+ | swap 函数用于**交换**(// | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | vector< | ||
+ | //now svec1 has size 24, svc2 has size 10. | ||
+ | elementsswap(svec1, | ||
+ | </ | ||
+ | < | ||
+ | //swap// 要求进行交换的两个容器必须**类型相同**。\\ \\ | ||
+ | < | ||
+ | swap **对容器内的内容进行了交换**。内部的迭代器、引用和指针等等均指向原来的**元素**: | ||
+ | \\ \\ {{ : | ||
+ | < | ||
+ | - 在使用 swap 交换 //string// 的时候可能会使 迭代器、引用或者指针无效。 | ||
+ | - 在使用 sawp 交换 //array// 的时候,规则有些不同: | ||
+ | - 与其他 contianer 不同,交换 array 同时**也交换了他们的元素**;因此交换 array 所需时间与其元素数量成正比。 | ||
+ | - 这些元素对应的指针、应用或迭代器**不会发生交换**。他们依然指向原来的位置,只是内容已经发生了变化。 | ||
+ | <WRAP center round help 100%> | ||
+ | Swap 没有像 copy assignment 一样复制、删除或者插入任何元素。因此,swap 的执行速度非常快(可以在**常数时间**完成)。 | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | swap 的成员函数形式 a.swap(b) 是 C++11 的新标准。为了向前兼容,请尽量使用非成员版本的 swap。 | ||
+ | </ | ||
+ | |||
+ | ===容器大小操作=== | ||
+ | 除了一个例外,每个容器都有三个与大小相关的操作: | ||
+ | <code cpp linenums: | ||
+ | c.size(); // return the number of elements in container | ||
+ | c.empty(); // return true if there are no elements in container | ||
+ | c.max_size(); | ||
+ | </ | ||
+ | 需要注意的是,// | ||
+ | ===关系运算符=== | ||
+ | 所有容器类型都支持 '' | ||
+ | * 比较的前提: | ||
+ | * 如果使用关系运算符,容器的元素必须**定义**了相应的**关系运算符**。 | ||
+ | * **容器**必须是**相同类型**,**元素**也必须是相同类型。 | ||
+ | 如果满足比较的条件,则通过以下的策略进行比较: | ||
+ | * **相等性**:如果容器大小相等,且对应元素一一相等,那么两个容器相等,否则不等。 | ||
+ | * **大小性**: | ||
+ | * 如果容器 '' | ||
+ | * 否则,'' | ||
+ | ====顺序容器的操作==== | ||
+ | |||
+ | ===容器相关操作总览=== | ||
+ | 除了 array, 所有的 STL 容器都提供了 Run-time 期间的容器元素操作机制。当然,根据容器的实现方式不同,不同的操作在不同的容器上的效率也不同: | ||
+ | \\ \\ {{ : | ||
+ | <WRAP center round help 100%> | ||
+ | 需要注意的是,使用对象**初始化**容器、**添加**、**插入对象**到容器这些操作,都是**拷贝操作**。实际对容器产生影响的不是原来的对象,而是它的拷贝。因此,之后对容器的任何操作**不会影响**原本的对象。 | ||
+ | </ | ||
+ | |||
+ | ===向容器首尾添加元素=== | ||
+ | ==使用 push_back()== | ||
+ | *使用限制:不能应用于 '' | ||
+ | *使用方法: | ||
+ | <code cpp> | ||
+ | string word; | ||
+ | while (cin >> word) | ||
+ | container.push_back(word) | ||
+ | </ | ||
+ | ==使用 push_front()== | ||
+ | * 使用限制:'' | ||
+ | * 使用方法: | ||
+ | <code cpp> | ||
+ | list< | ||
+ | // add elements to the start of ilist | ||
+ | for (size_t ix = 0; ix != 4; ++ix) | ||
+ | ilist.push_front(ix); | ||
+ | </ | ||
+ | 由于 // | ||
+ | |||
+ | ===插入元素=== | ||
+ | ==对指定位置使用 insert()== | ||
+ | 对容器指定位置添加元素可以使用 //insert// 成员。 //insert// 操作适合除了 // | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | 一般的使用方式: | ||
+ | <code cpp> | ||
+ | slist.insert(iter, | ||
+ | </ | ||
+ | 值得注意的是,// | ||
+ | ==使用 insert() 插入范围内的元素== | ||
+ | insert() 有两个重载版本提供了插入范围内元素的功能:\\ \\ | ||
+ | < | ||
+ | <code cpp> | ||
+ | container.insert(p, | ||
+ | </ | ||
+ | 这个版本往迭代器 '' | ||
+ | <code cpp> | ||
+ | svec.insert(svec.end(), | ||
+ | </ | ||
+ | < | ||
+ | 第二个版本接收**一对表示范围的迭代器**,或者一个初始化列表,并将其插入到迭代器指定的位置之后: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | // insert the last two elements of v at the beginning of slist | ||
+ | slist.insert(slist.begin(), | ||
+ | slist.insert(slist.end(), | ||
+ | </ | ||
+ | 需要注意的是,传入的,表示范围的一对迭代器**不能指向正在被添加元素的容器**: | ||
+ | <code cpp> | ||
+ | // run-time error: iterators denoting the range to copy from | ||
+ | // must not refer to the same container as the one we are changing | ||
+ | slist.insert(slist.begin(), | ||
+ | </ | ||
+ | ==利用 insert() 范围版本的返回值== | ||
+ | C++ 11 中, // | ||
+ | <code cpp> | ||
+ | list< | ||
+ | auto iter = 1st.begin(); | ||
+ | while (cin >> word) | ||
+ | iter = 1st.insert(iter, | ||
+ | </ | ||
+ | 每次插入操作完成后,都会得到一个迭代器指向新元素所在的位置。下次循环使用该迭代器作为新的初始位置;由于 // | ||
+ | |||
+ | ==使用 emplace 进行操作== | ||
+ | C++ 11 中提供了新的 emplace 系列成员来进行元素的插入。与之前提到的插入操作不同,emplace 使用**构造的方法**在容器中**直接**产生新的元素。由于这样的方式,emplace 的大部分 parameter 取决于**当前容器元素的构造函数**需要什么样 parameter。也就是说,emplace 实际上是在**容器内指定的位置调用构造函数**。\\ \\ | ||
+ | emplace 成员有三个版本: | ||
+ | * emplace(iter, | ||
+ | * emplace_back(arg) | ||
+ | * emplace_front(arg) | ||
+ | 一些例子: | ||
+ | <code cpp> | ||
+ | c.emplace_back(); | ||
+ | c.emplace(iter, | ||
+ | c.emplace_front(" | ||
+ | </ | ||
+ | ===访问元素=== | ||
+ | C++ 提供了四种访问顺序容器元素的操作: | ||
+ | \\ \\ {{ : | ||
+ | 使用 //back()// 、 //front()// 成员的之前应该确保容器是非空的: | ||
+ | <code cpp> | ||
+ | // check that there are elements before dereferencing an iterator or calling front or back | ||
+ | if (!c.empty()) { | ||
+ | // val and val2 are copies of the value of the first element in c | ||
+ | auto val = *c.begin(), val2 = c.front(); | ||
+ | // val3 and val4 are copies of the of the last element in c | ||
+ | auto last = c.end(); | ||
+ | auto val3 = *(--last); // can't decrement forward_list iterators | ||
+ | auto val4 = c.back(); // not supported by forward_list | ||
+ | } | ||
+ | </ | ||
+ | ==访问元素的成员返回引用== | ||
+ | 上述的访问操作均返回绑定指定元素的**引用**。引用的 const 根据**容器**是否是 const 决定。\\ \\ | ||
+ | 除此之外,如果希望得到正确的返回值(引用),需要**显式的定义引用**(包括 auto): | ||
+ | <code cpp> | ||
+ | if (!c.empty()) { | ||
+ | c.front() = 42; // assigns 42 to the first element in c | ||
+ | auto &v = c.back(); // get a reference to the last element | ||
+ | v = 1024; // changes the element in c | ||
+ | auto v2 = c.back(); // v2 is not a reference; it's a copy of c.back() | ||
+ | v2 = 0; // no change to the element in c | ||
+ | } | ||
+ | </ | ||
+ | 如果没有显式的应用,那么实际完成的是拷贝赋值,得到的是引用绑定元素的**副本**。 | ||
+ | ==下标访问与安全的随机访问== | ||
+ | 另外两种访问方式是使用下标访问的方式。这两种方式都要求 index 必须在容器的范围内。但通常,使用下标操作符的方式不会检测 Index 是否在范围内。\\ \\ | ||
+ | 如果希望确保 index 是有效的,使用 //at()// 成员函数。当 index 无效时,该函数会抛出 // | ||
+ | ===删除元素=== | ||
+ | C++ 也提供了几种方法用于删除容器内删除元素(不适用于 array): | ||
+ | \\ \\ | ||
+ | {{ : | ||
+ | \\ | ||
+ | <WRAP center round important 100%> | ||
+ | 这些成员不会检测其 arugments,删除前必须**确保被删除的元素存在**。 | ||
+ | </ | ||
+ | ==pop_front() / pop_back()== | ||
+ | // | ||
+ | * 类似 // | ||
+ | * 类似 // | ||
+ | * 这两个成员返回 void。如果需要保存被删除的元素,需要在删除之前存储: | ||
+ | <code cpp> | ||
+ | while (!ilist.empty()) { | ||
+ | process(ilist.front()); | ||
+ | ilist.pop_front(); | ||
+ | } | ||
+ | </ | ||
+ | ==删除容器中指定的元素== | ||
+ | 使用 // | ||
+ | \\ \\ < | ||
+ | 一个简单的应用 //erase()// 返回值的实例: | ||
+ | <code cpp> | ||
+ | list< | ||
+ | auto it = lst.begin(); | ||
+ | while (it != lst.end()) | ||
+ | if (*it % 2) // if the element is odd | ||
+ | it = lst.erase(it); | ||
+ | else | ||
+ | ++it; | ||
+ | </ | ||
+ | ==删除容器中多个元素== | ||
+ | 接收双迭代器版本的 //erase(b, e)// 允许我们删除范围内的元素: | ||
+ | <code cpp> | ||
+ | // delete the range of elements between two iterators | ||
+ | // returns an iterator to the element just after the last removed element | ||
+ | elem1 = slist.erase(elem1, | ||
+ | </ | ||
+ | 指定的区间依然是左闭右开的区间,因此这里的 elem2 指向的元素不会被删除。\\ \\ | ||
+ | 如果需要清空所有元素,可以使用 //clear()// | ||
+ | <code cpp> | ||
+ | slist.clear(); | ||
+ | </ | ||
+ | 等同于: | ||
+ | <code cpp> | ||
+ | slist.erase(slist.begin(), | ||
+ | </ | ||
+ | ===forward_list 的特殊操作=== | ||
+ | < | ||
+ | 之所以这样做事因为 // | ||
+ | \\ \\ < | ||
+ | 由于其单向性,如果按照先断开链接,再添加或删除元素的顺序来操作是行不通的。断开链接后,被添删的元素之前的节点的指向信息无法更新,不知道新的节点在哪里: | ||
+ | \\ \\ < | ||
+ | 因此,我们必须通过**先建立链接,再添加删除元素**的方式,来确保被操作元素之前的节点能够正确的指向新的节点: | ||
+ | \\ \\ < | ||
+ | |||
+ | 基于上面的原因, forward_list 没有通用的 // | ||
+ | \\ \\ < | ||
+ | 可以看出来的是,如果需要添加 / 删除某一个元素,我们必须先获取其前置节点的位置。因此,上面所有的成员函数,操作的都是**迭代器 parameter '' | ||
+ | |||
+ | 一些注意事项: | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | |||
+ | 由于所有的 forward_list 成员函数都需要指向被操作元素之前位置的迭代器,因此在实际应用中需要同时维护两个迭代器: | ||
+ | <code cpp> | ||
+ | forward_list< | ||
+ | auto prev = flst.before_begin(); | ||
+ | auto curr = flst.begin(); | ||
+ | while (curr != flst.end()) { // while there are still elements to process | ||
+ | if (*curr % 2) // if the element is odd | ||
+ | curr = flst.erase_after(prev); | ||
+ | else { | ||
+ | prev = curr; // move the iterators to denote the next | ||
+ | ++curr; // element and one before the next element | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | 两个迭代器,前置的用于操作当前的元素,后置的用于循环和判断。 | ||
+ | ===改变容器大小=== | ||
+ | C++中给我们提供一种改变容器大小的成员函数:'' | ||
+ | <code cpp> | ||
+ | c.reszie(n); | ||
+ | c.resize(n, | ||
+ | </ | ||
+ | 当 //n// 与容器大小不等时: | ||
+ | - 如果 //n// < // | ||
+ | - 如果 //n// > // | ||
+ | 注意:对容器的 //resize// 操作也将导致**被删除元素**对应的迭代器、引用和指针失效。对 // | ||
+ | ===容器与迭代器的失效=== | ||
+ | 总结一下容器操作对迭代器、指针和引用产生的影响: | ||
+ | * **添加**元素后: | ||
+ | * 容器是 //Vector & String//: | ||
+ | * 存储空间被**重新分配**:**所有**指向容器的指针,引用,迭代器均失效。 | ||
+ | * 存储空间没有被重新分配:**插入位置之前**的指针,引用,迭代器不失效。 | ||
+ | * 容器是 // | ||
+ | * 任何插入到**不是首尾位置**的元素都会导致所有迭代器,指针,引用失效。 | ||
+ | * 在首尾添加元素,则**只有迭代器**失效。 | ||
+ | * 容器是 //List & Forward_List// | ||
+ | \\ < | ||
+ | * 删除元素后: | ||
+ | * 指向被删除元素的所有指针,引用,迭代器都将失效。 | ||
+ | * 容器是 //Vector & String// | ||
+ | * 容器是 // | ||
+ | * 如果是除首尾之外删除任何元素,那么所有指针,引用,迭代器都会失效。 | ||
+ | * 如果删除尾部元素,尾部迭代器失效。 | ||
+ | * 容器是 //List & Forward_List// | ||
+ | \\ < | ||
+ | < | ||
+ | * 最小化影响范围:尽量减少需要迭代器必须有效的判定。 | ||
+ | * 及时更新迭代器:使用某些受改变容器大小影响非常大的容器时(比如 // | ||
+ | ===循环中迭代器的维护=== | ||
+ | < | ||
+ | // | ||
+ | \\ \\ < | ||
+ | |||
+ | < | ||
+ | // | ||
+ | \\ \\ < | ||
+ | | ||
+ | <code cpp> | ||
+ | p = insert(p, N); | ||
+ | p += 2; | ||
+ | </ | ||
+ | < | ||
+ | 该点在对 // | ||
+ | <code cpp> | ||
+ | // disaster: the behavior of this loop is undefined | ||
+ | auto begin = v.begin(), | ||
+ | end = v.end(); // bad idea, saving the value of the end iterator | ||
+ | while (begin != end) { | ||
+ | // do some processing | ||
+ | // insert the new value and reassign begin, which otherwise would be invalid | ||
+ | ++begin; // advance begin because we want to insert after this element | ||
+ | begin = v.insert(begin, | ||
+ | ++begin; // advance begin past the element we just added | ||
+ | } | ||
+ | </ | ||
+ | 当 // | ||
+ | <code cpp> | ||
+ | // safer: recalculate end on each trip whenever the loop adds/erases elements | ||
+ | while (begin != v.end()) { | ||
+ | // do some processing | ||
+ | ++begin; // advance begin because we want to insert after this element | ||
+ | begin = v.insert(begin, | ||
+ | ++begin; // advance begin past the element we just added | ||
+ | } | ||
+ | </ | ||
+ | ====Vector是如何增长的==== | ||
+ | //Vector// 是一种大小可变的容器。当自身元素超过容器大小时,// | ||
+ | 但这个过程有一个问题: // | ||
+ | 理论上每一次插入操作都会使 //Vector// 进行 // | ||
+ | 为了尽量减少上述的开销,// | ||
+ | ===管理容量的成员函数==== | ||
+ | C++ 为 '' | ||
+ | <code cpp> | ||
+ | /* only for vector、string and dqeue */ | ||
+ | c.shrink_to_fit(); | ||
+ | |||
+ | /* only for vector and string */ | ||
+ | c.capacity(); | ||
+ | c.reserve(n); | ||
+ | </ | ||
+ | 有几点要注意的是: | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | <WRAP center round info 100%> | ||
+ | // | ||
+ | </ | ||
+ | |||
+ | ===capacity 和 size=== | ||
+ | < | ||
+ | * // | ||
+ | * //size// 则代表了容器现有元素的数量 | ||
+ | < | ||
+ | //Vector// 的 // | ||
+ | \\ \\ < | ||
+ | 通过这样的策略,使用 // | ||
+ | ====额外的 String 操作==== | ||
+ | 除了一些容器通用的函数以外,// | ||
+ | ===string 的创建=== | ||
+ | //string// 支持三种专属的初始化方式: | ||
+ | \\ \\ < | ||
+ | ==使用字符串进行初始化时的注意事项== | ||
+ | * 使用 //const char*// 对 // | ||
+ | * //const char*// 字符串不带 ''/ | ||
+ | * //const char*// 字符串不带 ''/ | ||
+ | * 使用 // | ||
+ | <code cpp> | ||
+ | const char *cp = "Hello World!!!"; | ||
+ | char noNull[] = {' | ||
+ | string s1(cp); | ||
+ | string s2(noNull, | ||
+ | string s3(noNull); | ||
+ | string s4(cp + 6, 5);// copy 5 characters starting at cp[6]; s4 == " | ||
+ | string s5(s1, 6, 5); // copy 5 characters starting at s1[6]; s5 == " | ||
+ | string s6(s1, 6); // copy from s1 [6] to end of s1; s6 == " | ||
+ | string s7(s1, | ||
+ | string s8(s1, 16); // throws an out_of_range exception | ||
+ | </ | ||
+ | ==使用 sutstr() 构造子字符串== | ||
+ | 通过 // | ||
+ | <code cpp> | ||
+ | //Return a string contains n characters from a staring at pos. | ||
+ | //pos default to 0 | ||
+ | //n defaults to a value that cause the library to copy all the characters in a starting from pos. | ||
+ | s.substr(pos, | ||
+ | </ | ||
+ | 如果 '' | ||
+ | ===string 的修改=== | ||
+ | //string// 类同时也支持一些额外的修改操作。某些操作还支持不同的重载版本: | ||
+ | \\ \\ < | ||
+ | 几个比较重要的概念: | ||
+ | * 牵涉到插入和替换的操作,range 分为 [index + length] 和 [iter.begin, | ||
+ | * 某些成员支持对 c-style string 进行操作。这些操作通过指向对应C 字符串的 '' | ||
+ | * argument 形式中可能会出现两个字符串:被修改的 s 和 用于 str: | ||
+ | * str 与 s 不能是同一个 string | ||
+ | * 如果是迭代器 (b,e) 形式的 args,b,e 不能指向 s | ||
+ | ==append() & replace()== | ||
+ | string 额外拥有两个成员:// | ||
+ | <code cpp> | ||
+ | string s("C++ Primer" | ||
+ | s.insert(s.size(), | ||
+ | s2.append(" | ||
+ | |||
+ | s.erase(11, 3); //s == "C++ Primer Ed." | ||
+ | s.insert(11, | ||
+ | s2.replace(11, | ||
+ | </ | ||
+ | ==用于修改 string 的多种重载形式== | ||
+ | 尽管 // | ||
+ | * assgin & append **不需要指定** 目标 string 需要被修改的 range,因为 assign 是对 string 做整体替换,而 append 总是将内容全加到 string 的末尾。 | ||
+ | * replace 与 insert 都通过**两种** range 形式来指定替换区域和插入位置。 insert 的插入位置总是在 range **之前**。 | ||
+ | * 往目标 string 中添加字符的来源有多种:另一个 string, c-string(的指针),初始化列表,或者指定个数的同一个字符。来源是 string / c-string 的时候,会提供额外的控制参数。 | ||
+ | * 不是所有的 args 都是通用的。 | ||
+ | ===string 的搜索=== | ||
+ | //String// 类提供了六种函数用于搜索 string 或者 字符。当这些函数找不到匹配结果的时候,会返回一个 //static// 的成员 '' | ||
+ | \\ | ||
+ | < | ||
+ | 所有上述函数返回的都是位置(index,'' | ||
+ | \\ | ||
+ | \\ | ||
+ | ==搜寻不是数字的字符== | ||
+ | 通过创建字符白名单,再利用 // | ||
+ | <code cpp> | ||
+ | string numbers(" | ||
+ | string dept(" | ||
+ | // returns 5, which is the index to the character ' | ||
+ | auto pos = dept.find_first_not_of(numbers); | ||
+ | </ | ||
+ | ==利用 find 返回的位置指定搜索点== | ||
+ | //find// 系列函数会返回对应的 index. 一个应用是利用该 index 与 '' | ||
+ | <code cpp> | ||
+ | string:: | ||
+ | //refresh and matching number at current position, if match then move to the next loop | ||
+ | while ((pos = name.find_first_of(number, | ||
+ | cout << | ||
+ | ++pos; //move to the next character | ||
+ | } | ||
+ | </ | ||
+ | 这里利用了 | ||
+ | <code cpp> | ||
+ | pos = name.find_first_of(number, | ||
+ | </ | ||
+ | 这个表达式来刷新当前的 index. 该 index 是基于之前的位置刷新的,即找到匹配的字符以后,就把该字符位置作为当前的起始点,接着向下查找。需要注意 '' | ||
+ | ==从右向左查找== | ||
+ | //find// 系列函数同时提供了从右向左查找的选项: | ||
+ | <code cpp> | ||
+ | string river(" | ||
+ | auto first_pos = river.find(" | ||
+ | auto last_pos = river.rfind(" | ||
+ | </ | ||
+ | 这里 //rfind// 找到的是右边第一个匹配的 '' | ||
+ | * // | ||
+ | * // | ||
+ | ===string 的比较=== | ||
+ | //compare// 函数和 C 语言的 //strcmp// 很像,用于比较 string 的大小:如果相等返回 '' | ||
+ | 一些常用的参数组合: | ||
+ | \\ \\ < | ||
+ | ===String 的数字转换=== | ||
+ | String 的中往往会出现数字,但这些数字存储的方式是以 //Latin-1// 编码的字符来存储的,比如字符串 '' | ||
+ | <code cpp> | ||
+ | 00110001 00110101 // string " | ||
+ | </ | ||
+ | 而对应的数值 '' | ||
+ | <code cpp> | ||
+ | 00000000 00001111 //int 15 | ||
+ | </ | ||
+ | C++11中,string 类提供了对应的成员函数,使得这两者可以相互转换: | ||
+ | \\ \\ < | ||
+ | 这些成员函数分为三类: | ||
+ | * 从数值转换为 //string// | ||
+ | * //string// 转换为整型 | ||
+ | * //string// 转换为浮点型 | ||
+ | <code cpp> | ||
+ | string s2 = "pi = 3.14"; | ||
+ | // convert the first substring in s that starts with a digit, | ||
+ | d = stod(s2.substr(s2.find_first_of(" | ||
+ | </ | ||
+ | 上面的例子中,'' | ||
+ | 可以转换成数字的 string 需要满足以下要求: | ||
+ | * string 首个 non-whitespece 的字符必须是正负符号('' | ||
+ | * 如果 '' | ||
+ | * 如果是浮点数,可以以 '' | ||
+ | 如果 string **不能转换为一个数值**, 则会抛出 // | ||
+ | ====容器适配器==== | ||
+ | **适配器**(// | ||
+ | **容器适配器**(// | ||
+ | \\ \\ < | ||
+ | |||
+ | ==容器适配器的定义== | ||
+ | 容器适配器拥有两个构造函数: | ||
+ | * 默认构造函数,创造空的对象 | ||
+ | * 初始化是适配器的构造函数,接收一个容器,将其以复制的形式初始化适配器: | ||
+ | 比如定义一个基于 '' | ||
+ | <code cpp> | ||
+ | stack< | ||
+ | </ | ||
+ | 不仅如此,在定义适配器的时候可以指定其使用的容器: | ||
+ | <code cpp> | ||
+ | // empty stack implemented on top of vector | ||
+ | stack< | ||
+ | // str_stk2 is implemented on top of vector and initially holds a copy of svec | ||
+ | stack< | ||
+ | </ | ||
+ | |||
+ | 当然,每种适配器必须选择合适的容器来进行初始化。一些限制: | ||
+ | * 适配器要求可以添删 & 尾部元素修改,因此**不能使用** //array// 和 // | ||
+ | * //stack// 要求 '' | ||
+ | * //queue// 要求 '' | ||
+ | * // | ||
+ | |||
+ | ===stack 适配器=== | ||
+ | //stack// 适配器定义在 ''< | ||
+ | \\ \\ < | ||
+ | 一个实例: | ||
+ | <code cpp> | ||
+ | stack< | ||
+ | //fill up the stack | ||
+ | for (size_t ix = 0; ix != 10; ++ix) { | ||
+ | intStack.push(ix); | ||
+ | } | ||
+ | whlie(!intStack.empty()) { | ||
+ | int value = intStack.top(); | ||
+ | intStack.pop(); | ||
+ | } | ||
+ | </ | ||
+ | 注意这里,即便 //stack// 是基于 //deque// 实现的,我们也不能使用 // | ||
+ | <WRAP center round help 100%> | ||
+ | **适配器应该使用适配器定义的操作,不能使用底层容器的操作来操作适配器**。 | ||
+ | </ | ||
+ | |||
+ | ===queue 适配器=== | ||
+ | 标准库中: | ||
+ | * //queue// 是一种访问策略为**先进先出**(// | ||
+ | * // | ||
+ | 相关的操作: | ||
+ | \\ \\ < |