C++标准库 -- 顺序容器 (Primer C++ 第五版 · 阅读笔记)

news2025/1/20 10:46:34

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 外,其他容器都提供高效、灵活的内存管理。

  • listforward_list 两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器。而且,与 vectordequearray相比,这两个容器的额外内存开销也很大.
  • deque是一个更为复杂的数据结构。与stringvector类似,deque 支持快速的随机访问。与stringvector一样,在 deque 的中间位置添加或删除元素的代价(可能)很高。但是,在 deque 的两端添加或删除元素都是很快的,与 listforward_list添加删除元素的速度相当。
  • forward_list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。
  • 对其他容器而言size保证是一个快速的常量时间的操作。

9.2、容器库概览

一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。即,deque定义头文件 deque中,list定义在头文件list中,以此类推。

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

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

所有容器都适用的操作:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9.2.1 、迭代器

与容器一样,迭代器有着公共的接口:如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。

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

[begin, end)

标准库使用左闭合范围是因为这种范围有三种方便的性质。假定beginend 构成一个合法的迭代器范围,则

  • 如果beginend相等,则范围为空
  • 如果beginend不等,则范围至少包含一个元素,且 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 成员

beginend操作生成指向容器中第一个元素和尾元素之后位置的迭代器。

  • beginend有多个版本:带r的版本返回反向迭代器;
  • c开头的版本则返回const迭代器:
  • 不以c开头的函数都是被重载过的。也就是说,实际上有两个名为 begin 的成员。一个是const成员,返回容器的const_iterator类型。另一个是非常量成员,返回容器的iterator类型。rbeginendrend的情况类似。
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,但反之不行。

我们可以用以支持autobeginend 函数结合使用。过去,没有其他选择,只能显式声明希望使用哪种类型的迭代器:

//是iterator还是const_ iterator依赖于a的类型
auto it7 = a.begin();//仅当a是const时,it7是const_iterator
auto it8 = a.cbegin();// it8是const_iterator,不管容器的类型是什么

当不需要写访问时,应使用 cbegincend

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];//所有元素值均为0

al = 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之后,这些元素已经属于不同的容器了。例如,假定iterswap之前指向svec1[3]string,那么在swap之后它指向svec2[3]的元素。与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效。

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

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

  • 在新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的 swap。而早期标准库版本只提供成员函数版本的swap。非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。

9.2.6 、容器大小操作

除了一个例外,每个容器类型都有三个与大小相关的操作。

  • 成员函数size返回容器中元素的数目;
  • emptysize为0时返回布尔值 true,否则返回 false;
  • max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值。
  • forward_list支持max_sizeempty,但不支持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)添加元素的操作。

在这里插入图片描述

当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响性能。在一个vectorstring的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vectorstring添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。

  • push_back:除arrayforward_list之外,每个顺序容器(包括string类型)都支持push_back
  • push_front:除了push_backlistforward_listdeque容器还支持名为push_front内类似操作。此操作将元素插入到容器头部;
  • insertinsert成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。vectordequeliststring都支持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_frontemplaceemplace_back,这
      些操作构造而不是拷贝元素。这些操作分别对应push_frontinsertpush_back,允许我们将元素放置在容器头部、一个指定位置之前或容器尾部。
    • 当调用pushinsert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个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中的首元素和尾元素的引用(变量别名)。直接的方法是调用frontback。而间接的方法是通过解引用begin返回的迭代器来获得首元素的引用,以及通过递减然后解引用end返回的迭代器来获得尾元素的引用。

在这里插入图片描述

访问成员函数返回的是引用

在容器中访问元素的成员函数(即,frontback下标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成员函数类似下标运算符,但如果下标越界,at 会抛出一个out_of_range异常:

vector<string> svec;		//空vector
cout << svec[0];			//运行时错误:svec中没有元素!
cout << svec.at(0);			//抛出一个out_of_range异常

9.3.3 、删除元素

与添加元素的多种方式类似,(非array)容器也有多种删除元素的方式。下表列出了这些成员函数。

在这里插入图片描述

pop_frontpop_back返回void。如果你需要弹出的元素的值,就必须在执行弹出操作之前保存它:

while (!ilist.empty(){
	process(ilist.front()); //对ilist的首元素进行一些处理
	ilist.pop_front() ;//完成处理后删除首元素
}

9.3.4 、特殊的forward_list操作

当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生改变。为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱的链接。

由于这些操作与其他容器上的操作的实现方式不同,forward_list并未定义insertemplaceerase,而是定义了名为insert_afteremplace aftererase_after的操作。

forward_list也定义了before_begin,它返回一个首前( off-the-beginning)迭代器。这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素(亦即在链表首元素之前添加删除元素)。

在这里插入图片描述

9.3.5 、改变容器大小

如下表所描述,我们可以用resize来增大或缩小容器,与往常一样,array不支持resize。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部:

在这里插入图片描述

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

向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针引用迭代器 失效

  • 一个失效的指针引用迭代器将不再表示任何元素。使用失效的指针、引用或迭代器是一种严重的程序设计错误,很可能引起与使用未初始化指针一样的问题。

在向容器添加元素后:

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

当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效,这应该不会令人惊讶。毕竟,这些元素都已经被销毁了。

当我们删除一个元素后:

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

由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对 vectorstringdeque尤为重要。

编写改变容器的循环程序

添加/删除 vectorstringdeque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。

  • 程序必须保证每个循环步中都更新迭代器、引用或指针

如果循环中调用的是inserterase,那么更新迭代器很容易。这些操作都返回迭代器,我们可以用来更新:

//傻瓜循环,删除偶数元素,复制每个奇数元素
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; //向前移动迭代器,跳过当前元素以及插入到它之前的元素
	}else
		iter = vi.erase(iter);
		//删除偶数元素
		//不应向前移动迭代器,iter指向我们删除的元素之后的元素
}

在调用erase后,不必递增迭代器,因为erase返回的迭代器已经指向序列中下一个元素。调用insert后,需要递增迭代器两次。记住,insert在给定位置之前插入新元素,然后返回指向新插入元素的迭代器。因此,在调用insert后,iter指向新插入元素,位于我们正在处理的元素之前。我们将迭代器递增两次,恰好越过了新添加的元素和正在处理的元素,指向下一个未处理的元素。

不要保存end返回的迭代器

如果在一个循环中插入/删除 dequestringvector中的元素,不要缓存end返回的迭代器。

必须在每次插入操作后重新调用end(),而不能在循环开始前保存它返回的迭代器:

//安全的方法:在每个循环步添加/删除元素后都重新计算end
while (begin != v.end(){
	//做一些处理
	++begin; //向前移动begin,因为我们想在此元素之后插入元素
	begin = v.insert(begin, 42);//插入新值
	++begin; //向前移动begin,跳过我们刚刚加入的元素
}

9.4、vector对象是如何增长的

vector在每次重新分配内存空间时都要移动所有元素,但使用此策略后,其扩张操作通常比listdeque还要快。

管理容量的成员函数

如下表所示,vectorstring类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动。

在这里插入图片描述

  • 只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间(可能更大)。
  • 在新标准库中,我们可以调用shrink_to_fit来要求dequevectorstring退回不需要的内存空间。此函数指出我们不再需要任何多余的内存空间。但是,具体的实现可以选择忽略此请求。也就是说,调用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类型支持顺序容器的赋值运算符以及assigninserterase操作。除此之外,它还定义了额外的inserterase版本。

  • 接受下标的版本:下标指出了开始删除的位置,或是insert到给定值之前的位置:
s.insert(s.size(), 5, '!');		//在s末尾插入5个感叹号
s.erase(s.size() - 5, 5);		//从s删除最后5个字符
  • 接受C风格字符数组的insertassign版本:我们可以将以空字符结尾的字符数组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类定义了两个额外的成员函数: appendreplace,这两个函数可以改变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操作是调用eraseinsert的一种简写形式:
//将"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列出的appendassigninsertreplace函数有多个重载版本,这些函数有共同的接口。

  • assignappend函数无须指定要替换string 中哪个部分: assign总是替换string中的所有内容append总是将新字符追加string末尾。
  • replace函数提供了两种指定删除元素范围的方式。可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。
  • insert函数允许我们用两种方式指定插入点:用一个下标或一个迭代器。在两种情况下,新元素都会插入到给定下标(或迭代器)之前的位置。

并不是每个函数都支持所有形式的参数。例如,insert就不支持下标和初始化列表参数。类似的,如果我们希望用迭代器指定插入点,就不能用字符指针指定新字符的来源。

9.5.3、string搜索操作

string类提供了6个不同的搜索函数,每个函数都有4个重载版本。表9.14描述了这些搜索成员函数及其参数。

  • 每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。
  • 如果搜索失败,则返回一个名为string::nposstatic成员。标准库将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、容器适配器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414589.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑0x0000001A蓝屏错误怎么U盘重装系统教学

电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug&#xff0c;只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢&#xff1f;来看看以下的详细操作方法教学吧。 准备工作&…

ThinkPad-L480电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板ThinkPad-L480 处理器Intel Core i7-8550U已驱动 内存16GB DDR4 2400Mhz已驱动 硬盘Intel 760p 512GB已驱动 显卡Intel UHD 620已驱动 声卡瑞昱…

【探花交友】day06—即时通信

目录 1、即时通信 1.1、什么是即时通信&#xff1f;​编辑 1.2、功能说明 1.3、技术方案 2、环信 2.1、开发简介 2.2、环信Console 2.3、接口说明 3、抽取环信组件 3.1、编写HuanXinTemplate 3.2、编写Properties对象 3.3、配置 3.4、测试 4、用户体系集成 4.1、…

力扣刷题第一天:剑指 Offer 18. 删除链表的节点、LC206.反转链表

目录 零、前言 剑指 Offer 18. 删除链表的节点 一、题目描述 二、解题思路 三、完整代码 LC206.反转链表 一、题目描述 二、解题思路 三、完整代码 零、前言 这篇文章主要讲解两道链表相关的题目&#xff0c;分别是剑指 Offer 18和LC206。链表作为数据结构中重要的一…

Vue学习——【第五弹】

前言 上一篇文章 Vue学习——【第四弹】 中学到了数据代理&#xff0c;这篇文章接着学习 Vue中的事件处理。 事件处理 我们在学习JavaScript时就经常接触事件处理&#xff0c;比如在进行表单、按钮、列表折叠等操作时&#xff0c;我们就经常用到 click&#xff08;点击&…

Redis源码之Hash表实现

通常我们如果要设计一个 Hash 表&#xff0c;那么我们需要考虑这几个问题&#xff1a; 有没有并发操作Hash冲突如何解决以什么样的方式扩容对 Redis 来说&#xff0c;首先它是单线程的工作模式&#xff0c;所以不需要考虑并发问题。 想实现一个性能优异的 Hash 表&#xff0c…

ubuntu快速安装VMware Tools(全屏用的)

VMware Tools实现主机和虚拟机的文件共享。 第一步 打开VMware Workstation,启动ubuntu系统。 点击主界面的&#xff08;虚拟机&#xff09;——点击&#xff08;安装VMware Tools&#xff09;。 弹出提示框点击是——等待自动下载完成。 第二步 将安装包复制到桌面&#x…

ESP32 web WiFi 管理器esp32-wifi-manager

拓 2023/04/09-2022/04/11 1. 简介 github仓库 https://github.com/tonyp7/esp32-wifi-manager 说明 esp32-wifi-manager是esp32的纯C esp-idf组件&#xff0c;可通过门户网站轻松管理wifi网络。 esp32-wifi-manager是一个集所有功能于一身的wifi扫描仪、http服务器和dns守…

2022年第十三届蓝桥杯题解(全)C/C++

A题就是一个简单的进制转化&#xff0c;代码实现如下&#xff1a; #include <bits/stdc.h>using namespace std;const int N 1e5 10;int main() {int x 2022;int a 1;int res 0;while(x) {res (x % 10) * a;a a * 9;x / 10;}cout << res;return 0; } B题有…

【论文阅读笔记】COFFEE: A Contrastive Oracle-Free Framework for Event Extraction

论文题目&#xff1a;COFFEE: A Contrastive Oracle-Free Framework for Event Extraction 论文来源&#xff1a; 论文链接&#xff1a;https://arxiv.org/pdf/2303.14452.pdf 代码链接&#xff1a; 0 摘要 事件抽取是一项复杂的信息抽取任务&#xff0c;它涉及到从非结构…

【AIGC】7、CLIP | OpenAI 出品使用 4 亿样本训练的图文匹配模型

文章目录一、背景二、方法2.1 使用自然语言来监督训练2.2 建立一个超大数据集2.3 选择预训练模型2.4 模型缩放和选择三、效果论文&#xff1a;Learning Transferable Visual Models From Natural Language Supervision 代码&#xff1a;https://github.com/OpenAI/CLIP 官网&…

DJ3-5 死锁概述

目录 3.5 死锁概述 3.5.2 计算机系统中的死锁 1. 竞争资源 2. 进程推进顺序不当 3.5.3 死锁的必要条件和处理方法 1. 死锁的必要条件 2. 处理死锁的方法 3.6 预防死锁 3.6.1 摒弃 “请求和保持” 条件 3.6.2 摒弃 “不剥夺” 条件 3.6.3 摒弃 “环路等待” 条…

企业在数字化建设中,BI 处于什么位置?

对市场异常敏感的商业世界自然不会放过获取数字经济的机会&#xff0c;在众多企业开始进行数字化转型&#xff0c;通过信息化建设&#xff0c;部署BI来完成转型工作。 很多人都听说过BI&#xff0c; 但是并不太清楚BI 在IT信息化中到底处于一个什么位置&#xff1f;有很多的疑…

APIs --- DOM基础事件

1. 事件 事件是编程时系统内发生的动作或者发生的事情&#xff0c;它是用来描述程序的行为或状态的&#xff0c;一旦行为或状态发生改变&#xff0c;便立即调用一个函数。 例如&#xff1a;用户使用【鼠标点击】网页中的一个按钮、用户使用【鼠标拖拽】网页中的一张图片 事件…

【MySQL】外键约束和外键策略

一、什么是外键约束&#xff1f; 外键约束&#xff08;FOREIGN KEY&#xff0c;缩写FK&#xff09;是用来实现数据库表的参照完整性的。外键约束可以使两张表紧密的结合起来&#xff0c;特别是针对修改或者删除的级联操作时&#xff0c;会保证数据的完整性。 外键是指表…

ElasticSearch——详细看看ES集群的启动流程

参考&#xff1a;一起看看ES集群的启动流程 本文主要从流程上介绍整个集群是如何启动的&#xff0c;集群状态如何从Red变成Green&#xff0c;然后分析其他模块的流程。 这里的集群启动过程指集群完全重启时的启动过程&#xff0c;期间要经历选举主节点、主分片、数据恢复等重…

java中的SPI机制

文章目录SPI 介绍何谓 SPI?SPI 和 API 有什么区别&#xff1f;实战演示Service Provider InterfaceService Provider效果展示ServiceLoaderServiceLoader 具体实现自己实现一个 ServiceLoader总结在面向对象的设计原则中&#xff0c;一般推荐模块之间基于接口编程&#xff0c;…

测试开发备战秋招面试3

努力了那么多年,回头一望,几乎全是漫长的挫折和煎熬。对于大多数人的一生来说,顺风顺水只是偶尔,挫折、不堪、焦虑和迷茫才是主旋律。我们登上并非我们所选择的舞台,演出并非我们所选择的剧本。继续加油吧&#xff01; 目录 1.讲一下redis和mySQL的区别&#xff1f; 2.讲一下…

ChatGPT让现在的软件都土掉渣了

我们家有两个娃&#xff0c;每次我们想要出去时订个酒店时都好麻烦。我在某程上找&#xff0c;我先看有没有家庭房&#xff0c;但家庭房很少&#xff0c;而且有些家庭房实际上只能睡得下两大一小。普通房间能不能睡得下四个人&#xff0c;那可是得查看很多信息&#xff0c;如床…

Redis队列Stream、Redis多线程详解(一)

Redis队列与Stream Redis5.0 最大的新特性就是多出了一个数据结构 Stream&#xff0c;它是一个新的强大的支持多播的可持久化的消息队列&#xff0c;作者声明Redis Stream地借鉴了 Kafka 的设计。 Redis Stream 的结构如上图所示,每一个Stream都有一个消息链表&#xff0c;将所…