《C++11Primer》阅读随记 -- 九、顺序容器

第九章 顺序容器

顺序容器概述

STL 容器在以下方面有不同的性能折中:

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

迭代器

迭代器范围

一个迭代器范围( iterator range ) 由一堆迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置( one past the last element )。,它们标记了容器中元素的一个范围。

标准 array 具有固定大小

与内置数组一样,标准库 array 的大小也是类型的一部分。当定义一个 array 的时候,除了指定元素类型,还要指定容器的大小

array<int, 42>		// 类型为:保存 42 个 int 的数组
array<string, 10>	// 类型为:保存 10 个 string 的数组

为了使用 array 类型,我们必须同时指定元素类型和大小

array<int, 10>::size_type i;		// 数组类型包括元素类型和大小
array<int>::size_type j;			// 错误: array<int> 不是一个类型

虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但是 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;	// 正确:只要数组类型匹配即合法

使用 assign (仅顺序容器)

赋值运算符要求左边和右边的运算对象具有相同的类型。它将右边运算对象中所有元素拷贝到左边运算对象中。顺序容器( array除外 ) 害定义勒一个名为 assign 的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign 操作用参数所指定的元素(的拷贝)替换左边容器中的所有元素。例如,我们可以用 assign 实现将一个 vector 中的一段 char* 赋予一个 list 中的 string

cbegin()和cend()是C++11新增的,它们返回一个const的迭代器,不能用于修改元素。

list<string> names;
vector<const char*> oldstyle;
names = oldstyle;				// 错误:容器类型不匹配

// 正确:可以将 const char* 转换为 string
names.assign(oldstyle.cbegin(), oldstyle.cend());

这段代码中对 assgin 的调用将 names 中的元素替换为迭代器指定范围中的元素的拷贝。assign 的参数决定勒容器中将有多少个元素以及它们的指都是什么。

assign 的第二个版本接受一个整型值和一个元素值。它用指定数目且具有相同给定值的元素替换容器中原有的元素

// 等价于 slits1.clear()
// 后跟 slist1.insert(slist1.begin(), 10, "Hiya!");
list<string> slistl(1);		// 1 个元素,为空 string
slist1.assign(10, "Hiya!");	// 10 个元素,每个都是 "Hiya!"

swap

vector<string> svec1(10);
vector<string> svec2(24);
swap(svec1, svec2);

交换两个容器内容的操作保证会很快—元素本身并未交换,swap 只是交换了两个容器的内部数据结构。

元素不会被移动的事实意味着,除 string 外,指向容器的迭代器、引用和指针在 swap 操作之后都不会失效。它们仍指向 swap 操作之前所指向的那些元素。但是,在 swap 之后,这些元素已经属于不同的容器了。例如,假定 iterswap 之前指向 svec1[3]string,那么在 swap 之后它指向 svec2[3] 的元素。与其他容器不同,对一个 string 调用 swap 会导致迭代器、引用和指针失效

与其他容器不同,swap 两个 array 会真正交换它们的元素。因此,交换两个 array 所需的时间与 array 中元素的数目成证必

因此,对于 array,在 swap 操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另外一个 array 中对应的值进行了交换。

操作作用
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 插入元素会使所有指向容器的迭代器、引用和指针失效。

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

每个 insert 函数都接受一个迭代器作为其第一个参数。迭代器指出了容器中什么位置防止新元素。它可以指向容器中任何位置,包括容器尾部之后的下一个位置。由于迭代器可能指向容器尾部之后不存在的元素位置,而且在容器开始位置插入元素是很有用的功能,所以 insert 函数将元素插入到迭代器所指定的位置之前。例如,下面的语句

slist.insert(iter, "Hello!"); // 将 "Hello" 添加到 iter 之前的位置

虽然某些容器不支持 push_front 操作,但它们对于 insert 操作并无类似的限制( 插入开始位置 )。因此我们可以将元素插入到容器的开始位置,而不必担心容器是否支持 push_front

vector<string> svec;
list<string> slist;

// 等价于调用 slist.push_front("Hello!");
slist.insert(slist.begin(), "Hello!");

// vector 不支持 push_front, 但我们可以插入到 begin() 之前
// 插入到 vector 末尾以外的任何位置都可能很慢
svec.insert(svec.begin(), "Hello!");

插入范围内元素


svec.insert(svec.end(), 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());

emplace

新标准三个新成员 – emplace_front、emplace 和 emplace_back。这些操作构造而不是拷贝元素。

当调用 pushinsert 成员函数时,则是将参数传递给元素类型的构造函数。emplace 成员使用这些参数在容器管理的内存空间中直接构造元素。例如,假定 c 保存 Sales_data 元素:

// 在 c 的末尾构造一个 Sales_data 对象
// 使用三个参数的 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_back();	// 使用 Sales_data 的默认构造函数
c.emplace(iter, "999-99999999");	// 使用Sales_data(string)

// 使用 Sales_data 的接受一个 ISBN、一个 count 和一个 price 的构造函数
c.emplace_front("978-0590353403", 25, 15.99);

在顺序容器中访问元素的操作
at 和下标操作只适用于 string、vector、deque 和 array
back 不适用于 forward_list
c.at(n) 返回下标为 n 的引用。如果下标越界,则抛出一 out_of_range 异常

删除元素

写法作用
c.erase(p)删除迭代器 p 所指定的元素,返回一个指向被删元素之后元素的迭代器,若 p 指向尾元素,则返回尾后( off-the-end )迭代器。若 p 是尾后迭代器,则函数行为未定义
c.erase(b, e)删除迭代器 b 和 e 所指定范围内的元素。返回一个指向最后一个被删元素之后元素的迭代器,若 e 本身就是尾后迭代器,则函数也返回会尾后迭代器
slist.clear();	// 删除容器中所有元素
slist.erase(slist.begin(), slist.end());	// 等价调用

因为后继前驱的存在,forward_list 的操作实现方式不同,并未定义 insert、emplace 和 erase,而是定义了名为 insert_after、emplace_after 和 erase_after 的操作。为了支持这些操作,forward_list 也定义了 before_begin,它返回一个 **首前(off-the-beginning)**迭代器。这个迭代允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素

lst.before_begin()	返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。
lst.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, arg)		使用 args 在 p 指定的位置之后创建一个元素。返回一个指向
							这个新元素的迭代器。若 p 为尾后迭代器,则函数行为未定义

lst.erase_after(p)			删除 p 指向的位置之后的元素,或删除从 b 之后直到(但不包
lst.erase_after(b, e)) e 之间的元素。返回一个指向被删元素之后元素的迭代器,
							若不存在遮掩给的元素,则返回尾后迭代器。如果 p 指向 lst
							的尾元素或者是一个尾后迭代器,则函数行为未定义
forward_list<int> flst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto prev = flst.before_begin();	// 表示 flst 的“首前”元素
auto curr = flst.begin();			// 表示 flst 的第一个元素
while(curr != flst.end()){
	if(*curr % 2)
		curr = flst.erase_after(prev);
	else{
		prev = curr;
		++curr;
	}
}

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

向容器添加元素后:

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

当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效。

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

使用失效的迭代器、指针或引用是严重的运行时错误

管理容量的成员函数

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

额外的 string 操作

const char* cp = "Hello World!!!";	// 以空字符结束的数组
char noNull[] = {'H', 'i'};			// 不是以空字符结束
string s1(cp);	// 拷贝 cp 中的字符直到遇到空字符; s1 == "Hello World!!!";
string s2(noNull, 2);				// 从 noNull 拷贝两个字符; s2 == "Hi"
string s3(noNull);					// 未定义:noNull 不是以空字符结束
string s4(cp + 6, 5);				// 从 cp[6] 开始拷贝 5 个字符; s4 == "World"
string s5(s1, 6, 5);				// 从 s1[6] 开始拷贝 5 个字符; s5 == "World"
string s6(s1, 6);				// 从 s1[6] 开始拷贝,直至 s1 末尾; s6 == "World!!!"
string s7(s1, 6, 20);			// 正确,只拷贝到 s1 末尾; s7 == "Hello World!!!"
string s8(s1, 16);				// 抛出一个 out_of_range 异常

通常当我们从一个 const char* 创建 string 时,指针指向的数组必须以空字符结束,拷贝操作遇到空字符时停止。如果我们害传递给构造函数一个计数值,数组就不必以空字符结尾。如果为传递计数值且数组也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。

substr 操作

substr 操作返回一个 string,它是原始 string 的一部分或全部的拷贝。可以传递给 substr 一个可选的开始位置和计数器

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

改变 string 的其他方法

s.insert(s.size(), 5, '!');		// 在 s 的末尾插入 5 个感叹号
s.erase(s.size() - 5, 5);		// 从 s 删除最后 5 个字符

const char* cp = "Stately, plump Buck";
s.assign(cp, 7);			// s == "Stately"
s.insert(s.size(), cp + 7);	// s == "Stately, plump Buck"
// 通过调用 assign 替换 s 的内容。我们赋予 s 的内容是从 cp 指向的地址开始的 7 个字符。要求赋值的字符数必须小于或等于 cp 指向的数组中的字符数(不包括结尾的空字符)
// 接下来在 s 上调用 insert,将 cp 开始的 7 个字符拷贝到 s 中

在这里插入图片描述

append

string s("C++ Primer"), s2 = s;		// 将 s 和 s2 初始化为 "C++ Primer"
s.insert(s.size(), " 4 th Ed.");	// s == "C++ Primer 4th Ed.";
s2.append(" 4 th Ed.");				// s2 == "C++ Primer 4th Ed."; 等价

replace

s.erase(11, 3);		// s == "C++ Primer Ed.";
s.insert(11, "5th")	// s == "C++ Primer 5th Ed.";

// 从位置 11 开始,删除 3 个字符并插入 "5th"
s2.replace(11, 3, "5th")		// 等价 s2 == "C++ Primer 5th Ed.";
s.insert(pos, args)		// 在 pos 之前插入 arg 指定的字符。pos 可以是一个下标或
						// 一个迭代器。接受下标的版本返回一个指向 s 的引用; 接受
						// 迭代器的版本返回指向第一个插入字符的迭代器

s.erase(pos, len)		// 删除从位置 pos 开始ide len 个字符。如果 len 被省略,则
						// 删除从 pos 开始直至 s 末尾所有字符。返回一个指向 s 的引用

s.assign(args)			// 将 s 中的字符替换为 args 指定的字符。返回一个指向 s 的引用
s.append(args)			// 将 args 追加到 s。
s.replace(range, arg)	// 删除 s 中范围 range 内的字符,替换为 args 指定的字符。range
						// 或者是一个下标和一个长度,或者是一对指向 s 的迭代器。返回
						// 一个指向 s 的引用

arg 可以是下列形式之一; append 和 assign 可以使用所有形式
str 不能与 s 相同,迭代器 b 和 e 不能指向 s

str					字符串 str
str, pos, len		str 中从 pos 开始最多 len 个字
cp, len				从 cp 指向的字符数组的前 len 个字符
cp					cp 指向的以空字符结尾的字符数组
n, c				n 个字符 c
b, e				迭代器 b 和 e 指定的范围内的字符
初始化列表			花括号包围的,以逗号分隔的字符列表

find

string 搜索操作都返回一个 string::size_type 指,表示匹配发生位置的下标。如果搜索失败,则返回一个名为 string::nposstatic 成员。

string name("AnnaBelle");
auto pos1 = name.find("Anna");		// pos1 == 0

string lowercase("annabelle");
pos1 = lowercase.find("Anna");		// pos1 == npos

string numbers("0123456789"), name("r2d2");
// 返回 1,即,name 中第一个数字的下标
auto pos = name.find_first_of(numbers);

// 如果要搜索第一个不再参数中的字符,我们应该调用 find_first_not_of。例如,为了搜索一个 string 中的第一个非数字字符
string dept("03714p3");
// 返回 5 -- 字符 'p' 的下标
auto pos = dept.find_first_not_of(number);
  • s.find(args) 查找 sargs 第一次出现的位置
  • s.rfind(args) 查找 sargs 最后一次出现的位置
  • 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 中出现的字符

arg 必须是以下形式之一

  • c, poss 中位置 pos 开始查找字符 cpos 默认为 0
  • s2, poss 中位置 pos 开始查找字符串 s2pos 默认为 0
  • cp, poss 中位置 pos 开始查找指针 cp 指向的以空字符结束的 C 风格字符串,pos 默认为 0
  • cp, pos, ns 中位置 pos 开始查找指针 cp 指向的数组的前 n 个字符。posn 无默认值

在这里插入图片描述

数值转换

int i = 42;
string s = to_string(i);		// 将征数 i 转换为字符表示形式
double d = stod(s);				// 将字符串 s 转换为浮点数
to_string(val)
stoi(s, p, b)		// 返回 s 的起始子串(表示整数内容)的数值,返回值类型分别是 int
stol(s, p, b)		// long、unsigned long、long long 、unsigned long long。 b 表示
stoul(s, p, b)		// 转换所用的基数,默认值为 10。p 是 size_t 指针,用来保存 s 中第
stoull(s, p, b)		// 一个非数值字符的下标, p 默认为 0,即,函数不保存下标

stof(s, p)			// 返回 s 的起始子串( 表示浮点数内容 )的数值,返回值类型分别是
stod(s, p)			// float、double 或 long double。参数 p 的作用与整数转换函数中
stold(s, p)			// 一样
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Artintel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值