C++ Primer 第九章 顺序容器

  • 顺序容器中顺序不依赖元素的值,而是与元素假如容器时的位置相对应

顺序容器概述

所有顺序容器都提供了快速顺序访问元素的能力。但是,在以下方面都有不同的性能折中

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

确定使用哪种容器

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

容器库概述

容器类型上的操作形成了一种层次

  • 某些操作是所有容器类型都提供的
  • 另外一些操作仅针对顺序容器、关联容器或无序容器
  • 还有一些操作只适用于一小部分容器

对容器可以保存的元素类型的限制

顺序容器几乎可以保存任意类型的元素

vector<vector<string> > lines; // 较旧的编译器需要在两个尖括号之间输入空格
  • 我们可以定义一个保存没有默认构造函数对象的容器,但我们在构造这种容器时不能只传递给它一个元素数目的参数
vector<noDefault> v1(10, init); // 正确:提供了元素初始化器
vector<noDefault> v2(10); // 错误:必须提供一个元素初始化器

容器操作

类型别名
iterator                                此容器类型的迭代器类型
const_iterator                          可以读取元素,但不能修改元素的迭代器类型
size_type                               无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type                         带符号整数类型,足够保存两个迭代器之间的距离
value_type                              元素类型
reference                               元素的左值类型;与 value_type& 含义相同
const_reference                         元素的 const 左值类型(即,const value_type&)

构造函数
C c;                                    默认构造函数,构造空容器
C c1(c2);                               构造 c2 的拷贝 c1
C c(b, e);                              构造c,将迭代器 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)                              与 a.swap(b) 等价

大小
c.size()                                c 中元素的数目(不支持 forward_list)
c.max_size()                            c 可保存的最大元素数目
c.empty()                               若 c 中存储了元素,返回 false,否则返回 true

添加/删除元素(不适用于 array)
注:在不同容器,这些操作的接口都不同
c.insert(args)                          将 args 中的元素拷贝进 c
c.emplace(inits)                        使用 inits 构造 c 中的一个元素
c.erase(args)                           删除 args 指定的元素
c.clear()                               删除 c 中的所有元素,返回 void

关系运算符
==, !=                                 所有容器都支持相等(不等)运算符
<, <=, >, >=                            关系运算符(无序关联容器不支持)

获取迭代器
c.begin(), c.end()                      返回指向 c 的首元素和尾元素之后位置的迭代器
c.cbegin(), c.cend()                    返回 const_iterator

反向容器的额外成员(不支持 forward_list)
reverse_iterator                        按逆序寻址元素的迭代器
const_reverse_iterator                  不能修改元素的逆序迭代器
c.rbegin(), c.rend()                    返回指向 c 的尾元素和首元素之间位置的迭代器
c.crbegin(), c.crend()                  返回 const_reverse_iterator

迭代器

  • 例外:forward_list 不支持递减运算符(--)
  • 99 页这些运算只能应用于 string、vector、deque 和 array的迭代器

迭代器范围

  • 范围 [begin, end)
  • end 可以与 begin 指向相同的位置,但不能指向 begin 之前的位置

使用左闭合范围蕴含的编程假定

这种范围有三种方便的性质

  • 如果 begin 与 end 相等,则范围为空
  • 如果 begin 与 end 不等,则范围至少包含一个元素,且 begin 指向该范围中的第一个元素
  • 我们可以对 begin 递增若干次,使得 begin == end
while (begin != end) {
	*begin = val;
	++ begin;
}
  • 这使我们可以安全的解引用,因为 begin 必然指向一个元素

容器类型成员

  • 为了使用这些类型,我们必须显式使用其类名
// iter 是通过 list<string> 定义的一个迭代器类型
list<string>::iterator iter;
// count 是通过 vector<int> 定义的一个 difference_type 类型
vector<int>::difference_type count;

begin 和 end 成员

  • 不以 c 开头的函数都是重载过的,一个是 const,一个是非常量成员
list<string> a = {"Milton", "Shakespeare", "Austen"};
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 = a.crbegin(); // list<string>::const_reverse_iterator
  • 当 auto 与 begin 或 end 结合使用时,获得的迭代器类型依赖于容器类型,但以 c 开头的版本还是可以获得 const_iterator 的,不管容器类型是什么

容器定义和初始化

  • 除 array 之外,其他容器的默认构造函数都会创建一个指定类型和空容器,且都可以接受指定容器大小和元素初始值的参数
    **容器定义和初始化
C c                     默认构造函数。如果 C 是一个 array,则 c 中元素按默认方式初始化;否则 c 为空
C c1(c2)                c1 初始化为 c2 的拷贝。c1 和 c2 必须是相同容器类型,且保存的是相同的元素类型,
                        对于 array 类型,两者还必须具有相同的大小
C c1 = c2               同上
C c{a, b, c ... }       c 初始化为初始化列表中元素的拷贝。列表中元素的类型必须与 C 的元素类型相同。
                        对于 array 类型,列表中元素数目必须等于或小于 array 的大小,任何遗漏的元素都进行值初始化
C c = {a, b, c ... }    同上
C c(b, e)               c 初始化为迭代器 b 和 e 指定范围中的元素的拷贝。
                        范围中元素的类型必须与 C 的元素类型相同(array 不适用)

只有顺序容器(不包括 array)的构造函数才能接受大小参数
C seq(n)                seq 包含 n 个元素,这些元素进行了值初始化;此构造函数是 explicit 的(string 不适用)
C seq(n, t)             seq 包含 n 个初始化为值 t 的元素

将一个容器初始化为另一个容器的拷贝

  • 当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了
    list<string> authors = {"Milton", "Shakespeare", "Austen"};
    vector<const char*> articles = {"a", "an", "the"};

    list<string> list2(authors); // 正确:类型匹配
    deque<string> authList(authors); // 错误:容器类型不匹配
    vector<string> words(articles); // 错误:容器类型必须匹配

    // 正确,const char* 可以转为 string
    forward_list<string> words(articles.begin(), articles.end());
    // 正确:it 表示指向 authors 中的一个元素
    deque<string> authList(authors.begin(), it);

列表初始化

    list<string> authors = {"Milton", "Shakespeare", "Austen"};
    vector<const char*> articles = {"a", "an", "the"};

与顺序容器大小相关的构造函数

  • 如果元素类型是内置类型或是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小的参数,如果元素类型没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。
  • 只有顺序容器(array除外)的构造函数才接受大小参数,关联容器并不支持
vector<int> ivec(10, -1);
list<string> svec(10, "hi!");
forward_list<int> ivec(10); // 十个元素,每个都是 0
deque<string> svec(10); // 十个元素,每个都是空 string

标准库 array 具有固定大小

  • 当定义 array 时,除了指定元素类型,还要指定容器大小
array<int, 42> // 保存 42 个 int 的数组
array<string, 42> // 保存 42 个 string 的数组
  • 使用 array 类型,必须同时指定元素类型和大小
array<int, 10>::size_type i;
array<int>::iterator j; // 错误:array<int> 不是一个类型
  • array 不支持普通的容器构造函数
  • 一个默认构造的 array 是非空的:它包含了与其大小一样多的元素,这些元素都被默认初始化
array<int, 10> ia1; // 10 个默认的 int
array<int, 10> ia2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 列表初始化
array<int, 10> ia3 = {42}; // ia3[0] = 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; // 正确:只要数组类型匹配即合法

赋值和 swap

  • array 不支持 assign 也不能将一个花括号列表赋予数组
array<int, 10> = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> = {0}; // 所有元素值均为 0
a1 = a2; // 替换 a1 中的元素
a2 = {0}; // 错误:不能将一个花括号数组赋予数组
c1 = c2                 将 c1 中的元素替换为 c2 中的元素的拷贝。c1 和 c2 必须具有相同的类型
c = {a, b, c ...}       将 c1 中元素替换为初始化列表中元素的拷贝(array 不适用)
swap(c1, c2)            交换 c1 和 c2 中的元素。c1 和 c2 必须具有相同的类型。swap 通常比从 c2 向 c1 拷贝元素快得多
c1.swap(c2)             同上

assign 操作不适用于关联容器和 array
seq.assign(b, e)        将 seq 中的元素替换为迭代器 b 和 e 所表示的范围中的元素。迭代器 b 和 e 不能指向 seq 中的元素
seq.assign(il)          将 seq 中的元素替换为初始化列表 il 中的元素
seq.assign(n, t)        将 seq 中的元素替换为 n 个值为 t 的元素

使用 assign(仅顺序容器)

  • 传递给 assign 的迭代器不能指向调用 assign 的容器

使用 swap

  • 除 array 外,swap 只是交换了两个容器内部的数据结构
  • 除 string 外,指向容器的迭代器、引用和指针在 swap 操作之后都不会失效
  • swap 两个 array 会真正交换他们的元素,因此在 swap 后,迭代器、引用和指针所绑定的元素保持不变,但元素值已经与另一个 array 中对应的元素值进行了交换

容器大小操作

  • size 返回容器中元素的数目
  • empty 当 size 为 0 的时候返回 true
  • max_size 返回一个大于或等于该类型容器所能容纳的最大元素数的值
  • forward_list 不支持 size

关系运算符

比较两个容器

  • 如果两个容器具有相同大小且所有元素都两两相对应相等,则这两个容器相等;否则两个容器不等
  • 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
  • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果

容器的关系运算符使用元素的关系运算符完成比较

  • 只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器
vector<Sales_daata> storeA, storeB;
if (storeA < storeB) // 错误:Sales_data 没有 < 运算符

顺序容器操作

向顺序容器添加元素

这些操作会改变容器的大小;array 不支持这些操作
forward_list 有自己专有版本的 insert 和 emplace;
forward_list 不支持 push_back 和 emplace_back
vector 和 string 不支持 push_front 和 emplace_front
c.push_back(t)               在 c 的尾部创建一个值为 t 或由 args 创建的元素。返回 void
c.emplace_back(args)
c.push_front(t)              在 c 的头部创建一个值为 t 或由 args 创建的元素。返回 void
c.emplace_front(args)        
c.insert(p, t)               在迭代器 p 指向的元素之前创建一个值为 t 或由 agrs 创建的元素。返回指向新添加的元素的迭代器
c.emplace(p, args)
c.insert(p, n, t)            在迭代器 p 指向的元素之前插入 n 个值为 t 的元素。返回指向新添加的第一个元素的迭代器;
                             若 n 为 0,则返回 p
c.insert(p, b, e)            将迭代器 b 和 e 指定的范围内的元素插入到迭代器 p 指向的元素之前。b 和 e 不能指向 c 中
                             的元素。返回指向新添加的第一个元素的迭代器;若范围为空,则返回 p
c.insert(p, il)              il 是一个花括号包围的元素值列表。将这些给定值插入到迭代器 p 指向的元素之前。
                             返回指向新添加的第一个元素的迭代器;若列表为空,则返回 p

向一个 vector、string 或 deque 插入元素会使所有指向容器的迭代器、引用和指针失效

使用 push_back

string words;
while (cin >> words) {
	container.push_back(words);
}

关键概念:容器元素是拷贝

使用 push_front

  • list、forward_list 和 deque 还支持 push_front
list<int> ilist;
for (size_t ix = 0; ix != 4; ++ ix)
	ilist.push_front(ix);

在容器中的特定位置添加元素

  • vector、deque、list 和 string 都支持 insert 成员。forward_list 提供了特殊版本的 insert 成员
slist.insert(iter, "Hello!"); // 将 "Hello!" 添加到 iter 之前的位置,iter 是一个迭代器

插入范围内元素

svec.insert(svec.end(), 10, "Anna"); // 在 svec 末尾插入 10 个 Anna

vector<string> v = {"quasi", "simba", "frollo", "scar"};
// 将 v 的最后两个元素添加到 slist 的开始位置
slist.insert(slist.begin(), v.end() - 2, v.end());
slist.insert(slist.end(), {"these", "words", "will", "go", "at", "the", "end"});
// 运行时错误:迭代器表示要拷贝的范围,不能指向与目的地的位置相同的容器
slist.insert(slist.begin(), slist.begin(), slist.end());

使用 insert 的返回值

  • 通过使用 insert 的返回值,可以在容器中一个特定位置反复插入元素
list<string> lst;
auto iter = lst.begin();
while (cin >> word)
	iter = lst.insert(iter, word);

使用 emplace 操作

  • 调用 emplace 是将参数传递给元素类型的构造函数。emplace 成员使用这些参数在容器管理的内存空间中直接构造元素。
// 使用三个参数的 Sales_data 构造函数
c.emplace_back("978-0590353403", 25, 15.99);
// 错误:没有接受三个参数的 push_back 版本
c.push_back("978-0590353403", 25, 15.99);
// 正确:创建一个临时的 Sales_data 对象传递给 push_back
c.push_back(Sales_data("978-0590353403", 25, 15.99));
c.emplace();
c.emplace(iter, "999-999999999");
c.emplace_front("978-0590353403", 25, 15.99);

emplace 函数在容器中直接构造元素。传递给 emplace 函数的参数必须与元素类型的构造函数相匹配

访问元素

at 和下标操作只适用于 string、vector、deque 和 array
back 不适用于 forward_list
c.back()       返回 c 中尾元素的引用。若 c 为空,函数行为未定义
c.front()      返回 c 中首元素的引用。若 c 为空,函数行为未定义
c[n]           返回 c 中下标为 n 的元素的引用,n 是一个无符号整数。若 n >= c.size(), 则函数行为未定义
c.at(n)        返回下标为 n 的元素的引用。如果下标越界,则抛出一 out_of_range 异常

访问成员返回的是引用

  • 如果容器是一个 const 对象,则返回值是一个 const 引用
  • 如果容器不是 const 的,则返回值是普通引用
if (!c.empty()) {
	c.front() = 42;
	auto &v = c.back();
	v = 1024;
	auto v2 = c.back();
	v2 = 0;
}

下标操作和安全的随机访问

  • 如果我们希望确保下标是合法的,可以使用 at 成员函数。at 成员函数类似下标运算符,但如果下标越界,at 会抛出一个 out_of_range 异常
vector<string> svec;
cout << svec[0]; // 运行时错误
cout << svec.at(0); // 抛出异常

删除元素

这些操作会改变容器的大小,所以不适用于 array
forward_list 有特殊版本的 erase
forward_list 不支持 pop_back();vector 和 string 不支持 pop_front
c.pop_back()     删除 c 中尾元素。若 c 为空,则函数行为未定义。函数返回 void
c.pop_front()    删除 c 中首元素。若 c 为空,则函数行为未定义。函数返回 void
c.erase(p)       删除迭代器 p 所指定的元素,返回一个指向被删元素之后元素的迭代器,若 p 指向尾元素,则返回尾后迭代器
                 若 p 是尾后迭代器,则函数行为未定义
c.erase(b, e)    删除迭代器 b 和 e 所指定范围内的元素。返回一个指向最后一个被删元素之后元素的迭代器,若 e 本身就是
                 尾后迭代器,则函数也返回尾后迭代器
c.clear()        删除 c 中的所有元素。返回 void
删除 deque 中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。
指向 vector 或 string 中删除点之后位置的迭代器、引用和指针都会失效
  • 删除元素的成员函数并不会检查其参数。在删除元素之前,程序员必须确保它(们)是存在的

pop_front 和 pop_back 成员函数

  • 这些操作返回 void
	while (!ilist.empty()) {
		process(ilist.front());
		ilist.pop_front();
	}

从容器内部删除一个元素

  • 两种形式的 erase 都返回指向删除(最后一个)元素之后位置的迭代器
    例:删除 list 所有奇数
    list<int> lst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto it = lst.begin();
    while (it != lst.end()) {
        if (*it % 2) {
            it = lst.erase(it);
        } else {
            ++ it;
        }
    }

删除多个元素

  • 接受一对迭代器
elem1 = slist.erase(elem1, elem2); // 调用后,elem1 == elem2
  • 删除所有元素
slist.clear();
slist.erase(slist.begin(), slist.end()); // 等价

特殊的 forward_list 操作

lst.before_begin()              返回指向链表首元素之前不存在的元素的迭代器。此迭代器不
lst.cbefore_begin()             能解引用。cbefore_begin() 返回一个 const_iterator

lst.insert_after(p, t)          在迭代器 p 之后的位置插入元素。t 是一个对象,n 是数量,
lst.insert_after(p, n, t)       b 和 e 是表示范围的一对迭代器(b 和 e 不能指向 lst 内),
lst.insert_after(p, b, e)       il 是一个花括号列表。返回一个指向最后一个插入元素的迭
lst.insert_after(p, il)         代器。如果范围为空,则返回 p。若 p 为尾后迭代器,则函数行为未定义

emplace_after(p, args)          使用 args 在 p 指定的位置之火创建一个元素。返回一个指向
                                这个新元素的迭代器。若 p 为尾后迭代器,则函数行为未定义

lst.erase(p)                    删除 p 指向的位置之后的元素,或删除从 b 之后直到(但不
lst.erase(b, e)                 包含)e 之间的元素。返回一个指向被删元素之后元素的迭代
                                器,若不存在这样的元素,则返回尾后迭代器。如果 p 指向
                                lst 的尾元素或者是一个尾后迭代器,则函数行为未定义

改变容器的大小

  • 当前大小大于所要求的大小,容器后部的元素会被删除
  • 当前大小小于所要求的大小,会将新元素添加到容器后部
resize 不适用于 array
c.resize(n)                调整 c 的大小为 n 个元素。若 n < c.size(),则多出的元素被丢弃。
                           若必须添加新元素,对新元素进行值初始化
c.resize(n, t)             调整 c 的大小为 n 个元素。任何新添加的元素都初始化为值 t

如果 resize 缩小容器,则指向被删除元素的迭代器、引用和指针都会失效。
对 vector、string 或 deque 进行 resize 可能导致迭代器、指针 和 引用失效

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

在向容器添加元素后

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

当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效,因为这些元素都已经被销毁了。

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

注意:当我们删除元素时,尾后迭代器总是会失效

编写改变容器的循环程序

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

不要保存 end 返回的迭代器

  • 添加或删除元素的循环程序必须反复调用 end,而不能在循环之前保存 end 返回的迭代器,一直当作容器末尾使用。
  • 在循环体中,我们向容器中添加了一个元素,这个操作使保存在 end 中的迭代器失效了。这个迭代器不再指向 v 中任何元素,或是 v 中尾元素之后的位置。
  • 必须在每次插入操作后重新调用 end(),而不能在循环开始前保存它返回的迭代器

vector 对象是如何增长的

  • 当不得不获取新的内存空间时,vector 和 string 的实现通常会分配比新的空间需求更大的内存空间

管理容量的成员函数

shrink_to_fit 只适用于 vector、string 和 deque
capacity 和 reserve 只适用于 vector 和 string
c.shrink_to_fit()         请将 capacity() 减少为与 size() 相同大小
c.capacity()              不重新分配内存空间的话,c 可以保存多少元素
c.reserve(n)              分配至少能容纳 n 个元素的内存空间

reserve 并不改变容器中元素的数量,它仅影响 vector 预先分配多大的内存空间

capacity 和 size

  • size 是指它已经保存的元素的数目
  • capacity 则是在不分配新的内存空间的前提下它最多可以保存多少元素
    vector<int> v;
    cout << v.size() << ' ' << v.capacity() << endl; // 0 0

    for (int i = 1; i != 25; i ++) v.push_back(i);
    cout << v.size() << ' ' << v.capacity() << endl; // 24 28

    v.reserve(50);
    cout << v.size() << ' ' << v.capacity() << endl; // 24 50

    for (int i = 25; i != 51; i ++) v.push_back(i);
    cout << v.size() << ' ' << v.capacity() << endl; // 50 50

    v.push_back(1);
    cout << v.size() << ' ' << v.capacity() << endl; // 51 75

    v.shrink_to_fit();
    cout << v.size() << ' ' << v.capacity() << endl; // 51 51

额外的 string 操作

构造 string 的其他方法

n、len2 和 pos2 都是无符号值
string s(cp, n)              s 是 cp 指向的数组中前 n 个字符的拷贝。此数组至少应该包含 n 个字符

string s(s2, pos2)           s 是 string s2 从下标 pos2 开始的字符的拷贝。若 pos2 > s2.size(),
                             构造函数的行为未定义

string s(s2, pos2, len2)     s 是 string s2 从下标 pos2 开始 len2 个字符的拷贝。若 pos2 > s2.size(),构造函数
                             的行为未定义。不管 len2 的值是多少,构造函数至多拷贝 s2.size() - pos2 个字符
  • 从 const char* 创建 string,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止,如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。如果我们未传递计数值且数组也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。
  • 从 string 拷贝字符时,我们可以提供可选的开始位置和一个计数值。开始位置必须小于或等于给定的 string 的大小。如果位置大于 size,则构造函数抛出一个 out_of_range 异常。如果我们传递一个数值,则从给定位置开始拷贝这么多个字符。不管我们要求拷贝多少个字符,标准库最多拷贝到 string 结尾,不会更多
    const char *cp = "Hello World!!!";
    char noNull[] = {'H', 'i'};

    string s1(cp);
    string s2(noNull, 2);
    //string s3(noNull);
    string s4(cp + 6, 5);
    string s5(s1, 6, 5);
    string s6(s1, 6);
    string s7(s1, 6, 20);
    //string s8(s1, 16);

    cout << s1 << endl // Hello World!!!
         << s2 << endl // Hi
         //<< s3 << endl // noNull 不以空字符结束
         << s4 << endl // World
         << s5 << endl // World
         << s6 << endl // World!!!
         << s7 << endl; // World!!!
         //<< s8 << endl; // out_of_range 异常

substr 操作

  • 如果开始位置超过了 string 的大小,则 substr 函数抛出一个 out_of_range 异常。如果开始位置加上计数值大于 string 的大小,则 substr 会调整计数值,只拷贝到 string 的末尾
s.substr(pos, n)        返回一个 string,包含 s 中从 pos 开始的 n 个字符的拷贝。pos 的默认值为 0。
                        n 的默认值为 s.size() - pos,即拷贝从 pos 开始的所有字符

改变 string 的其他方法

s.insert(s.size(), 5, '!');
s.erase(s.size() - 5, 5);

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

string s = "some thing", s2 = "some other string";
s.insert(0, s2); // 在 s 中位置 0 之前插入 s2 的拷贝
s.insert(0, s2, 0, s2.size()); // 在 s[0] 之前插入 s2 中 s2[0] 开始的 s2.size() 个字符

append 和 replace 函数

  • append 是在 string 末尾进行插入操作的一种简写形式
  • replace 操作是调用 erase 和 insert 的一种简写形式
s.insert(pos, args)       在 pos 之前插入 args 指定的字符
s.erase(pos, len)         删除从位置 pos 开始的 len 个字符
s.assign(args)            将 s 中的字符替换为 args 指定的字符
s.append(args)            将 args 追加到 s
s.replace(range, args)    删除 s 中范围 range 内的字符,替换为 args 指定的字符

改变 string 的多种重载函数

  • 这些函数参数有不同的版本,但是这些韩式有共同的接口

string 搜索函数

  • string 提供 6 个不同的搜索函数,每个函数有 4 个重载版本。
  • 每个搜索操作会返回一个 string::size_type 值,表示匹配发生位置的下标。
  • 如果搜索失败,则返回一个名为 string::npos 的 static 成员
  • 标准库将 npos 定义为一个 const string::size_type 类型,并初始化为 -1.由于 npos 是一个 unsigned 类型,此初始值意味着 npos 等于任何 string 最大的可能大小
s.find(args)                查找 s 中 args 第一次出现的位置
s.rfind(args)               查找 s 中 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。pos 默认为 0
s2, pos                     从 s 中位置 pos 开始查找字符串 s2。pos 默认为 0
cp, pos                     从 s 中位置 pos 开始查找指针 cp 指向的以空字符结尾的 C 风格字符串。pos 默认为 0
cp, pos, n                  从 s 中位置 pos 开始查找指针 cp 指向的数组的前 n 个字符。pos 和 n 无默认

指定在哪里开始搜索

    string ::size_type pos = 0;
    while ((pos = name.find_first_of(number, pos)) != string::npos) {
        cout << "found number at index: " << pos 
             << " element is " << name[pos] << endl;
        ++ pos;
    }

逆向搜素

  • 从右往左搜索,返回子字符串靠右的出现位置

compare 函数

s.compare 的几种参数形式

s2                          比较 s 和 s2
pos1, n1, s2                将 s 中从 pos1 开始的 n1 个字符与 s2 进行比较
pos1, n1, s2, pos2, n2      将 s 中从 pos1 开始的 n1 个字符与 s2 中从 pos2 开始的 n2 个字符进行比较
cp                          比较 s 与 cp 指向的以空字符结尾的字符数组
pos1, n1, cp                将 s 中从 pos1 开始的 n1 个字符与 cp 指向的以空字符结尾的字符数组进行比较
pos1, n1, cp, n2            将 s 中从 pos1 开始的 n1 个字符与指针 cp 指向的地址开始的 n2 个字符进行比较

数值转换

  • 如果 string 不能转换为一个数值,这些函数抛出一个 invalid_argument 异常。如果转换得到的数值无法用任何类型来表示,则抛出一个 out_of_range 异常
to_string(val)            一组重载函数,返回数值 val 的 string 表示。val 可以是任何算数类型。对每个浮点类型
                          和 int 或更大的整型,都有相应版本的 to_string。与往常一样,小整型会被提升

stoi(s, p, b)             返回 s 的起始子串(表示整数内容)的数值,返回类型分别是 int、long、unsigned long
stol(s, p, b)             、long long、unsigned long long。b 表示转换所用的基数,默认值为 10。p 是 size_t
stoul(s, p, b)            指针,用来保存 s 中第一个非数值字符的下标,p 默认为 0,即,函数不保存下标
stoll(s, p, b)
stoull(s, p, b)

stof(s, p)                返回 s 的起始子串(表示浮点数内容)的数值,返回值类型分别是 float、double 或
stod(s, p)                long double。参数 p 的作用与整数转换函数中一样
stold(s, p)

容器适配器

  • 适配器是标准库中的一个通用概念
  • 一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物

所有容器适配器都支持的操作和类型

size_type             一种类型,足以保存当前类型的最大对象的大小
value_type            元素类型
container_type        实现适配器的底层容器类型
A a                   创建一个名为 a 的空适配器
A a(c)                创建一个名为 a 的适配器,带有容器 c 的一个拷贝
关系运算符             每个适配器都支持所有关系运算符:==  !=  <  <=  >  >=
                      这些运算符返回底层容器的比较结果
a.empty()             若 a 包含任何元素,返回 false,否则返回 true
a.size()              返回 a 中的元素数目
swap(a, b)            交换 a 和 b 的内容,a 和 b 必须有相同类型,包括底层容器类型也必须相同
a.swap(b)

定义一个适配器

每个适配器都定义两个构造函数

  • 默认构造函数创建一个空对象
  • 接受一个容器的构造函数拷贝该容器来初始化适配器
stack<int> stk(deq);
  • 默认情况下,stack 和 queue 是基于 deque 实现的,priority_queue 是在 vector 之上实现的
  • 我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数来重载默认容器类型
stack<string, vector<string> > str_stk;
stack<string, vector<string> > str_stk2(svec);

所有适配器都要求容器具有添加和删除元素的能力

  • stack:可以使用除 array 和 forward_list 之外的任何容器类型来构造
  • queue:list、deque
  • priority_queue:vector、deque

栈适配器

  • stack 头文件中
栈默认基于 deque 实现,也可以在 list 或 vector 之上实现
s.pop()             删除栈顶元素,但不反悔该元素值
s.push(item)        创建一个新元素压入栈顶,该元素通过拷贝或移动 item 而来,或者由 args 构造
s.emplace(args)
s.top()             返回栈顶元素,但不将元素弹出栈

我们只可以使用适配器操作,而不能使用底层容器类型的操作

    stack<int> intStack;

    for (size_t ix = 0; ix != 10; ++ ix) intStack.push(ix);

    while (!intStack.empty()) {
        int value = intStack.top();
        intStack.pop();
    }

队列适配器

  • queue 和 priority_queue 定义在 queue
queue 默认基于 deque 实现,priority_queue 默认基于 vector 实现
queue 也可以用 list 或 vector 实现,priority_queue 也可以用 deque 实现
q.pop()             返回 queue 的首元素或 priority_queue 的最高优先级的元素,但不返回此元素
q.front()           返回首元素或尾元素,但不删除此元素
q.back()            只适用于 queue
q.top()             返回最高优先级元素,但不删除该元素
                    只适用于 priority_queue
q.push(item)        在 queue 末尾或 priority_queue 中恰当的位置创建一个元素
q.emplace(args)     其值为 item,或者由 args 创造
posted @ 2023-02-04 15:31  HuiPuKui  阅读(26)  评论(0)    收藏  举报