9.1 顺序容器概述
标准库中的顺序容器在以下方面都有不同的性能折中:(P292)
- 向容器添加或从容器中删除元素的代价;
- 非顺序访问容器中元素的代价。
(一)确定使用哪种顺序容器
1、选择容器的基本原则:(P293)
- 除非你有很好的理由选择其他容器,否则应使用vector。
- 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list。
- 如果程序要求随机访问元素,应使用vector或deque。
- 如果程序要求在容器的中间插入或删除元素,应使用list或forward_list。
- 如果程序需要在头尾位置插入或删除元素,但不会再中间位置进行插入或删除操作,则使用deque。
9.2 容器库概览
(一)对容器可以保存的元素类型的限制
1、顺序容器几乎可以保存任意类型的元素。(P294)
但元素为没有默认构造函数的类型时,需要注意。
//可以定义一个容器,其元素的类型是另一个容器
vector<vector<string>> lines; //vector的vector
//假定noDefault是一个没有默认构造函数的类型
vector<noDefault> v1(10,int); //正确:提供了元素初始化器
vector<noDefault> v2(10); //错误:必须提供一个元素初始化器
(二)迭代器
1、一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。(P296)
注意:第二个迭代器从来都不会指向范围中的最后一个元素,而是指向尾元素之后的位置。
2、这种元素范围被称为左闭合区间:[begin,end) 。(P296)
3、对构成范围的迭代器的要求:
- 它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置
- end不在begin之前
4、容器的类型成员:
//iter是通过list<string>定义得一个迭代器类型
list<string>::iterator iter;
//count是通过vector<int>定义的一个difference_type类型
vecto<int>::difference_type count;
(三)begin和end成员
1、begin和end有多个版本:(P298)
带r的版本返回反向迭代器;以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
2、实际上有两个名尾begin的成员:一个是const成员,返回容器的const_iterator类型,另一个是非常量(非const)成员,返回容器的iterator类型。(P298)
练习9.10:下面4个对象分别是什么类型?
vector<int> v1;
const vector<int> v2;
auto it1 = v1.begin(); //iterator类型
auto it2 = v2.begin(); //const_iterator类型
auto it3 = v1.cbegin(); //const_iterator类型
auto it4 = v2.cbegin(); //const_iterator类型
(四)容器定义和初始化
1、拷贝初始化
①当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。(P300)
//每个容器有三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton","Shakesperar","Austen"};
vector<const char*> articles = {"a","an","the"};
list<string> list2(authors); //正确:类型匹配
deque<string> authList(authors); //错误:容器类型不匹配
vector<string> words(articles); //错误:容器类型必须匹配
②当传递迭代器参数来拷贝一个范围时,容器类型和元素类型都可以不同。(P300)
forward_list<string> words(articles.begin(),articles.end());
2、列表初始化
list<string> authors = {"Milton","Shakesperar","Austen"};
vector<const char*> articles = {"a","an","the"};
3、直接初始化
顺序容器(array除外)还提供另一个构造函数,可接受一个容器大小和一个(可选的)元素初始值。(P300)
4、array的创建和初始化
标准库array具有固定大小(P301)
//定义一个array时,除了指定元素类型,还要指定容器大小
array<int,42> //类型为:保存42个int的数组
array<string,10> //类型为:保存10个string的数组
//初始化
array<int,10> ial; //10个默认初始化的int
array<int,10> iaw2 = {0,1,2,3,4,5,6,7,8,9}; //列表初始化
5、我们不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。(P301)
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
(六)容器大小操作
1、除了forward_list,每个容器类型都有三个与大小相关的操作:size()、empty()和max_size()。forward_list支持max_size和empty,但不支持size。(P304)
(七)关系运算符
1、关系运算符作用两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。(P304)
9.3 顺序容器操作
(一)向顺序容器添加元素
(二)使用push_back
1、除了array和forward_list之外,每个顺序容器都支持push_back。(P306)
(三)使用push_front
1、只有list、forward_list和deque容器支持push_front。(P306)
(四)在容器中的特定位置添加元素(使用insert)
1、insert成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。(P307)
2、每个insert函数都接受一个迭代器作为其第一个参数。
3、insert函数将元素插入到迭代器所指定的位置之前。
(五)访问元素
1、直接访问首位元素:调用front和back。
2、间接访问首位元素:使用迭代器,解引用begin和end。
3、迭代器end指向的是容器尾元素之后(不存在的)元素。
4、访问元素之前,要确保容器非空。
(六)删除元素
【注意】
forward_list有特殊版本的erase: 当在forward_list中添加或删除元素时,我们必须要关注两个迭代器——一个指向我吗要处理的元素,另一个指向其前驱元素。(P313)
(七)改变容器大小
【注意】
如果容器保存的是类类型元素,且resize向容器添加新元素,则我们必须提供初始值,或者元素类型必须提供一个默认构造函数。(P314)
(八)容器操作可能使迭代器失效
1、管理迭代器:由于迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector,string和deque尤为重要。(P315)
2、不要保存end返回的迭代器,当我们添加、删除vector或string的元素后,或在deque中首元素之外任何位置添加、删除元素后,原来end返回的迭代器总是失效。
错误例子:
正确例子:
【练习所得】
1、std::list是顺序容器,但不可随机访问,只能遍历整个容器(见P292),所以其迭代器只支持++和–这种双向链式操作。
练习 9.31:
#include<list>
#include<iostream>
using namespace std;
int main(int argc, char**argv)
{
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.insert(it, *it);
it++;
it++;
//it += 2; list迭代器只支持++和--
}
else
{
it = lst.erase(it);
}
}
it = lst.begin();
while (it != lst.end())
{
cout << (*it) << endl;
it++;
}
system("pause");
}
9.4 vector对象是如何增长的
1、vector将元素连续存储(string容器也是)。(P356)
2、向vector或string容器添加元素:如果没有空间容纳新元素,容器不可能简单地将它添加到内存中其他位置(因为元素必须连续存储)。容器必须分配新的内存空间来保存已有元素合新元素,将已有元素从旧位置移动到新空间中,然后添加新元素,释放旧存储空间。(P356)
3、为避免内存繁复分配和释放带来的性能变慢的代价,在不得不获取新内存空间时,vector和string的实现会分配比新的空间需求更大的内存空间。(P357)
【注意】
①shrink_to_fit只是一个请求,并不保证一定退回内存空间;
②capacity和size的区别:size是容器已经保存的元素个数,capacity是容器在不分配新的内存空间前提下它最多可以保存多少个元素;
③list和array没有capoacity成员:list所占的空间不是连续的,array是固定size的。
9.5 额外的string 操作
【练习】
#include <vector>
#include <iostream>
#include <string>
using namespace std;
//练习9.43
string InsertAndErase(string &str, string oldVal, string newVal)
{
auto it = str.begin();
if (oldVal.size() > str.size())
{
//如果str的长度比oldVal还短,说明str中不可能有oldVal
return str;
}
int pos = 0;
while (it != str.end())
{
if ((str.size() - pos >= oldVal.size()))
{ //str剩余部分的大小还能容纳oldVal,说明str剩余部分可能有oldVal
string temp(str, pos, oldVal.size());
if (temp == oldVal)
{
str.erase(pos, oldVal.size());
str.insert(pos, newVal);
pos += newVal.size();//偏移跳过nwVal长度
continue;
}
pos++;
}
else
{
//剩余部分不可能容纳oldVal
break;
}
}
return str;
}
//练习9.44
string Replace(string &str, string oldVal, string newVal)
{
cout << str << endl;
auto it = str.begin();
if (oldVal.size() > str.size())
{
//如果str的长度比oldVal还短,说明str中不可能容纳oldVal
return str;
}
int pos = 0;
while (it != str.end())
{
if ((str.size() - pos >= oldVal.size()))
{ //str剩余部分的大小还能容纳oldVal,说明str剩余部分可能有oldVal
string temp(str, pos, oldVal.size());
if (temp == oldVal)
{
str.replace(pos, oldVal.size(), newVal);
pos += newVal.size();//偏移跳过nwVal长度
continue;
}
pos++;
}
else
{
//剩余部分不可能容纳oldVal
break;
}
}
return str;
}
int main(int argc, char**argv)
{
string s("tho and thru");
string oldVal_1("tho");
string newVal_1("though");
string oldVal_2("thru");
string newVal_2("through");
InsertAndErase(s, oldVal_1, newVal_1);
Replace(s, oldVal_2, newVal_2);
cout << s << endl;
system("pause");
}
(一)构造string的其他方法
【注意】
1、通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。
(二)改变string的其他方法
(三)string搜索操作
1、每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为string::npos的static成员。(P325)
2、搜索(以及其他string操作)是大小写敏感的。(P325)
【注意】
1、未找到不是返回false,而是string::npos,因此常见的一种循环搜索string的设计模式是: