C++标准库 -- 顺序容器 (Primer C++ 第五版 · 阅读笔记)
C++标准库 -- 顺序容器(Primer C++ 第五版 · 阅读笔记)
- 第9章 顺序容器------(持续更新)
- 9.1、顺序容器概述
- 9.2、容器库概览
- 9.2.1 、迭代器
- 9.2.2 、容器类型成员
- 9.2.3 、begin 和 end 成员
- 9.2.4 、容器定义和初始化
- 9.2.5 、赋值和 swap
- 9.2.6 、容器大小操作
- 9.2.7 、关系运算符
- 9.3、顺序容器操作
- 9.3.1 、向顺序容器添加元素
- 9.3.2 、访问元素
- 9.3.3 、删除元素
- 9.3.4 、特殊的forward_list操作
- 9.3.5 、改变容器大小
- 9.3.6 、容器操作可能使迭代器失效
- 9.4、vector对象是如何增长的
- 9.5、额外的string 操作
- 9.5.1、构造string的其他方法
- 9.5.2、改变string的其他方法
- 9.5.3、string搜索操作
- 9.5.4、compare函数
- 9.5.5、数值转换
- 9.6、容器适配器
第9章 顺序容器------(持续更新)
所有容器类都共享公共的接口,不同容器按不同方式对其进行扩展。
这个公共接口使容器的学习更加容易—我们基于某种容器所学习的内容也都适用于其他容器。
每种容器都提供了不同的性能和功能的权衡。
9.1、顺序容器概述
下表列出了标准库中的顺序容器, 所有顺序容器都提供了快速顺序访问元素的能力。但是,这些容器在以下方面都有不同的性能折中:
- 向容器添加或从容器中删除元素的代价
- 非顺序访问容器中元素的代价
除了固定大小的 array
外,其他容器都提供高效、灵活的内存管理。
list
和forward_list
两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器。而且,与vector
、deque
和array
相比,这两个容器的额外内存开销也很大.deque
是一个更为复杂的数据结构。与string
和vector
类似,deque
支持快速的随机访问。与string
和vector
一样,在deque
的中间位置添加或删除元素的代价(可能)很高。但是,在deque
的两端添加或删除元素都是很快的,与list
或forward_list
添加删除元素的速度相当。forward_list
的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_lis
t没有size
操作,因为保存或计算其大小就会比手写链表多出额外的开销。- 对其他容器而言
size
保证是一个快速的常量时间的操作。
9.2、容器库概览
一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。即,deque
定义头文件 deque
中,list
定义在头文件list
中,以此类推。
容器类型上的操作形成了一种层次:
- 某些操作是所有容器类型都提供的。
- 另外一些操作仅针对顺序容器、关联容器 或 无序容器。
- 还有一些操作只适用于一小部分容器。
对所有容器都适用的操作:
9.2.1 、迭代器
与容器一样,迭代器有着公共的接口:如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。
使用左闭合范围蕴含的编程假定
[begin, end)
标准库使用左闭合范围是因为这种范围有三种方便的性质。假定begin
和 end
构成一个合法的迭代器范围,则
- 如果
begin
与end
相等,则范围为空 - 如果
begin
与end
不等,则范围至少包含一个元素,且begin
指向该范围中的第一个元素 - 我们可以对begin递增若干次,使得
begin==end
这些性质意味着我们可以像下面的代码一样用一个循环来处理一个元素范围而这是安全的:
while (begin != end) {*begin = val; //正确:范围非空,因此begin指向一个元素++begin; //移动迭代器,获取下一个元素
}
9.2.2 、容器类型成员
为了使用这些类型,我们必须显式使用其类名,使用作用域运算符来说明我们希望使用list<string>
类的iterator
成员及vector<int>
类定义的difference_type
:
// iter是通过list<string>定义的一个迭代器类型
list<string>::iterator iter;
// count是通过vector<int>定义的一个difference_ type类型
vector<int>::difference_type count;
9.2.3 、begin 和 end 成员
begin
和end
操作生成指向容器中第一个元素和尾元素之后位置的迭代器。
begin
和end
有多个版本:带r
的版本返回反向迭代器;- 以
c
开头的版本则返回const
迭代器: - 不以
c
开头的函数都是被重载过的。也就是说,实际上有两个名为begin
的成员。一个是const
成员,返回容器的const_iterator
类型。另一个是非常量成员,返回容器的iterator
类型。rbegin
、end
和rend
的情况类似。
list<string> a = {"Milton","Shakespeare", "Austen" };auto itl = 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
当我们对一个非常量对象调用这些成员时,得到的是返回 iterator
的版本。只有在对一个const
对象调用这些函数时,才会得到一个const
版本。
与const
指针和引用类似,可以将一个普通的iterator
转换为对应的const_iterator
,但反之不行。
我们可以用以支持auto
与begin
和 end
函数结合使用。过去,没有其他选择,只能显式声明希望使用哪种类型的迭代器:
//是iterator还是const_ iterator依赖于a的类型
auto it7 = a.begin();//仅当a是const时,it7是const_iterator
auto it8 = a.cbegin();// it8是const_iterator,不管容器的类型是什么
当不需要写访问时,应使用
cbegin
和cend
。
9.2.4 、容器定义和初始化
每个容器类型都定义了一个默认构造函数。
- 除
array
之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
将一个容器初始化为另一个容器的拷贝
将一个新容器创建为另一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者(array
除外)拷贝由一个迭代器对指定的元素范围。
- 为了创建一个容器为另一个容器的拷贝,两个 容器的类型 及其 元素类型 必须匹配。
- 不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且,新容器和原容器中的元素类型也可以不同,只要能将要铂贝的元素转换为要初始化的容器的元素类型即可。
//每个容器有三个元素,用给定的初始化器进行初始化
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());
当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。
与顺序容器大小相关的构造函数
除了与关联容器相同的构造函数外,顺序容器(array
除外)还提供另一个构造函数,它接受一个容器大小和一个(可选的)元素初始值。如果我们不提供元素初始值则标准库会创建一个值初始化器:
vector<int> ivec (10, -1); // 10个int元素,每个都初始化为-1
list<string> svec(10,"hi !"); // 10个strings;每个都初始化为"hi! "
forward_list<int> ivec(10); // 10个元素,每个都初始化为0
deque<string> svec(10); // 10个元素,每个都是空string
只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
标准库 array
具有固定大小
与内置数组一样,标准库array
的大小也是类型的一部分。当定义一个array
时,除了指定元素类型,还要指定容器大小:
array<int, 10>::size_type i; //数组类型包括元素类型和大小
array<int>::size_type j; //错误:array<int>不是一个类型
由于大小是array
类型的一部分,array
不支持普通的容器构造函数。
如果我们对array
进行列表初始化,初始值的数目必须等于或小于array的大小。
array<int, 10> ial;// 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;//正确:只要数组类型匹配即合法
与其他容器一样,
array
也要求初始值的类型必须与要创建的容器类型相同。此外,array
还要求 元素类型 和 大小 也都一样,因为大小是array
类型的一部分。
9.2.5 、赋值和 swap
下表中列出的与赋值相关的运算符可用于所有容器。赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝:
c1 = c2; //将c1的内容替换为c2中元素的拷贝
c1 = {a,b,c}; //赋值后,c1大小为3
与内置数组不同,标准库array
类型允许赋值。赋值号左右两边的运算对象必须具有相同的类型;
array<int, 10> al = {0,1,2,3,4,5,6,7,8,9};
array<int,10> a2 = {0];//所有元素值均为0al = a2;//替换a1中的元素
a2 = {0};//错误:不能将一个花括号列表赋予数组
由于右边运算对象的大小可能与左边运算对象的大小不同,因此 array
类型不支持assign
,也不允许用花括号包围的值列表进行赋值。
使用assign(仅顺序容器)
赋值运算符要求左边和右边的运算对象具有相同的类型。它将右边运算对象中所有元素拷贝到左边运算对象中。
顺序容器(array
除外)还定义了一个名为assign
的成员,允许我们从一个 不同但相容 的类型赋值,或者从容器的一个子序列赋值。
assign
操作用参数所指定的元素(的拷贝)替换左边容器中的所有元素。
例如,我们可以用assgin
实现将一个vector
中的一段char*
值赋予一个list
中的string
:
list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误:容器类型不匹配//正确:可以将const char*转换为string
names.assign(oldstyle.cbegin(), oldstyle.cend());
由于其旧元素被替换,因此传递给
assign
的迭代器不能指向调用assign
的容器。
assign
的第二个版本接受一个整型值和一个元素值。它用指定数目且具有相同给定值的元素替换容器中原有的元素:
//等价于slist1.clear ();
//后跟slist1.insert(slist1.begin(), 10, "Hiya ! ");
list<string> slist1(1); // 1个元素,为空string
slist1.assign(10,"Hiya! "); //10个元素,每个都是“Hiya !”
使用swap
swap
操作交换两个相同类型容器的内容。调用swap
之后,两个容器中的元素将会交换:
vector<string> svec1(10); // 10个元素的vector
vector<string> svec2 (24);// 24个元素的vector
swap (svecl, svec2) ;
调用swap
后,svec1
将包含24个string
元素,svec2
将包含10个string
.除array
外,交换两个容器内容的操作保证会很快——-元素本身并未交换,swap
只是交换了两个容器的内部数据结构。
除
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
。而早期标准库版本只提供成员函数版本的swap
。非成员版本的swap
在泛型编程中是非常重要的。统一使用非成员版本的swap
是一个好习惯。
9.2.6 、容器大小操作
除了一个例外,每个容器类型都有三个与大小相关的操作。
- 成员函数
size
返回容器中元素的数目; empty
当size
为0时返回布尔值true
,否则返回false
;max_size
返回一个大于或等于该类型容器所能容纳的最大元素数的值。forward_lis
t支持max_size
和empty
,但不支持size
,原因我们将在下一节解释。
9.2.7 、关系运算符
每个容器类型都支持相等运算符(==
和!=
);除了无序关联容器外的所有容器都支持关系运算符(>
、>=
、<
、<=
)。
关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。
比较两个容器实际上是进行元素的逐对比较。这些运算符的工作方式与string
的关系运算类似:
- 如果两个容器具有相同大小且所有元素都两两对应相等,则这两个容器相等;否则两个容器不等。
- 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
- 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。
例如:
vector<int> v1 = { 1,3,5,7,9,12 };
vector<int> V2 = { 1,3,9 };
vector<int> v3 = { 1,3,5,7 };
vector<int> v4 = { 1,3,5,7,9,12 };vl < v2 // true; v1和v2在元素[2]处不同:v1[2]小于等于v2[2]
v1 < v3 // false;所有元素都相等,但v3中元素数目更少
vl == v4 // true;每个元素都相等,且v1和v4大小相同
vl == v2 // false;v2元素数目比v1少
容器的关系运算符使用元素的关系运算符完成比较
只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。
容器的相等运算符实际上是使用元素的==
运算符实现比较的,而其他关系运算符是使用元素的<
运算符。
- 如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。
9.3、顺序容器操作
9.3.1 、向顺序容器添加元素
顺序容器和关联容器的不同之处在于两者组织元素的方式。这些不同之处直接关系到了元素如何存储、访问、添加以及删除。上面介绍了所有容器都支持的操作。本章剩余部分将介绍顺序容器所特有的操作。
- 除
array
外,所有标准库容器都提供灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。下表列出了向顺序容器(非array
)添加元素的操作。
当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响性能。在一个
vector
或string
的尾部之外的任何位置,或是一个deque
的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vector
或string
添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。
- push_back:除
array
和forward_list
之外,每个顺序容器(包括string
类型)都支持push_back
; - push_front:除了
push_back
,list
、forward_list
和deque
容器还支持名为push_front
内类似操作。此操作将元素插入到容器头部; - insert:
insert
成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。vector
、deque
、list
和string
都支持insert
成员。forward_list
提供了特殊版本的insert
成员,我们将在后面介绍。
迭代器可以指向容器中任何位置,包括容器尾部之后的下一个位置,所以迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器开始位置插入元素是很有用的功能,所以
insert
函数将元素插入到迭代器所指定的位置之前。
-
使用
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_front
、insert
和push_back
,允许我们将元素放置在容器头部、一个指定位置之前或容器尾部。 - 当调用
push
或insert
成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace
成员函数时,则是将参数传递给元素类型的构造函数。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));
emplace
函数在容器中直接构造元素。传递给emplace
函数的参数必须与元素类型的构造函数相匹配。
// iter指向c中一个元素,其中保存了sales_data元素
c.emplace_back();//使用sales_data的默认构造函数
c.emplace(iter,"999-999999999");//使用sales_data(string)
//使用sales_data的接受一个ISBN、一个count和一个price的构造函数
c.emplace_front("978-0590353403", 25, 15.99);
9.3.2 、访问元素
包括array
在内的每个顺序容器都有一个 front
成员函数;
而除forward_list
之外的所有顺序容器都有一个back
成员函数。
这两个操作分别返回首元素和尾元素的引用:
//在解引用一个迭代器或调用front或 back之前检查是否有元素
if(!c.empty()){// val和val2是c中第一个元素值的拷贝auto val = *c.begin(), val2 = c.front();// val3和val4是c中最后一个元素值的拷贝auto last = c.end();auto val3 = *(--last);//不能递减forward_list迭代器auto val4 =c.back(); // forward_list不支持
}
此程序用两种不同方式来获取c中的首元素和尾元素的引用(变量别名)。直接的方法是调用front
和 back
。而间接的方法是通过解引用begin
返回的迭代器来获得首元素的引用,以及通过递减然后解引用end
返回的迭代器来获得尾元素的引用。
访问成员函数返回的是引用
在容器中访问元素的成员函数(即,front
、back
、下标
和at
)返回的都是引用。如果容器是一个const
对象,则返回值是const
的引用。如果容器不是const
的,则返回值是普通引用,我们可以用来改变元素的值:
if (!c.empty()){c.front() = 42; //将42赋予c中的第一个元素auto &v = c.back(); //获得指向最后一个元素的引用v = 1024; //改变c中的元素auto v2 = c.back(); //v2不是一个引用,它是c.back()的一个拷贝v2 = 0; //未改变c中的元素
}
下标操作和安全的随机访问
下标运算符并不检查下标是否在合法范围内。如果我们希望确保下标是合法的,可以使用at
成员函数。at
成员函数类似下标运算符,但如果下标越界,a
t 会抛出一个out_of_range
异常:
vector<string> svec; //空vector
cout << svec[0]; //运行时错误:svec中没有元素!
cout << svec.at(0); //抛出一个out_of_range异常
9.3.3 、删除元素
与添加元素的多种方式类似,(非array
)容器也有多种删除元素的方式。下表列出了这些成员函数。
pop_front
和pop_back
返回void
。如果你需要弹出的元素的值,就必须在执行弹出操作之前保存它:
while (!ilist.empty(){process(ilist.front()); //对ilist的首元素进行一些处理ilist.pop_front() ;//完成处理后删除首元素
}
9.3.4 、特殊的forward_list操作
当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生改变。为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱的链接。
由于这些操作与其他容器上的操作的实现方式不同,forward_list
并未定义insert
、emplace
和erase
,而是定义了名为insert_after
、emplace after
和erase_after
的操作。
forward_list
也定义了before_begin
,它返回一个首前( off-the-beginning
)迭代器。这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素(亦即在链表首元素之前添加删除元素)。
9.3.5 、改变容器大小
如下表所描述,我们可以用resize
来增大或缩小容器,与往常一样,array
不支持resize
。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部:
9.3.6 、容器操作可能使迭代器失效
向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器 失效。
- 一个失效的指针、引用或迭代器将不再表示任何元素。使用失效的指针、引用或迭代器是一种严重的程序设计错误,很可能引起与使用未初始化指针一样的问题。
在向容器添加元素后:
- 如果容器是
vector
或string
,且存储空间被重新分配,则指向容器的迭代器指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效; - 对于
deque
,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。 - 对于
list
和forward_list
,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效,这应该不会令人惊讶。毕竟,这些元素都已经被销毁了。
当我们删除一个元素后:
- 对于
list
和forward_list
,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。 - 对于
deque
,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque
的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。 - 对于
vector
和string
,指向被删元素之前元素的迭代器、引用和指针仍有效注意:当我们删除元素时,尾后迭代器总是会失效。
由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对
vector
、string
和deque
尤为重要。
编写改变容器的循环程序
添加/删除 vector
、string
或deque
元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。
- 程序必须保证每个循环步中都更新迭代器、引用或指针。
如果循环中调用的是insert
或erase
,那么更新迭代器很容易。这些操作都返回迭代器,我们可以用来更新:
//傻瓜循环,删除偶数元素,复制每个奇数元素
vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
auto iter = vi.begin(); // 调用begin而不是cbegin,因为我们要改变vi
while (iter != vi.end()){if(*iter % 2){iter = vi.insert(iter, *iter); //复制当前元素iter += 2; //向前移动迭代器,跳过当前元素以及插入到它之前的元素}elseiter = vi.erase(iter);//删除偶数元素//不应向前移动迭代器,iter指向我们删除的元素之后的元素
}
在调用erase
后,不必递增迭代器,因为erase
返回的迭代器已经指向序列中下一个元素。调用insert
后,需要递增迭代器两次。记住,insert
在给定位置之前插入新元素,然后返回指向新插入元素的迭代器。因此,在调用insert
后,iter
指向新插入元素,位于我们正在处理的元素之前。我们将迭代器递增两次,恰好越过了新添加的元素和正在处理的元素,指向下一个未处理的元素。
不要保存end返回的迭代器
如果在一个循环中插入/删除 deque
、string
或 vector
中的元素,不要缓存end
返回的迭代器。
必须在每次插入操作后重新调用end()
,而不能在循环开始前保存它返回的迭代器:
//安全的方法:在每个循环步添加/删除元素后都重新计算end
while (begin != v.end(){//做一些处理++begin; //向前移动begin,因为我们想在此元素之后插入元素begin = v.insert(begin, 42);//插入新值++begin; //向前移动begin,跳过我们刚刚加入的元素
}
9.4、vector对象是如何增长的
vector
在每次重新分配内存空间时都要移动所有元素,但使用此策略后,其扩张操作通常比list
和 deque
还要快。
管理容量的成员函数
如下表所示,vector
和string
类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动。
- 只有当需要的内存空间超过当前容量时,
reserve
调用才会改变vector
的容量。如果需求大小大于当前容量,reserve
至少分配与需求一样大的内存空间(可能更大)。 - 在新标准库中,我们可以调用
shrink_to_fit
来要求deque
、vector
或string
退回不需要的内存空间。此函数指出我们不再需要任何多余的内存空间。但是,具体的实现可以选择忽略此请求。也就是说,调用shrink_to_fit
也并不保证一定退回内存空间。 - 容器的
size
是指它已经保存的元素的数目; capacity
则是在不分配新的内存空间的前提下它最多可以保存多少元素。
vector
的实现采用的策略似乎是在每次需要分配新内存空间时将当前容量翻倍。
每个
vector
实现都可以选择自己的内存分配策略。但是必须遵守的一条原则是:只有当迫不得已时才可以分配新的内存空间。
9.5、额外的string 操作
除了顺序容器共同的操作之外,string
类型还提供了一些额外的操作。这些操作中的大部分要么是提供string
类和C风格
字符数组之间的相互转换,要么是增加了允许我们用下标代替迭代器的版本。
9.5.1、构造string的其他方法
除了已经介绍过的构造函数,以及与其他顺序容器相同的构造函数外,string
类型还支持另外三个构造函数,如下表:
- 通常当我们从一个
const char*
创建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 == "World!!!"
string s8(s1, 16); //抛出一个out_of_range异常
substr 操作
9.5.2、改变string的其他方法
string
类型支持顺序容器的赋值运算符以及assign
、insert
和erase
操作。除此之外,它还定义了额外的insert
和erase
版本。
- 接受下标的版本:下标指出了开始删除的位置,或是
insert
到给定值之前的位置:
s.insert(s.size(), 5, '!'); //在s末尾插入5个感叹号
s.erase(s.size() - 5, 5); //从s删除最后5个字符
- 接受C风格字符数组的
insert
和assign
版本:我们可以将以空字符结尾的字符数组insert
到或assign
给一个string
:
const char *cp = "stately, plump Buck";
s.assign(cp, 7); // s=="stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"
- 我们也可以指定将来自其他
string
或子字符串的字符插入到当前string
中或赋予当前string
:
string s = "some string", s2 = "some other string";
s.insert(0, s2);//在s中位置0之前插入s2的拷贝
//在s[0]之前插入s2中s2[0]开始的s2.size ()个字符
s.insert(0, s2, 0, s2.size());
append 和replace函数
string
类定义了两个额外的成员函数: append
和replace
,这两个函数可以改变string
的内容。
append
操作是在string
末尾进行插入操作的一种简写形式:
string s("C++ Primer"), s2 = s; //将s和s2初始化为"C++ Primer"
s.insert(s.size(), " 4th Ed."); //s == "C++ Primer 4th Ed."s2.append (" 4th Ed."); //等价方法:将"4th Ed."追加到s2;s == s2
replace
操作是调用erase
和insert
的一种简写形式:
//将"4th"替换为"5th"的等价方法
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");//等价方法:s == s2
改变string的多种重载函数
表9.13列出的append
、assign
、insert
和 replace
函数有多个重载版本,这些函数有共同的接口。
assign
和append
函数无须指定要替换string
中哪个部分:assign
总是替换string
中的所有内容,append
总是将新字符追加到string
末尾。replace
函数提供了两种指定删除元素范围的方式。可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。insert
函数允许我们用两种方式指定插入点:用一个下标或一个迭代器。在两种情况下,新元素都会插入到给定下标(或迭代器)之前的位置。
并不是每个函数都支持所有形式的参数。例如,
insert
就不支持下标和初始化列表参数。类似的,如果我们希望用迭代器指定插入点,就不能用字符指针指定新字符的来源。
9.5.3、string搜索操作
string
类提供了6个不同的搜索函数,每个函数都有4个重载版本。表9.14描述了这些搜索成员函数及其参数。
- 每个搜索操作都返回一个
string::size_type
值,表示匹配发生位置的下标。 - 如果搜索失败,则返回一个名为
string::npos
的static
成员。标准库将npos
定义为一个const stringsize_type
类型,并初始化为值-1。由于npos
是一个unsigned
类型,此初始值意味着npos
等于任何string最大的可能大小。
find
函数完成最简单的搜索。它查找参数指定的字符串,若找到,则返回第一个匹配位置的下标,否则返回npos
:
//子字符串"Anna"在"AnnaBelle"中第一次出现的下标
string name ("AnnaBelle");
auto pos1 = name.find("Anna"); // pos1 == 0//搜索(以及其他string操作)是大小写敏感的
string lowercase("annabelle");
pos1 = lowercase.find("Anna"); // posl ==npos
一个更复杂一些的问题是查找与给定字符串中任何一个字符匹配的位置
//定位name 中的第一个数字;
string numbers("0123456789"), name ( "r2d2");
//返回1,即,name中第一个数字的下标
auto pos = name.find_first_of(numbers);//搜索第一个不在参数中的字符
string dept("03714p3");//返回5——字符'p'的下标
auto pos = dept.find_first_not_of(numbers);
9.5.4、compare函数
除了关系运算符外,标准库 string
类型还提供了一组compare
函数,这些函数与C标准库的strcmp
函数很相似。类似 strcmp
,根据s
是等于
、大于
还是小于
参数指定的字符串,s.compare
返回0
、正数
或负数
。
9.5.5、数值转换
字符串中常常包含表示数值的字符。例如,我们用两个字符的
string
表示数值15——字符1’后跟字符’5’。一般情况,一个数的字符表示不同于其数值。数值15如果保存为16
位的short类型,则其二进制位模式为0000000000001111,而字符串"15"存为两个Latin-1
编码的char
,二进制位模式为0011000100110101。第一个字节表示字符’1’,其八进制值为061,第二个字节表示’5’,其Latin-1
编码为八进制值065。
新标准引入了多个函数,可以实现数值数据与标准库string
之间的转换:
int i =42;
string s = to_string(i); //将整数i转换为字符表示形式
double d = stod(s);//将字符串s转换为浮点数
要转换为数值的string
中第一个非空白符必须是数值中可能出现的字符:
string s2 = "pi = 3.14";
//转换s中以数字开始的第一个子串,结果d = 3.14
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
如果
string
不能转换为一个数值,这些函数抛出一个invalid_argument
异常。如果转换得到的数值无法用任何类型来表示,则抛出一个out_of_range
异常。
9.6、容器适配器
除了顺序容器外,标准库还定义了三个顺序容器适配器:stack
、queue
和priority_queue
。
- 适配器(
adaptor
)是标准库中的一个通用概念。
容器、迭代器和函数都有适配器。
- 本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
- 一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。
例如,
stack
适配器接受一个顺序容器(除array
或forward_list
外),并使其操作起来像一个stack
一样。下表列出了所有容器适配器都支持的操作和类型。
定义一个适配器
每个适配器都定义两个构造函数:
- 默认构造函数创建一个空对象;
- 接受一个容器的构造函数铂贝该容器来初始化适配器。
例如,假定deq
是一个 deque<int>
,我们可以用deq
来初始化一个新的stack
,如下所示:
stack<int> stk(deq);//从deq拷贝元素到stk
默认情况下,
stack
和queue
是基于deque
实现的,priority_queue
是在vector
之上实现的。
我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。
//在vector上实现的空栈
stack<string, vector<string>> str_stk;
//str_stk2在vector上实现,初始化时保存svec的拷贝
stack<string, vector<string>> str_stk2(svec) ;
对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要求容器具有添加和删除元素的能力。因此,适配器不能构造在 array
之上。类似的,我们也不能用forward_list
来构造适配器,因为所有适配器都要求容器具有添加、删除以及访问尾元素的能力。
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
头文件中。下表列出了stack
所支持的操作。下面的程序展示了如何使用stack
:
stack<int> intStack; //空栈
//填满栈
for(size_t ix = 0; ix != 10; ++ix)intStack.push(ix); //intStack保存0到9十个数
while (!intStack.empty()) { // intStack中有值就继续循环int value = intstack.top() ;//使用栈顶值的代码intStack.pop(); //弹出栈顶元素,继续循环, 返回值为void
}
每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,而不能使用底层容器类型的操作。
队列适配器
queue
和priority_queue
适配器定义在queue
头文件中。下表列出了它们所支持的操作。
priority_queue
允许我们为队列中的元素建立优先级。
- 新加入的元素会排在所有优先级比它低的已有元素之前。饭店按照客人预定时间而不是到来时间的早晚来为他们安排座位,就是一个优先队列的例子。
- 默认情况下,标准库在元素类型上使用
<
运算符来确定相对优先级。
注:仅供学习参考,如有不足,欢迎指正!!!
相关文章:

C++标准库 -- 顺序容器 (Primer C++ 第五版 · 阅读笔记)
C标准库 -- 顺序容器(Primer C 第五版 阅读笔记)第9章 顺序容器------(持续更新)9.1、顺序容器概述9.2、容器库概览9.2.1 、迭代器9.2.2 、容器类型成员9.2.3 、begin 和 end 成员9.2.4 、容器定义和初始化9.2.5 、赋值和 swap9.2.6 、容器大小操作9.2.7 、关系运算…...

JavaEE初阶学习:文件操作
1.文件 1.认识文件 平时说的文件一般都是指存储再硬盘上的普通文件,形如txt,jpg,MP4,rar等这些文件都可以认为是普通文件,它们都是再硬盘上存储的。 在计算机中,文件可能是一个广义的概念,就…...

【外设零基础通用教程】GPIO 下
【外设零基础通用教程】GPIO 下使用方法GPIO 值输入读取值输出设置值GPIO输入输出应用GPIO输入应用GPIO输出应用文档使用理论补充输出方式推挽输出开漏输出上篇连接:【外设零基础通用教程】GPIO 上,主要是在做视频的时候,发现上篇理论很多&am…...

在window上安装python
在Windows上安装python 1.进入python官网https://www.python.org/ 下载配置环境,点击上方downloads,根据系统选择python环境下载(选择windows) 往下拉查找需要的版本并下载 下载后双击就可以安装python了 如何检验是否安装成功 通过【winr】调出【运行】弹窗,输…...
[hive SQL] 预约业务线
这两天有个数据需求,记录一下。 原始需求说明产品写得很乱不清晰确认了半天无语死了(开始骂人),直接列转换后的问题了 问题1: 现有一张办事预约服务记录表reservation_order,包含字段用户id、服务名称、服务…...

LNMP架构和论坛搭建以及一键部署
数据流向 一、Nginx服务安装 1、关闭防火墙 [rootking ~]# systemctl stop firewalld [rootking ~]# systemctl disable firewalld [rootking ~]# setenforce 0 2、将所需软件包拖入/opt目录下 3、安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make 4、创建运…...

RK3568平台开发系列讲解(设备驱动篇)V4L2程序实现流程
🚀返回专栏总目录 文章目录 一、V4L2 进行视频采集二、命令标识符三、V4L2程序实例3.1、打开设备3.2、查询设备属性3.3、显示所有支持的格式3.4、设置图像帧格式3.5、申请缓冲区3.6、将申请的缓冲帧从内核空间映射到用户空间3.7、将申请的缓冲帧放入队列,并启动数据流3.8、启…...

人工智能中的顶级会议
当搭建好了AI领域的知识架构,即具备了较好的数学、编程及专业领域知识后,如果想在AI领域追踪前沿研究,就不能再只看教材了。毕竟AI领域的发展一日千里,教材上的知识肯定不是最新的。此时,应该将关注的重点转向AI领域的…...
【Python OpenCV】第六天:图像的基础操作
文章目录 一、本期目标二、获取并修改像素值三、获取图像属性(1)img.shape(2)img.size(3)img.dtype四、图像 ROI五、拆分及合并图像通道六、为图像扩边(填充)一、本期目标 获取像素值并修改获取图像的属性(信息)图像的 ROI图像通道的拆分及合并几乎所有这些操作与 Nu…...
2022年陕西省职业院校技能大赛“网络搭建与应用”赛项竞赛试题
2022年陕西省职业院校技能大赛 “网络搭建与应用”赛项 竞赛试题 竞赛说明 一、竞赛内容发布 “网络搭建与应用”赛项竞赛共分三个部分,其中: 第一部分:网络搭建及安全部署项目(500分) 第二部分:服务器配置及应用项目(480分) 第三部分:职业规范与素养(20分) 二、竞赛…...
面经-01
面试java开发工程师 常用数据结构,区别及使用场景 以下是一些常用的数据结构,它们的区别以及适用场景: 数组 (Array): 区别:数组是一种连续内存空间的数据结构,具有固定的大小,用于存储相同类型…...

c/c++:visual studio的代码快捷键,VS设置自定义默认代码,使用快捷键
c: 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,此时学会c的话, 我所知道的周边的会c的同学,可手握10多个offer,随心所欲,而找啥算法岗的,基本gg 提…...
mysql基本语法
-- 显示所有数据库 show databases;-- 创建数据库 CREATE DATABASE test;-- 切换数据库 use test;-- 显示数据库中的所有表 show tables;-- 创建数据表 CREATE TABLE pet (name VARCHAR(20),owner VARCHAR(20),species VARCHAR(20),sex CHAR(1),birth DATE,death DATE );-- 查看…...

出苗率相关论文
文章目录2021Automatic UAV-based counting of seedlings in sugar-beet field and extension to maize and strawberry(Computers and Electronics in Agriculture)2022Detection and Counting of Maize Leaves Based on Two-Stage Deep Learning with UAV-Based RGB Image&am…...

【Kubernetes】StatefulSet对象详解
文章目录简介1. StatefulSet对象的概述、作用及优点1.1 对比Deployment对象和StatefulSet对象1.2 以下是比较Deployment对象和StatefulSet对象的优缺点:2. StatefulSet对象的基础知识2.1 StatefulSet对象的定义2.1.1 下表为StatefulSet对象的定义及其属性࿱…...

选择排序与堆排序
全文目录引言选择排序思路实现堆排序思路实现总结引言 从这篇文章开始,将介绍几大排序算法:选择排序、堆排序、直接插入排序、希尔排序、冒泡排序、快速排序、归并排序以及计数排序。 在本篇文章中要介绍的是选择排序与堆排序,它们都属于选…...

AI绘图体验:想象力无限,创作无穷!(文生图)
基础模型:3D二次元 PIXEL ART (1)16-bit pixel art, outside of caf on rainy day, light coming from windows, cinematic still(电影剧照), hdr (2) 16-bit pixel art, island in the clouds, by studio ghibli(吉卜力工作室…...

【图片分割】【深度学习】Windows10下SAM官方代码Pytorch实现
【图片分割】【深度学习】Windows10下SAM官方代码Pytorch实现 提示:最近开始在【图片分割】方面进行研究,记录相关知识点,分享学习中遇到的问题已经解决的方法。 文章目录【图片分割】【深度学习】Windows10下SAM官方代码Pytorch实现前言SAM模型运行环境安装打开cmd,执行下面的…...

“我用 ChatGPT 造了一个零日漏洞,成功逃脱了 69 家安全机构的检测!”
一周以前,图灵奖得主 Yoshua Bengio、伯克利计算机科学教授 Stuart Russell、特斯拉 CEO 埃隆马斯克、苹果联合创始人 Steve Wozniak 等在内的数千名 AI 学者、企业家联名发起一则公开信,建议全球 AI 实验室立即停止训练比 GPT-4 更强大的模型࿰…...

Compose (14/N) - 附带效应 EffectPI
一、概念 纯函数函数与外界交换数据只能通过形参和返回值进行,不会对外界环境产生影响。副作用函数内部与外界进行了交互,产生了其它结果(如修改外部变量)。组合函数是用来声明UI的,所以跟UI描述不相关的操作都是副作…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...

Visual Studio Code 扩展
Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后,命令 changeCase.commands 可预览转换效果 EmmyLua…...

客户案例 | 短视频点播企业海外视频加速与成本优化:MediaPackage+Cloudfront 技术重构实践
01技术背景与业务挑战 某短视频点播企业深耕国内用户市场,但其后台应用系统部署于东南亚印尼 IDC 机房。 随着业务规模扩大,传统架构已较难满足当前企业发展的需求,企业面临着三重挑战: ① 业务:国内用户访问海外服…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...

鸿蒙Navigation路由导航-基本使用介绍
1. Navigation介绍 Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(Nav…...