文章目录
- 概述
- 所有容器都支持的操作
- 迭代器
- 迭代器支持的操作
- 迭代器支持的算术运算
- 容器类型
- size_type
- iterator 和 const_iterator
- 容器定义和初始化
- 拷贝初始化
- 顺序容器独有的构造函数(array除外)
- array的初始化
- 与内置数组类型的区别
- 6种初始化方法(以vector为例)
- 赋值和swap
- 使用assign(仅顺序容器)
- 使用swap
- swap仅交换容器内部的数据结构
- swap的效率
- 容器的大小操作
- 成员函数
- sizeof()
- size和capacity
- 为什么list或array没有capacity成员函数
- reserve()
- shrink_to_fit()
- 关系运算符
- capacity不会影响相等性判定
- 顺序容器独有操作
- 添加元素
- push_back
- push_front
- insert
- 格式
- 使用insert的返回值
- 使用emplace操作
- 访问元素
- 访问成员函数返回的是引用
- 下标操作和安全的随机访问
- 删除元素
- pop_front和pop_back
- erase
- forward_list独有的操作
- resize改变容器大小
- 容器的相关操作导致的迭代器、指针、引用失效
- vector的空间分配问题
- 空间分配策略
- 在重新分配内存空间时,vector采用的策略似乎是将旧的内存空间容量翻一倍:
- string类型独有的操作
- 构造string的方法
- 从一个char的vector初始化string
- 子字符串(substr)操作
- 改变string内容的操作
- 支持下标的insert和erase版本
- 实例
- 关于string的insert使用须知
- 接受C风格字符数组的insert和assign版本
- assign
- insert
- string和C风格字符数组
- string与string
- append函数和replace函数
- string搜索操作
- 搜索操作的返回值
- find()
- find_first_of() and find_first_not_of()
- 指定搜索开始位置从而实现遍历搜索想要的子串:
- string的比较操作——compare()函数
- 数值转换
- 报错
- 容器适配器
- 概念
- 定义
- 适配器可以使用哪些容器是有限制的
- 重载
- 栈适配器
- 队列适配器
概述
所有顺序容器都提供了快速顺序访问元素的能力。
但是在两个方面不可兼得:
· 向容器中间添加或删除元素
· 非顺序访问容器中元素
链表的添加删除操作很快,但是访问一个特定元素只能遍历整个容器。
string和vector用下标访问特定元素是很快的,但是向中间位置做增删操作会很复杂。由于其是一段连续内存空间组成,如果插入的数据过多,则重新申请一块足够大的内存空间,将原来的数据复制过来,插入新数据,之后释放原空间
队列是一块块连续内存空间拼接而成,如果插入的数据过多,则找一块足够大的空间插入数据,将新空间与原deque连接在一起,关于增删和访问的特点与string和vector类似,在中间位置增删元素的代价很高,但是在两端增删元素的速度堪比链表的增删速度。
array比内置数组更安全也更易于使用。
PS:如果程序只有在读取输入时才需要在中间位置插入元素,随后需要随机访问元素,则可以考虑在输入阶段使用list,输入完成将list中的内容拷贝到vector中。
所有容器都支持的操作
为什么forward_list不支持size操作?
forward_list设计目标是达到与最好的手写单向链表数据结构相当的性能。因此,其没有size操作,因为保存或计算大小会比手写链表多出额外的开销。
迭代器
迭代器支持的操作
forward_list迭代器不支持自减运算符(–)
迭代器支持的算术运算
下表这些算术运算只能应用于string、vector、deque、array的迭代器。list迭代器只支持递增、递减、==、!=,不支持比较大小。
原因在于vector和deque将元素在内存中连续保存;而list则是将元素以链表方式存储,这样的话指针的大小关系与它们指向的元素的前后关系并不一定是吻合的,实现"<“或者”>"将会非常困难和低效。
容器类型
size_type
· 一个无符号类型的值
· 足够放下任何string对象的大小
· string类的size函数返回值的类型
iterator 和 const_iterator
拥有迭代器的标准库类型用iterator 和const_iterator来表示迭代器所指对象的类型。
- 后者是前者的权限缩放,能读取但不能修改所指的元素值。
- 如果vector对象或string对象是常量,只能使用const_iterator;不是常量则两者都能使用。
容器定义和初始化
拷贝初始化
拷贝初始化有两种方式:
- 拷贝整个容器。两个容器的类型及其元素类型必须匹配
- 拷贝一个迭代器对[begin,end)指定的范围。元素类型可以不同,只要能将拷贝的元素转换为要初始化的容器的元素类型即可,容器类型也可以不同。
// 列表初始化
list<string> authors = {"zhangsan", "lisi", "wangwu"};
vector<const char*> vc = {"a", "bc", "def"};list<string> ls(authors); // 正确
deque<string> ds(authors); // error:容器类型不匹配
vector<string> vs(vc); // error:元素类型不匹配
forward_list<string> fs(vc.begin(), vc.end());
// 正确:元素类型可以转换
顺序容器独有的构造函数(array除外)
接收一个容器大小和一个元素初始值,不提供元素初始值的话,标准库会创建一个值初始化器。
如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素类型没有默认构造函数,除了大小参数外,必须指定一个显式的元素初始值。
PS:说这样的构造函数时顺序容器独有的是因为关联容器不支持构造函数接收大小参数。
array的初始化
array与内置数组一样,大小是类型的一部分。定义时,要同时指定元素类型和容器大小:
array<int, 10> // 类型为保存10个int的数组
array<string> // error:未指定容器大小
这也就解释了为什么顺序容器独有的构造函数不适合array
由于大小是array类型的一部分,而构造函数会确定容器的大小,要么隐式地,要么显式地。而允许用户向array构造函数传递大小参数地行为是多余的,而且也容易出错。
array执行列表初始化需要注意的地方:
- 默认构造的array是非空的:包含了与大小一样多的、被默认初始化的元素。
- 列表初始化时初始值的数目必须等于或小于array的大小,小于的话array中剩余元素会执行值初始化
- 如果元素类型是一个类类型,必须要有一个默认构造函数,使值初始化能够进行
与内置数组类型的区别
虽然我们不能对内置数组类型进行拷贝或者对象赋值操作,但array并无此限制:
int arr[5] = {0,1,2,3,4};
int arr1[5] = arr; //error:不允许拷贝初始化
int arr2[5];
arr2 = arr; //error:不允许赋值
array<int, 5> data = {0,1,2,3,4};
array<int, 5> copy = data; //正确:只要数组类型匹配即合法
但由于右边运算对象的大小可能与左边运算对象的大小不同,因此array类型不允许使用花括号包围的值列表进行赋值。
array<int, 5> a;
a = {3,4}; // error:不允许将一个花括号列表赋予数组
array进行拷贝或者对象赋值操作要注意的地方:
- array要求初始值的类型必须与要创建的容器类型相同
- 要求元素类型和大小也都一样,因为大小是array的一部分
6种初始化方法(以vector为例)
vector<int> ilist1; // 默认初始化,vector为空——size返回0,
//表明容器中尚未有元素;capacity返回0,意味着尚未分配存储空间。
//这种初始化方式适合于元素个数和值未知,需要在程序运行中动态添加的情况。vector<int> ilist2(ilist); // 容器拷贝初始化,
//ilist2初始化为ilist的拷贝,ilist必须与ilist2类型相同,
//即也是int的vector类型,ilist2将具有与ilist相同的容量和元素。vector<int> ilist2_1 = ilist; // 等价方式

vector<int> ilist = {1, 2, 3.0, 4, 5, 6, 7}; // ilist初始化为列表中
//元素的拷贝,列表中的元素类型必须与ilist的元素类型相容,
//在本例中必须是与整型相容的数值类型。对于整型,会直接拷贝其值,
//对于其他类型则需进行类型转换(如3.0转换为3)。这种初始化方式适合
//元素数量和值预先可知的情况。vector<int> ilist_1{1, 2, 3.0, 4, 5, 6, 7}; // 等价方式vector<int> ilist3(ilist.begin()+2, ilist.end()-1); // 范围初始化,
//ilist3初始化为两个迭代器指定范围中的元素的拷贝,
//范围中的元素类型必须与ilist3的元素类型相容,在本例中ilist3被初始化为
//{3, 4, 5, 6}。注意,由于只要求范围中元素类型与待初始化的容器的元素类型相容,
//因此,迭代器来自于不同类型的容器是可能的,例如,用一个double的list的范围
//来初始化ilist3是可行的。另外,由于构造函数只是读取范围中的元素并进行拷贝,
//因此使用普通迭代器还是const迭代器来指出范围并无区别。这种初始化方法特别
//适合于获取一个序列的子序列。vector<int> ilist4(7); // 默认值初始化,ilist4中将包含7个元素,
//每个元素进行缺省的值初始化,对于int,也就是被赋值为0,因此ilist4被初始化为
//包含7个0。当程序运行初期元素大致数量可预知,而元素的值需动态获取时,可采用
//这种初始化方式。vector<int> ilist5(7, 3); // 指定值初始化,ilist5被初始化为
//包含7个值为3的int。
赋值和swap
使用assign(仅顺序容器)
赋值运算符要求左边和右边的运算对象具有相同的类型。但顺序容器(array除外)允许我们使用assign从一个不同但相容的类型赋值。
assign第二个版本接受一个整型值和一个元素值。
由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。
使用swap
swap仅交换容器内部的数据结构
调用swap后,svec1将包含24个string元素,svec2将包含10个string。除array外,交换两个容器内容的操作很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构。
元素本身并未交换意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。 它们仍指向swap操作之前所指向的那些元素。 但是,在swap之后,这些元素已经属于不同的容器了。
vector<int> a{0,1,2,3,4};
vector<int> b{5,6,7,8,9};
auto abeg = a.begin();
auto bend = b.end()-1;
cout << *abeg << endl;
cout << *bend << endl;
cout << &abeg << endl;
cout << &bend << endl;
swap(a,b);
cout << *abeg << endl;
cout << *bend << endl;
cout << &abeg << endl;
cout << &bend << endl;
与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效。
swap的效率
除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
与其他容器不同,swap两个array会真正交换它们的元素。因此,交换两个array所需的时间与array中元素的数目成正比。
同样的,对于array,在swap操作之后,指针、引用和迭代器所绑定的元素位置保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
array<int, 5> a{0,1,2,3,4};array<int, 5> b{5,6,7,8,9};auto abeg = a.begin();auto bend = b.end()-1;cout << *abeg << endl;cout << *bend << endl;cout << &abeg << endl;cout << &bend << endl;swap(a,b);cout << *abeg << endl;cout << *bend << endl;cout << &abeg << endl;cout << &bend << endl;
PS:在新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap。而早期标准库版本只提供成员函数版本的swap。非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。
容器的大小操作
成员函数
sizeof()
sizeof():容器本身的大小
例:
sizeof(vector)为3 * sizeof(void*)个字节,由指向begin,指向end,指向capacity的三个值组成
size和capacity
size():已经保存的元素的数目
capacity():当前所在内存空间最多可以保存的元素数量。
为什么list或array没有capacity成员函数
list是链表,当有新元素加入时,会从内存空间中分配一个新节点保存它;当从链表中删除元素时,该节点占用的内存空间会被立刻释放。因此,一个链表占用的内存空间总是与它当前保存的元素所需空间相等(换句话说,capacity总是等于size)。
而array是固定大小数组,内存一次性分配,大小不变,不会变化。
因此它们均不需要capacity。
reserve()
reserve()并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。
只有当需要的内存空间大于当前容量时,reserve才会改变vector的容量,至少分配与新需求一样大的空间(可能更大)。
新需求小于或等于当前容量的话,reserve什么也不做。即使小于也不会退回内存空间(退回是shrink_to_fit做的)。因此,在调用reserve之后,capacity将会大于或等于传递给reserve的参数。
shrink_to_fit()
此函数指出我们不再需要任何多余的空间,但具体的实现可以忽略此请求。即,调用此函数也不能保证一定退回内存空间。
关系运算符
- 每个容器类型都支持相等运算符(==和!=)
- 除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=)
- 关系运算符左右两边的运算对象必须容器类型相同,且元素类型相同
- 两个容器大小相同元素相等则容器相等
- 两个容器大小不同,但小的是大容器的前缀子序列那么较大容器大
- 两个容器都不是另一个容器的前缀子序列则比较结果取决于第一个不相等的元素的比较结果
- 只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器
capacity不会影响相等性判定
顺序容器独有操作
添加元素
push_back
对push_back的调用在container尾部创建了一个新的元素,将container的size增大了1。该元素的值为word的一个拷贝。container的类型可以是list、vector或deque。
由于string是一个字符容器,我们也可以用push_back在string末尾添加字符:
push_front
将元素插入到容器头部,list、forward_list和deque容器支持。
insert
格式
接受一个迭代器作为第一个参数,简单理解为,insert(iterator, )
- 可以后跟一个值 insert(iterator, “hello”)
- 可以后跟值初始化 insert(iterator, 10 , 'dksla")
- 可以后跟列表初始化 insert(iterator, {“1”, “2”, “3”})
- 可以后跟范围初始化 insert(iterator, iterator1, iterator2 )
如果选择第四种方式传递给insert一对迭代器,它们不能指向添加元素的目标容器。
使用insert的返回值
通过使用insert的返回值,可以在容器中一个特定位置反复插入元素:
理解这个循环是如何工作的非常重要,特别是理解这个循环为什么等价于调用push_front尤为重要。
在循环之前,我们将iter初始化为lst.begin()。第一次调用insert会将我们刚刚读入的string插入到iter所指向的元素之前的位置。insert返回的迭代器恰好指向这个新元素。我们将此迭代器赋予iter并重复循环,读取下一个单词。只要继续有单词读入,每步while循环就会将一个新元素插入到iter之前,并将iter改变为新加入元素的位置。此元素为(新的)首元素。因此,每步循环将一个新元素插入到list首元素之前的位置。
使用emplace操作
当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。
而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。
例如,假定c保存Sales_data元素:
其中对emplace_back的调用和第二个push_back调用都会创建新的Sales_data对象。
在调用emplace_back时,会在容器管理的内存空间中直接创建对象。
而调用push_back则会创建一个局部临时对象,并将其压入容器中。
emplace函数在容器中直接构造元素。传递给emplace函数的参数根据元素类型而变化,参数必须与元素类型的构造函数相匹配:
(iter指向c中一个元素,其中保存了Sales_data元素)
访问元素
关于front和back的用法实例:
此程序用两种不同方式来获取c中的首元素和尾元素的引用。直接的方法是调用front和back。而间接的方法是通过解引用begin返回的迭代器来获得首元素的引用,以及通过递减然后解引用end返回的迭代器来获得尾元素的引用。
这个程序有两点值得注意:迭代器end指向的是容器尾元素之后的(不存在的)元素。为了获取尾元素,必须首先递减此迭代器。另一个重要之处是,在调用front和back(或解引用begin和end返回的迭代器)之前,要确保c非空。如果容器为空,if中操作的行为将是未定义的。
访问成员函数返回的是引用
在容器中访问元素的成员函数(即,front、back、下标和at)返回的都是引用。 如果容器是一个const对象,则返回值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值:
与以前一样,如果我们使用auto变量来保存这些函数的返回值,并且希望使用此变量来改变元素的值,必须记得将变量定义为引用类型。
下标操作和安全的随机访问
提供快速随机访问的容器(string、vector、deque和array)也都提供下标运算符。下标运算符接受一个下标参数,返回容器中该位置的元素的引用。给定下标必须“在范围内”(即,大于等于0,且小于容器的大小)。
保证下标有效是程序员的责任,下标运算符并不检查下标是否在合法范围内。使用越界的下标是一种严重的程序设计错误,而且编译器并不检查这种错误。
如果我们希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常:
删除元素
pop_front和pop_back
这些操作返回void,若需要弹出元素值,则必须在执行弹出前保存它。
erase
成员函数erase从容器中指定位置删除元素。
- 可以删除由迭代器指定的单个元素
- 也可以删除由一对迭代器指定的范围内的所有元素
两种形式erase都返回指向删除的最后一个元素之后(insert返回第一个新增元素之前)位置的迭代器。
第二种方式的例子:
elem = slist.erase(elem1, elem2); // 调用后,elem == elem2
迭代器elem1指向我们要删除的第一个元素,elem2指向我们要删除的最后一个元素之后的位置。当两个迭代器相等时,什么也不会发生,容器保持不变。
因为上述规则,因此可以使用begin和end获得的迭代器作为参数调用erase,以起到clear的作用:
slist.clear(); // 删除容器中所有元素
slist.erase(slist.begin(), slist.end()); // 等价调用
forward_list独有的操作
当删除或添加一个元素,被操作元素之前(elem3)的元素(elem2)的后继会发生改变。因此从逻辑上讲,需要访问被操作元素的前驱,以改变前驱的链接。但是forward_list是单向链表。在一个单向链表中,没有简单的方法来获取一个元素的前驱。因此在forward_list中增删操作是通过传入被操作元素(elem3)之前的迭代器(elem2)来完成的。返回的迭代器是最后一个新元素的迭代器(添加操作)或者被删元素之后的迭代器(删除操作)。
resize改变容器大小
实例:
同样的,如果resize向一个保存类类型元素的容器添加新元素,则我们必须提供初始值(即必须有两个参数),或者元素类型必须提供一个默认构造函数(接收单个参数的resize版本)。
容器的相关操作导致的迭代器、指针、引用失效
在向容器添加元素后:
· 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。
· 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。 如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
· 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效, 这应该不会令人惊讶。毕竟,这些元素都已经被销毁了。当我们删除一个元素后:
· 对于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
· 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素, 这些也不会受影响。
· 对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍有效。 注意:当我们删除元素时,尾后迭代器总是会失效。
vector的空间分配问题
空间分配策略
我们都知道vector将元素连续存储,且容器大小是可以变化的。那么在内存中,如果vector存储地址的空间已经饱和,没办法容纳新的元素,此时应该怎么处理呢?
简单地将其添加到内存中的其他位置吗?
显然这样是不行的,vector不同于链表结构,可没有指向下一个节点的指针。
正确答案是容器必须分配新的内存空间来保存已有元素和新元素:
- 将已有元素从旧位置移动到新空间中
- 然后添加新元素
- 释放旧存储空间
这样操作同时也有一个隐患:如果新分配的空间只能新添加一个元素就达到饱和,岂不是说我们每添加一个新元素,vector就要执行一次这样的内存分配和释放操作?这样的话性能会慢到不可接受。
为了避免上述代价,当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可用来保存更多的新元素。以此来减少容器空间重新分配次数。
上述分配策略实际性能也表现得足够好——虽然vector每次重新分配内存空间时都要移动所有元素,但其扩张操作通常比list和deque还要快。
虽然分配策略的选择有很多,但是都应遵循一个原则:确保用push_back向vector添加元素的操作有高效率。从时间上说,就是通过在一个初始为空的vector上调用n次push_back来创建一个n个元素的vector,所花费时间不能超过n的常数倍。
在重新分配内存空间时,vector采用的策略似乎是将旧的内存空间容量翻一倍:
只有在执行insert时size与capacity相等(饱和状态无法添加新元素)、或者调用resize/reserve时给定的大小超过当前capacity,vector才可能重新分配空间。
string类型独有的操作
构造string的方法
几个实例:
当用一个c风格字符串初始化string时,我们可以提供一个形如上例:数组名(cp)或者数组名+下标(cp+6)或者(cp,6)形式的的开始位置和一个计数值。
-
通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。
-
如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。
-
如果我们未传递计数值且数组也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。
当从一个string拷贝字符时,我们可以提供一个可选的开始位置(仅数组下标,不可以s1+6 的形式)和一个计数值。
- 开始位置必须小于或等于给定的string的大小。如果位置大于size,则构造函数抛出一个out_of_range异常。
- 如果我们传递了一个计数值,则从给定位置开始拷贝这么多个字符。不管我们要求拷贝多少个字符,标准库最多拷贝到string结尾,不会更多。
从一个char的vector初始化string
两种方法:
- 使用迭代器(下例中的s3)
- 使用data成员函数(下例中的s4),将data返回值作为string的构造函数的第一个参数,将vector的size返回值作为第二个参数,即可获取vector中的数据,将其看作一个字符数组来初始化string。
关于data成员函数:
vector提供了data成员函数,其返回类型为const_pointer,返回其内存空间的首地址 (但是从上图中可以看出vector的data成员函数返回的是容器中的元素,并且data返回的地址和begin迭代器返回的地址也不一样)。
子字符串(substr)操作
如果开始位置加上计数值大于string的大小(s4),则substr会调整计数值,只拷贝到string的末尾。
改变string内容的操作
assign和append函数无须指定要替换string中哪个部分:assign总是替换string中的所有内容,append总是将新字符追加到string末尾。
replace函数提供了两种指定删除元素范围的方式。可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。
insert函数允许我们用两种方式指定插入点:用一个下标或一个迭代器。在两种情况下,新元素都会插入到给定下标(或迭代器)之前的位置。
可以用好几种方式来指定要添加到string中的字符。新字符可以来自于另一个string,来自于一个字符指针(指向的字符数组),来自于一个花括号包围的字符列表,或者是一个字符和一个计数值。当字符来自于一个string或一个字符指针时,我们可以传递一个额外的参数来控制是拷贝部分还是全部字符。
支持下标的insert和erase版本
实例
关于string的insert使用须知
首先需要明确的一点是insert是不支持string迭代器与常量字符串两个参数完成insert操作的:
但是支持其他包含string的容器(例如vector)的迭代器与常量字符串两个参数完成insert操作:
因此string有了支持下标的insert版本,这样就能够用两个参数实现insert:
如果实在想要使用string的迭代器和一个字符常量进行insert,那么可以将要添加的常量字符串赋给string变量,使用string变量的的迭代器作为第二个和第三个参数,也就是用三个参数来实现insert功能:
接受C风格字符数组的insert和assign版本
assign
通过assign替换s的内容。我们赋予s的是从c所指向的地址开始的四个字符。
计数值(第二个参数)必须小于或等于c指向的数组中的字符数(不包括结尾的空字符)。如果大于则会出现未定义的行为:
insert
string和C风格字符数组
string为空串:
将c[5]开始到结尾空字符之前的字符拷贝到s[0]之前的位置。因为string是空串,因此拷贝到s[size()]之前(非空串的话也就是从尾部添加元素)也是同样的效果:
string非空串:
指定长度:
但不支持这种格式:
如想使用上述格式,可以参照下面的方法⬇:
值得一提的是,C风格字符数组的指针亦可起到容器中迭代器的作用:
string与string
也可以指定将来自其他string或子字符串的字符插入到当前string中,或者赋予当前string:
也可以指定长度:
子串:
append函数和replace函数
append用于在string末尾进行插入字符串
操作,push_back仅能插入字符
。
replace是调用erase和insert的一种简写形式(替换一部分字符其实也就是先删除再添加的操作),其作用是将指定长度的字串,替换为新的字串,新的字串长度不需要和指定长度相同。
string搜索操作
搜索操作的返回值
string类的搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为string::npos的static成员。标准库将npos定义为一个const string::size_type类型,并初始化为值-1.由于npos是一个unsigned类型,此初始值意味着npos等于任何string最大的可能大小。
find()
找到则返回第一个匹配位置的下标,否则返回npos.
搜索(以及其他string操作)是区分大小写的:
find_first_of() and find_first_not_of()
string name("zhang2021san42");string birthday("2021li4si2");string numbers("0123456789");string::size_type pos1 = name.find_first_of(numbers);// name中第一个数字的下标,cout << pos1 << endl;unsigned pos2 = birthday.find_first_not_of(numbers);// birthday中第一个非数字字符的下标cout << pos2 << endl;auto pos3 = name.find_last_of(numbers);// 在name中查找numbers中任何一个字符最后一次出现的位置cout << pos3 << endl;
指定搜索开始位置从而实现遍历搜索想要的子串:
string::size_type pos1 = 0;while ((pos1 = name.find_first_of(numbers,pos1)) != string::npos) {// 循环搜索name中的子字符串numbers的出现下标cout << pos1 << ends << name[pos1] << endl;pos1++; // 忽略了递增则会死循环}
string的比较操作——compare()函数
类似strcmp,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0、正数或负数。
数值转换
新标准引入了多个函数,可以是心啊数值数据与标准库string之间的转换:
要转换为数值的string中第一个非空白字符必须是数值中可能出现的字符:
name中首先出现的是“z”,因此会发生错误。
可以看到,由find_first_of()获得name中第一个可能是数值一部分的字符的下标。返回给substr(),substr截取从该下标到末尾的字符,将其返回给stod(),stod()读取此参数,处理其中的字符,直到遇到不可能是数值的一部分的字符就停止。然后将这个范围内的字符串表示形式转换为对应的双精度浮点值。
提取字符串中全部的数值字符然后进行数值转换:
感觉上一个例子只能提取一部分字符有遗憾,自己尝试着写了提取全部数值字符的代码,可能不够简洁,有待优化:
string name("zhang2021.5san4.2");string numbers("+-.0123456789");double d; // string要转换为doubleunsigned pos = 0; // 从下标0开始搜索数值字符string over; // 存储数值字符的stringwhile ((pos = name.find_first_of(numbers, pos)) != string::npos) {string s; // 存储每一次循环搜出来的数值字符s = name.substr(pos); // 将数值字符起始位置到整体末尾的字符生成子串auto pos1 = s.find_first_not_of(numbers);// 从非数值字符位置结束if(pos1 != string::npos){over.append(name.substr(pos, pos1));pos += pos1;}else{ // 可能会有以数值字符结束的字符// 因此仅以上面的非数值字符结束是不够的// 还需要以最后出现的数值字符作为结尾auto pos2 = s.find_last_of(numbers)+1;over.append(name.substr(pos, pos2));pos += pos2;}cout << over << endl; // 调试的一部分//用来观察每次循环结束的存储数值字符的string的值}d = stod(over); // 将存储数值字符的string转化为doublecout << d << endl;
运行结果:
格式要求:
- string参数中第一个非空白符必须是符号(+ 或 -)或数字。
- 它可以以0x或0X开头来表示十六进制数。
- 对那些将字符串转换为浮点值的函数,string参数也可以以小数点(.)开头,并可以包含e或E来表示指数部分。
- 对于那些将字符串转换为整型值的函数,根据基数不同, string参数可以包含字母字符,对应大于数字9的数。
将string转换为16进制数:
报错
· 如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常。
· 如果转换得到的数值无法用任何类型来表示,则抛出一个out_of_range异常。
容器适配器
概念
标准库定义了三个顺序容器适配器:stack、queue和priority_queue。容器、迭代器喝函数都有适配器。一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
定义
每个适配器都定义了两个构造函数:
- 默认构造函数:创建一个空对象
- 拷贝构造函数:接受一个容器、拷贝该容器来初始化适配器
默认情况下:stack和queue是基于deque实现的,priority_queue是在vector上实现的。
适配器可以使用哪些容器是有限制的
- 所有适配器都要求容器具有添加和删除元素的能力,因此适配器不能构造在array上。
- 所有适配器都要求容器具有访问尾元素的能力,因此适配器不能构造在forward_list上。
- stack只要求push_back、pop_back和back操作,因此可以使用除array和forward_list之外的任何容器类型来构造stack。
- queue适配器要求back、push_back、front和push_front,因此它可以构造于list或deque之上,但不能基于vector构造(于vector而言,对首元素进行操作(front/push_front)会移动后面所有元素,效率较差)。
- priority_queue除了front、push_back和pop_back操作之外还要求随机访问能力,因此它可以构造于vector或deque之上,但不能基于list构造(list只能从头节点开始进行遍历,无法随机访问)。
重载
可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型:
vector<string> svec;stack<string, vector<string>> str_stk;stack<string, vector<string>> str_stk2(svec);
栈适配器
stack类型定义在stack头文件中。
每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,而不能使用底层容器类型的操作。例如:
s.push(item);
虽然stack是基于deque实现的,但我们不能在一个stack上直接使用deque操作——调用push_back,而必须使用stack自己的操作——push。
队列适配器
queue和priority_queue适配器定义在queue头文件中。
priority_queue的内部数据结构为 堆
。
PS:上表中q.pop()会删除元素,表中表述有误:
标准库queue使用一种先进先出(first-in,first-out,FIFO)的存储和访问策略。进入队列的对象被放置到队尾,而离开队列的对象则从队首删除。
priority_queue允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。
对于基本数据类型(int,char,double),priority_queue的排序是默认是数值越大越优先。
#默认大根堆
priority_queue<int> que;
#greater是小根堆,注意greater<int>与>之间的空格
priority_queue<int, vector<int>, greater<int> > que;
#less是大根堆,注意less<int>与>之间的空格
priority_queue<int, vector<int>, less<int> > que;