C++_Primer09.container

顺序容器

sequential container

概述

顺序容器中的元素顺序与加入容器的顺序是相同的。

容器类型 说明
vector 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快
list 双向链表。只支持双向顺序访问。在list中任何位置插入/删除速度都很快
forward_list 单向链表。只支持单向顺序访问。在链表任何位置插入/删除操作速度都很快
array 固定大小数组。支持快速随机访问。不能添加或删除元素
string 与vector相似的容器,但专门用于保存字符。随即访问快。在尾部插入/删除速度快

string 和 vector 将元素保存在连续的内存空间中。由于元素是连续存储的,由元素下表来计算地址是非常快速的。但是,这两种容器的中间位置增删元素会非常耗时:在一次插入或删除操作后,需要移动其后所有元素。并且在添加一个元素时,有时需要分配额外的存储空间,此时所有元素都必须移动到新的存储空间中。

list 和 forward_list 两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这俩容器不支持元素的随机访问:为了访问一个元素,必须遍历整个容器。而且,与 vector, deque 和 array 相比,这两个容器的额外内存开销也很大。

deque 是一个更复杂的数据结构。与 string 和 vector 类似,deque 支持快速随机访问,在 deque 的中间位置添加删除元素的代价(可能)很高。但是,在 deque 的两端添加或删除元素都是很快的,与 list 和 forward_list 添加删除元素的速度相当。

forward_list 和 array 是新 C++ 标准增加的类型。与内置数组相比,array是一种更安全,更容易使用的数组类型。与内置数组类似,array 大小是固定的。forward_list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此 forward_list 没有 size 操作,因为保存或计算其大小会比手写链表多出额外的开销。对其他容器而言,size 保证是一个快速的常量时间的操作。

新标准库的容器比旧版本快得多。新标准库容器的性能几乎与最精心优化过的同类数据结构一样好
现代C++程序应该使用标准库容器,而不是更原始的数据结构,如内置数组
通常,使用 vector 是最好的选择

确定使用哪种容器

  • 除非有更好的理由,否则应使用 vector
  • 如果程序中有很多小元素,且空间的额外开销很重要,则不要使用 list 或 forward_list
  • 如果程序要求随机访问元素,应使用 vector 或 deque
  • 如果程序要求在容器的中间插入或删除元素,应使用 list 或 forward_list
  • 如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用 deque
  • 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
    • 首先确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易的向 vector 追加数据,然后再调用标准库的 sort 函数重排,避免在中间位置添加元素。
    • 如果必须在中间位置添加元素,考虑在输入阶段使用 list,然后再将 list 中的内容拷贝到 vector 中。

如果程序既需要随机访问元素,又需要在容器中间位置插入元素,则取决于 list或 forward_list 中访问元素和 vector 或 deque 中插入删除元素的相对性能。一般来说,程序中占主导地位的操作决定了容器类型的选择。

如果不确定应该使用哪种容器,那么可以在程序中只使用 vector 和 list 公共的操作:使用迭代器,不使用下标操作,避免随机访问。

容器库概览

迭代器范围(iterator range):由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置,这个范围是一个左闭合区间 [begin, end)

  • 如果 begin 与 end 相等,则范围为空
  • 如果不相等,则范围至少包含一个元素
  • 可以对 begin 递增若干次,使得 begin==end
while (begin != end) {
    * begin = val;
    ++begin;
}

容器的操作

容器操作 说明
类型别名
iterator 迭代器
const_iterator 只读迭代器
size_type 无符号整数,足够保存此种容器类型最大可能容器的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型,与 value_type& 含义相同
const_reference 元素的 const 左值类型
构造函数
C c; 默认构造器
Cc1(c2); 构造 c2 的拷贝 c1
C c(b, e); 将迭代器 b 和 e 指定的范围内的元素拷贝到 c 中(array不支持)
C c(a, b, c...); 列表初始化 c
赋值与 swap
c1 = c2; c1 中的元素替换为 c2 中的元素
c1 = {a, b, c...}; c1 中的元素替换为列表中元素(不适用 array)
a.swap(b) 交换 a 和 b 的元素
swap(a, b) 同上
大小
c.size() c 的元素数(不适用forward_list)
c.max_size() 最大元素数目
c.empty() 判空
添加/删除(不适用array)
c.insert(args) 将 args 中的元素拷贝进 c
c.emplace(inits) 使用 inits 构造 c 中的一个元素
c.erase(args) 删除 args 指定的元素
c.clear() 清空
关系运算符
==, != 相等运算符
<, <=, >, >= 关系运算符(无序关联容器不支持)
获取迭代器
c.begin(), c.end() 返回指向 c 的首元素和尾元素之后位置的迭代器
c.cbegin(), c.cend() 返回 const_iterator
反向容器的额外成员(不支持forward_list)
reverse_iteraotr 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(), c.rend() 返回指向 c 的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() const 类型

begin 和 end 成员

list<string> a = {"Milton", "Tom", "July"};
auto it1 = a.begin();       // list<string>::iterator
auto it2 = a.rbegin();      // list<string>::reverse_iterator
auto it3 = a.cbegin();      // list<string>::const_iterator
auto it4 = z.crbegin();     // list<string>::const_reverse_iterator

其中 a 为 const 类型时 it1 类型为 list<string>::const_iterator

当不需要写操作时,应使用 cbegin 和 cend

容器定义和初始化

初始化 说明
C c; 默认构造函数。如果C是一个array,则c中元素按默认方式初始化,否则c为空
C c1(c2); c1,c2 类型必须相同(如果C为array,这两者大小还必须相同)
C c1 = c2; 同上
C c 列表初始化。列表中元素类型必须与C的元素类型相容
C c = 同上
C c(b,e) c 初始化为迭代器指定范围中元素的拷贝。其中元素类型必须与c元素类型相容(array不适用)
只有顺序容器(不包括array)的构造函数才能接受大小参数
C seq(n) seq 包含n个元素,这些元素都进行了值初始化;此构造函数是 explicit 的
C seq(n, t) seq 包含n个初始化为值t的元素

只有顺序容器(不包括 array)的构造器才能接受大小参数:

C seq(n);       // seq 包含 n 个元素,这些元素进行了初始化
C seq(n,t);     // seq 包含 n 个初始化为值 t 的元素

使用拷贝构造器时,两个容器的类型及元素类型必须匹配。不过当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且新容器与原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为新容器的元素类型即可。

list<string> authors = {"Milton", "Tom", "July"};
vector<const char*> articles = {"a", "an", "the"};
list<string> list2(author);         // 正确
deque<string> authList(authors);    // 错误,容器类型不匹配
vector<string> words(articles);     // 错误,容器类型不匹配
forward_list<string> words(articles.begin(), articles.end());   // 正确

除了和关联容器相同的构造函数外,顺序容器(array除外)还提供了另一个构造函数,它接受一个容器大小和一个元素初始值(可选):

vector<int> ivec(10, -1);       // 10个元素,每个都初始化为-1
list<string> svec(10, "hi!");   // 10个string,每个都初始化为 "hi!"
forward_list<int> ivec(10);     // 10个元素,每个都初始化为0
deque<string> svec(10);         // 10个元素,每个都是空string

对于没有默认构造函数的类型,在指定大小参数外,还必须指定一个显式的元素初始值

标准库 array 具有固定大小

大小是array类型的一部分

当定义一个 array 时,除了指定元素类型,还要指定容器大小

array<int, 42>
array<string, 10>
// 为了使用array类型,必须同时指定元素类型大小
array<int, 10>::size_type i;    // 正确
array<int>::size_type j;        // 错误

array的初始化:

array<int, 10> ia1;             // 10个默认初始化的 int
array<int, 10> ia2 = {1,2,3,4,5,6,7,8,9,10};
array<int, 10> a3 = {42};       // 剩余元素都为0

与内置数组类型不同,array可以进行拷贝或对象值操作:

int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs;                 // 错误
array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};
array<int, 10> copy = digits;       // 正确

习题 9.11

9.11 对6种创建和初始化vector对象的方法,每一种都给出一个实例。解释每个vector包含什么值。

vector<int> ivec1;						// 空
vector<int> ivec2{1, 2, 3, 4, 5};		 // 1, 2, 3, 4, 5
vector<int> ivec3 = {6, 7, 8, 9, 10};	  // 6, 7, 8, 9, 10
vector<int> ivec4(ivec2);				 // 1, 2, 3, 4, 5
vector<int> ivec5 = ivec3;				 // 6, 7, 8, 9, 10
vector<int> ivec6(10, 4);				// 10个4

习题 9.12

9.12 对于接受一个容器创建其拷贝的构造函数,和接受两个迭代器创建拷贝的构造函数,解释它们的不同。

接受一个容器创建其拷贝的构造函数,要求两个容器类型及元素类型必须匹配。
接受两个迭代器创建拷贝的构造函数,不要求容器类型匹配,而且元素类型也可以不同,只要拷贝的元素能转换就可以。

习题 9.13

9.13 如何用一个list初始化一个vector?从一个vector又该如何创建?编写代码验证你的答案。

list<int> ilist = {1, 2, 3, 4, 5};
vector<double> dvec1(ilist.cbegin(), ilist.cend());
vector<int> ivec = {5, 6, 7, 8};
vector<double> dvec2(ivec.cbegin(), ivec.cend());

swap

swap(c1,c2);        // 交换两个容器的元素,类型必须相同。swap通常比拷贝元素快得多
c1.swap(c2);        // 同上

除array外,swap 不对任何元素进行拷贝,删除或插入操作,因此可以保证在常数时间内完成。
元素不会被移动,这意味着,除 string 外,指向容器的迭代器,引用和指针在 swap 操作之后都不会失效。他们仍指向 swap 操作之前所指向的那些元素。但是在 swap 之后,这些元素已经属于不同的容器了。
比如,iter 在 swap 前指向 svec1[3] 的 string,那么在 swap 后它指向 svec2[3]的元素。
对一个 string 调用 swap 会导致迭代器,引用和指针失效。

与其他容器不同,swap 两个 array 会真正交换他们的元素,因此交换两个 array 所需的时间与 array 中元素的数目成正比。
对于 array,swap 后的指针,引用和迭代器所绑定的元素保持不变,但元素值已经与另一个 array 中对应的元素值进行了交换。

早期版本只提供成员函数版本的 swap,而统一使用非成员版本的 swap 是一个好习惯。

使用 assign

assgin 操作允许从一个不同但相容的类型赋值,或从一个子序列赋值

仅顺序容器,array除外

list<string> names;
vector<const char*> oldstyle;
names = oldstyle;               // 错误,容器类型不匹配
names.assign(oldstyle.cbegin(), oldstyle.cend());   // 正确

由于就元素被替换,因此传递给 assign 的迭代器不能指向调用 assign 的容器

使用assign替换为相同的值:

list<string> slist1(1);
slist1.assign(10, "Hiya!");
// 等价于:
// slist1.clear();
// slist.insert(slist1.begin(), 10, "Hiya!");

容器大小

每个容器都提供一个成员函数 size 返回容器中元素的数目。
成员函数 empty 当 size 为0时返回 true。
max_size 返回一个大于或等于容器能容纳最大元素数的值。
forward_list 支持 max_size 和 empty,但不支持 size

关系运算符

每个容器类型都支持相等运算符(==,!=)

除了无序关联容器外的所有容器都支持关系运算符(<,>,>=,<=),关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。即我们只能比较 vector 与另一个 vector,而不能比较 vector 与 vector 或 list.

两个容器的比较实际上是逐对比较,工作方式与 string 类似。
容器的相等运算符实际上使用的是元素的 == 运算符实现比较的;其他关系运算符使用元素的 < 运算符。

顺序容器操作

list, vector, deque
string
forward_list

添加元素

添加元素:

操作 说明
c.push_back(t)
c.emplace_back(args)
在c的尾部创建一个值为t或由args创建的元素,返回 void
c.push_front(t)
c.emplace_front(args)
在c的头部创建一个值为t或由args创建的元素,返回 void
c.insert(p, t)
c.emplace(p, args)
在迭代器 p 指向的元素前创建一个值为 t 或由 args 创建的元素。返回指向新添加元素的迭代器
c.insert(p, n, t) 在迭代器 p 指向的元素前插入 n 个 t。返回指向第一个新添加元素的迭代器,若 n 为0则返回 p
c.insert(p, b, e) 在迭代器 p 前插入由迭代器 b 和 e 指定的范围的元素,b 和 e 不能指向 c 中的元素。返回指向添加的第一个元素的迭代器,若范围为空,则返回 p
c.inert(p, il) il 是一个花括号包围的元素值列表,意思与上类似

forward_list 有自己转悠版本的 insert 和 emplace;并且它不支持 push_back 和 emplace_back
vector 和 string 不支持 push_front 和 emplace_back
向一个 vector, string 或 deque 插入元素会使所有指向容器的迭代器,引用和指针失效

向一个 vector, deque 或 string 添加元素可能会引起整个对象存储空间的重新分配,并将元素从旧空间移动到新的空间中。

当用一个对象初始化容器时,或将一个对象插入到容器时,实际放入到容器中的是对象值的一个拷贝,而不是对象本身

push_back

除 array 和 forward_list 外,每个顺序容器都支持 push_back (包括 string)

push_front

list, forward_list 和 deque 容器支持 push_front

vector 不支持 push_front 操作,但可以使用 insert 将元素插入到首位

在 vector 和 deque 的非首尾位置插入元素会很耗时

insert

vector, deque, list 和 string 都支持 insert. forward_list 提供了特殊版本的 insert 成员。

新标准下,insert 返回第一个新加入元素的迭代器(旧标准返回 void)。如果返回为空,insert 会返回第一个参数。

利用 insert 的返回值,可以在容器中的一个特定位置反复插入元素:

list<string> lst;
auto iter = lst.begin();
while(cin >> word){
    iter = lst.insert(iter, word);  // 等价于 push_front
}

emplace

emplace_front, emplace, emplace_back

当调用 push 或 insert 成员函数时,我们将元素类型的对象传递给他们,这些对象被拷贝到容器中。而当我们调用一个 emplace 成员函数时,则是将参数传递给元素类型的构造函数,emplace 成员会在容器管理的内存空间中直接构造元素:

c.emplace_back("1234567", 25, 15.99);
c.push_back("1234567", 25, 15.99);              // 错误
c.push_back(Sales_data("1234567", 25, 15.99));  // 正确

c.emplace_back();                       // c.push_back(Sales_data());
c.emplace(iter, "1234567");             // c.insert(iter, Sales_data("1234567"));
// 以下等价于 c.push_front(Sales_data("1234567", 25, 15.99));
c.emplace_front("1234567", 25, 15.99);

传递给 emplace 函数的参数必须与元素类型的构造函数相匹配

习题9.18

编写程序,从标准输入读取string序列,存入一个deque中。编写一个循环,用迭代器打印deque中的元素。

#include <string>
#include <iostream>
#include <deque>

using namespace std;

int main(int argc, char** argv){
    string tmp;
    deque<string> q;

    while(cin >> tmp){
        if (tmp == "exit"){
            break;
        }
        q.push_back(tmp);
    }

    // 或者用 auto iter = q.cbegin()
    for (deque<string>::const_iterator iter = q.cbegin(); iter != q.cend(); ++iter){
        cout << * iter << endl;
    }

    return 0;
}

访问元素

若容器中没有元素, 访问操作的结果是未定义的

操作 说明
c.back() 返回 c 的尾元素的引用,等价于 *(c.end()-1)
c.front() 首元素的引用,等价于 *c.begin()
c[n] 返回 c 下表为 n 的元素的引用。如果 n>=c.size(), 则函数行为未定义
c.at(n) 同上。如果下标越界则抛出 out_of_range 异常

at 和下标操作只适用于 string, vector, deque 和 array
back 不适用于 forwrad_list

在调用 front 和 back 之前,要确保 c 非空。对空容器调用 front 和 back,就像使用越界下标一样。

返回的是引用

if (!c.empty()){
    c.front() = 42;
    auto& v = c.back();
    v = 1024;
    auto v1 = c.back();     // v1 不是引用,它是 c.back() 的一个拷贝
    v1 = 0;                 // 未改变 c 中的元素
}

删除元素

操作 说明
c.pop_back() 删除 c 的尾元素,若 c 为空,则函数行为未定义。函数返回 void
c.pop_front() 删除 c 的首元素,若 c 为空,则函数行为未定义。函数返回 void
c.erase(p) 删除迭代器 p 所指定的元素,返回被删元素的下一个元素的迭代器。
若p是尾后迭代器则函数行为未定义
c.erase(b, e) 删除迭代器 b 和 e 所指定范围内的元素。返回最后被删元素之后的迭代器。若 e 是尾后迭代器,则返回尾后迭代器
c.clear() 删除 c 中所有元素。返回 void

这些操作会改变容器大小,所以不适用于 array
forward_list 有特殊版本的 erase
forward_list 不支持 pop_back
vector 和 string 不支持 pop_front

删除 deque 中除首尾位置之外的任何元素都会使所有迭代器,引用和指针失效。指向 vector 或 string 中删除点之后位置的迭代器,引用和指针都会失效
删除元素的成员函数并不检查参数,所以在删除前程序员必须确保它们是存在的。

pop_front 和 pop_back

while (!ilist.empty()) {
    process(ilist.front());
    ilist.pop_front();
}

删除指定位置的元素

容器中删除所有的奇数:

list<int> lst = {0,1,2,3,4,5,6,7,8};
auto iter = lst.begin();
while (iter != lst.end()){
    if (*iter % 2 == 1){
        it = lst.erase(iter);
    } else {
        ++iter;
    }
}

特殊的 forward_list 操作

forward_list 是单向链表,没有办法获取一个元素的前驱链接
forward_list 未定义 insert, emplace 和 erase,而是定义了 insert_after, emplace_after 和 erase_after 的操作,为了删除某个元素,需要知道它的前一个元素

操作 说明
lst.before_begin()
lst.cbefore_begin()
返回指向链表首元素之前不存在的元素的迭代器
lst.insert_after(p, t)
lst.insert_after(p, n, t)
lst.insert_after(p, b, e)
lst.insert_after(p, il)
在迭代器 p 之后插入元素。il 是花括号列表
lst.emplace_after(p, args) 使用元素构造器直接在容器中直接创建元素,插入位置在 p 之后
lst.erase_after(p)
lst.erase_after(b, e)
删除指定位置之后的元素。删除元素不包含 b

改变容器大小

list<int> ilist(10, 42);
ilist.resize(15);           // 将5个值为0的元素添加到 ilist 的末尾
ilist.resize(25, -1);       // 将10个值为-1的元素添加到 ilist 的末尾
ilist.resize(5);            // 从 ilist 末尾删除20个元素

容器操作可能使迭代器失效

添加元素:

  • 对于 vector 或 string,存储空间被重新分配后,指向容器的迭代器,指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器,指针和引用仍有效,但指向插入位置之后元素的迭代器,指针和引用将会失效。
  • 对于 deque,插入到除首尾位置之外的任何位置都会导致迭代器,指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
  • 对于 list 和 forward_lsit,指向容器的迭代器,指针和引用仍有效。

删除元素:

  • 对于 list 和 forward_list,指向容器其他位置的迭代器,引用和指针仍有效
  • 对于 deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器,引用或指针也会失效。如果删除 deque 尾元素,则尾后迭代器也会失效,但其他迭代器,引用和指针不受影响;如果删除首元素,则这些也不会受影响
  • 对于 vector 和 string,指向被删元素之前元素的迭代器,引用和指针仍有效

不要保存 end 返回的迭代器

当添加/删除 vector 或 string 的元素后,或在 deque 的首元素之外任何位置添加/删除元素后,原来 end 返回的迭代器总是会失效,因此,添加或删除元素的循环程序必须反复调用 end,而不能在循环之前保存 end 返回的迭代器当作容器末尾使用。通常 C++ 标准库实现的 end() 操作都很快。

vector 对象是如何增长的

为了支持快速随机访问,vector 和 string 会将元素连续存储。在增删元素时,如果空间不够容纳新元素,则容器必须分配新空间并将原来的元素拷贝到新位置,然后添加新元素,释放旧空间。为了避免每次添加元素都分配新空间和释放旧空间,在创建容器时会预留一部分空间,当不得不获取新空间时,才会分配更大的空间。
虽然 vector 在每次重新分配内存空间时都要移动所有元素,但使用此策略后,其扩张操作通常比 list 和 deque 还要快。

管理容量的成员函数

操作 说明
c.shrink_to_fit() 请将 capacity() 减少为与 size() 相同大小
c.capacity() 不重新分配内存空间的话,c 可以保存多少元素
c.reserve(n) 通知容器它应该准备保存 n 个元素,容器实际分配的空间至少是 n,可能更大

shrink_to_fit 只适用于 vector, string 和 deque
capacity 和 reserve 只适用于 vector 和 string

shrink_to_fit 要求容器退回不需要的内存空间,此函数指出我们不再需要任何多余的内存空间。

reserve 并不改变容器中元素的数量,它仅影响 vector 预先分配多大的内存空间
只有当需要的内存空间超过当前容量时,reserve 才会改变 vector 的容量,reserve 至少分配与需求一样大的内存空间。
类似的,resize 函数只改变容器中元素的数目,而不是容器的容量

只有在执行 insert 操作时 size 与 capacity 相等,或者调用 resize 或 reserve 时给定的大小超过当前 capacity,vector 才可能重新分配内存空间,分配多少空间取决于具体实现。

额外的 string 操作

构造 string 的其他方法

操作 说明
string s(cp, n) s 是 cp 指向的数组中前 n 个字符的拷贝,此数组应至少包含 n 个字符
string s(s2, pos2) s 是 s2 从下标 pos2 开始的字符的拷贝。若 pos2>s2.size(),则行为未定义
string s(s2, pos2, len2) 从下标 pos2 开始,长度为 len2

pos 参数不能超过 s 的范围,而长度可以,标准库最多拷贝到 string 结尾,不会更多

substr

返回一个 string,原始 string 的一部分或全部的拷贝

string s("hello world");
string s2 = s.substr(0, 5);     // hello
string s3 = s.substr(6);        // world
string s4 = s.substr(6, 11);    // world
string s5 = s.substr(12);       // 抛出 out_of_range

其他方法

s.insert(s.size(), 5, '!');     // 末尾添加5个叹号
s.erase(s.size()-5, 5);         // 删除最后5个字符

const char* cp = "Stately, plump Buck";
s.assign(cp, 7);                // s == "Stately"
s.insert(s.size(), cp + 7);     // s == "Stately, plump Buck"

string s = "abc", s2 = "hello";
s.insert(0, s2);                // s == "helloabc"
s.insert(0, s2, s2.size());     // s == "hellohelloabc"

append 和 replace

append: 追加到末尾
replace: 替换

string s("C++ Primer"), s2 = " 4th edition";
s.append(s2);

// 从位置11 开始删除3个字符,然后插入 "5th"
s.replace(11, 3, "5th");    // C++ Primer 5th edition
操作 说明
s.insert(pos, args) 在 pos 前插入 args 大于或小于参数指定的字符串。pos 可以是下标或迭代器,接受下标的版本返回 s 的引用;接受迭代器的版本返回指向第一个插入字符的迭代器
s.erase(pos, len) 删除从位置 pos 开始的 len 个字符。如果 len 被省略,则删除从 pos 开始到 s 末尾的所有字符。返回一个指向 s 的引用

搜索操作

6个不同的搜索函数,每个函数有4个重载版本
返回值是位置

操作 说明
s.find(args) 查找 s 中 args 第一次出现的位置
s.rfind(args) 最后一次出现的位置
s.find_first_of(args) 在 s 中查找 args 中任何一个字符第一次出现的位置
s.find_last_of(args) 在 s 中查找 args 中任何一个字符最后一次出现的位置
s.find_first_not_of(args) 在 s 中查找第一个不在 args 中的字符
s.find_last_not_of(args) 在 s 中查找最后一个一个不在 args 中的字符
args:
c, pos s 中位置 pos 开始查找字符 c
s2, pos s 中位置 pos 开始查找字符串 s
cp, pos cp 为C风格字符串指针,字符串以空字符结尾
cp, pos, n 查找 cp 的前 n 个字符

compare

根据 s 等于,大于或小于参数指定的字符串,s.compare 返回 0,正数或负数

参数形式 说明
s2 比较 s 和 s2
pos1, n1, s2 将 s 中的 pos1 开始的 n1 个字符与 s2 进行比较
pos1, n1, s2, pos2, n2 将 s 中的 pos1 开始的 n1 个字符与 s2 中 pos2 开始的 n2 个字符进行比较
cp cp 为指针
pos1, n1, cp pos1, n1 指定了 s 中的位置
pos1, n1, cp, n2 pos1, n1 指定了 s 中的位置; n2 指定了 cp 的字符数

数值转换

int i = 42;
string s = to_string(i);
double d = stod(s);

string s2 = "pi = 3.14";
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
参数形式 说明
to_string(val) 返回数值对应的字符串
stoi(s, p, b)
stol(s, p, b)
stoul(s, p, b)
stoll(s, p, b)
stoull(s, p, b)
p 是 size_t 指针,用来保存 s 中第一个非数值字符的下标,默认是0,即函数不保存下表。b 表示转换所用的基数,默认10
stof(s, p)
stod(s, p)
stold(s, p)
返回 s 的起始子串的数值,p 作用同上

习题9.51

设计一个类,它有三个unsigned成员,分别表示年、月和日。为其编写构造函数,接受一个表示日期的string参数。你的构造函数应该能处理不同数据格式,如January 1,1900、1/1/1990、Jan 1 1900等。

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char** argv){
    string dts = "Jan 10 1920";

    int format = 0;
    int tag = 0;
    if (dts.find_first_of(",") < dts.size()){
        format = 1;
        tag = 1;
    } else if (dts.find_first_of("/") < dts.size()){
        format = 2;
    } else if (dts.find_first_of(" ") < dts.size()){
        format = 1;
    }

    int year, month, day;
    switch(format){
        case 1:
            if (dts.find("Jan") < dts.size())  month = 1;
            if (dts.find("Feb") < dts.size())  month = 2;
            if (dts.find("Mar") < dts.size())  month = 3;
            if (dts.find("Api") < dts.size())  month = 4;
            if (dts.find("May") < dts.size())  month = 5;
            if (dts.find("Jun") < dts.size())  month = 6;
            if (dts.find("Jul") < dts.size())  month = 7;
            if (dts.find("Aug") < dts.size())  month = 8;
            if (dts.find("Sep") < dts.size())  month = 9;
            if (dts.find("Oct") < dts.size())  month = 10;
            if (dts.find("Nov") < dts.size())  month = 11;
            if (dts.find("Dec") < dts.size())  month = 12;

            if (tag == 1) {
                int index = dts.find(",");
                day = stoi(dts.substr(dts.find(" "), index - dts.find(" ")));
                year = stoi(dts.substr(index));
            } else {
                day = stoi(dts.substr(dts.find(" "), dts.rfind(" ") - dts.find(" ")));
                year = stoi(dts.substr(dts.rfind(" ")));
            }
            break;
        case 2:
            month = stoi(dts.substr(0, dts.find("/")));
            day = stoi(dts.substr(dts.find("/")+1, dts.rfind("/")));
            year = stoi(dts.substr(dts.rfind("/")));
            break;
        default:
            year = 0;
            month = 0;
            day = 0;
    }

    cout << "year: " << year << ", month: " << month << ", day: " << day << endl;

    return 0;
}

容器适配器

adapter,适配器是标准库中的一个通用概念,容器,迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。

三个顺序容器适配器:stack, queue 和 priority_queue

定义一个适配器

每个适配器都有两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器:

stack<int> stk(deq);    // 从 deq 拷贝元素到 stk

默认情况下,stack 和 queue 是基于 deque 实现的,priority_queue 是在 vector 之上实现的。在创建一个适配器时将一个顺序容器类型作为第二个类型参数,来重载默认容器类型:

// 在 vector 上实现的空栈
stack<string, vector<string>> str_stk;
// 在 vecotr 上实现,初始化时保存 svec 的拷贝
stack<string, vector<string>> str_stk2(svec);

适配器不能构造在 array 之上。
stack 只要求 push_back, pop_back 和 back 操作,因此可以使用除 array 和 forward_list 之外的任何容器类型来构造 stack
queue 要求 back, push_back, front 和 push_front,因此它可以构造于 list 和 deque 之上,但不能基于 vector 构造
priority_queue 除了 front, push_back 和 pop_back 操作之外还要求随机访问能力,因此它可以构造于 vector 或 deque 之上,但不能基于 list 构造

栈适配器(stack)

stack 先进后出

操作 说明
s.pop() 删除栈顶元素,但不返回该元素值
s.push(item)
s.emplace(args)
创建一个新元素压栈,该元素通过拷贝或移动 item 而来,或者由 args 构造
s.top() 返回栈顶元素,但不将元素弹出栈

队列适配器(queue 和 priority_queue)

定义在 queue 头文件中
queue 先进先出

操作 说明
q.pop() 返回queue 的首元素或 priority_queue 的最高优先级元素,但不删除此元素
q.front() 返回首元素或尾元素,但不删除此元素
q.back() 返回最后进入队列的元素,只适用于 queue
q.top() 返回最高优先级元素,但不删除该元素,只适用于 priority_queue
q.push(item)
q.emplace(args)
在 queue 末尾或 priority_queue 中恰当的位置创建一个元素,值为 item,或由 args 构建

小结

标准库容器是模板类型,用来保存给定类型的对象。顺序容器中的元素是按顺序存放的,通过位置来访问。

所有容器(array除外)都提供搞笑的动态内存管理,可以向容器中添加元素不必担心元素存储的位置。容器负责管理自身的存储,vector 和 string 通过 reserve 和 capacity 提供更细致的内存管理控制。

每个容器都定义了构造函数,添加和删除元素的操作,确定容器大小和返回特定元素的迭代器的操作。其他一些有用的操作比如搜索或排序,并不是由容器定义的,而是由标准库算法实现的。

添加和删除元素时可能会使迭代器,指针或引用失效,如 insert 和 erase

posted @ 2022-11-23 10:13  keep-minding  阅读(25)  评论(0)    收藏  举报