当前位置: 首页 > news >正文

C++ Primer 容器库概述

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

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

目录

  • 9.2容器库概览
    • 对容器可以保存的元素类型的限制
    • 迭代器
    • 迭代器范围
    • 使用左闭合范围蕴含的编程假定
    • 容器类型成员
    • begin和end成员
    • 容器定义和初始化
    • 将一个容器初始化为另一个容器的拷贝
    • 列表初始化
    • 与顺序容器大小相关的构造函数
    • 标准库array具有固定大小
    • 赋值和swap
    • 使用assign(仅顺序容器)
    • 使用swap
    • 容器大小操作
    • 关系运算符
    • 容器的关系运算符使用元素的关系运算符完成比较

9.2容器库概览

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

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

在本节中,我们将介绍对所有容器都适用的操作。本章剩余部分将聚焦于仁适用于顺序容器的操作。关联容器特有的操作将在第11章介绍。

一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。即,deque定义在头文件deque中,list定义在头文件list中,以此类推。容器均定义为模板类。例如对vector,我们必须提供额外信息来生成特定的容器类型。对大多数,但不是所有容器,我们还需要额外提供元素类型信恩:

list<Sales_data>//保存Sales_data对象的list
deque<double>   //保存double的deque

对容器可以保存的元素类型的限制

顺序容器几乎可以保存任意类型的元素。特别是,我们可以定义一个容器,其元素的类型是另一个容器。这种容器的定义与任何其他容器类型完全一样:在尖括号中指定元素类型(此种情况下,是另一种容器类型):

vector<vector<string>>lines;//vector的vector

此处lines是一个vector,其元素类型是string的vector。

虽然我们可以在容器中保存几乎任何类型,但某些容器操作对元素类型有其自己的特殊要求。我们可以为不支持特定操作需求的类型定义容器,但这种情况下就只能使用那些没有特殊要求的容器操作了。

例如,顺序容器构造函数的一个版本接受容器大小参数它使用了元素类型的默认构造函数。但树些类没有默认构造函数。我们可以定义一个保存这种类型对象的容器,但我们在构造这种容器时不能只传递给它一个元素数目参数:

//假定noDefault是一个没有默认构造出数的类型
vector<noDefault>vl(10,init);//正确:提供了元素初始化器
vector<noDefault>v2(10); //错误:必须提供一个元素初始化器

当后面介绍容器操作时,我们还会注意到每个容器操作对元素类型的其他限制。

表9.2:容器操作

类型别名
iterator此容器类型的迭代器类型
const_iterator可以读取元素,但不能修改元素的迭代器类型
size_type无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type带符号整数类型,足够保存两个选代器之间的距离
value_type元素类型
reference元素的左值类型;与value_typeg含义相同
const_reference元素的const左值类型(即,const value_type &)
构造函数
C c;默认构造函数,构造空容器构造c2的拷贝c1
C c1(c2);
C c(b,e);构造c,将迭代器b和e指定的范围内的元素拷贝到c(array不支持)
C c(a,b,c …);列表初始化c
赋值与swap
c1=c2将c1中的元素替换为c2中元素
c1 = {a,b,c…}将c1中的元素替换为列表中元素(不使用于array)
a.swap(b)交换a和b的元素
swap(a,b)与a,swap(b)等价
大小
c.size()c中元素的数目(不支持forward_1ist)
c.maxX_size()c可保存的最大元素数目
c.empty()若c中存储了元素,返回false,否则返回true
添加/删除元素(不适用于array)
c.insert(ags)将args中的元素拷贝进c
c.emplace(imits)使用imits构造c中的一个元素
c.erase(args)删除args指定的元素
c.clear()删除c中的所有元素,返回void
关系运算符
==,!=所有容器都支持相等(不等〕运算符
<,<=,>,>=关系运算符(无序关联容器不支持)
获取选代器
c.begin(),c.end()返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(),c.cend()返回const_iterator
反向容器的额外成员(一支持forward_list)
reverse_itterator按逆序寻址元素的迭代器
const_reverse_iterator不能修改元素的逆序迪代器
c.rbegin(),c.rend()返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(),c.crend()返回const_reverse_iterator

迭代器

与容器一样,迭代器有着公共的接口:如果一个迭代器提供棠个操作,那么所有提供相同操作的迪代器对这个操作的实现方式都是相同的。例如,标准容器类型上的所有迭代器部允许我们访问容器中的元素,而所有迭代器都是通过解引用运算符来实现这个操作的.类似的,标准库容器的所有迭代器都定义了递增运算符,从当前元素移动到下一个元素。

表3.6列出了容器送代器支持的所有操作,其中有一个例外不符合公共接口特点一一forword_list迭代器不支持递减运算符–)。列出了代器支持的算术运算,这些运算只能应用于string、vector、deque和array的迭代器。我们不能将它们用于其他任何容器类型的迭代器。

迭代器范围

迭代器范围的表述是标准库的基础。

-个迭代器范围(iterator range)由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置(one past the last element)。这两个迭代器通常被称为begin和end,或者是first和last(可能有些误导),它们标记了容器中元素的一个范围。

虽然第二个迭代器常常被称为last,但这种叫法有些误导,因为第二个迭代器从来都不会指向范围中的最后一个元素,而是指向尾元素之后的位置。迭代器范围中的元素包含first所表示的元素以及从first开始直至last(但不包含last)之间的所有元素。

这种元素范围被称为作闭合区间(left-inclusive interval),其标准数学描述为
[begin,end)

表示范围自begin开始,于end之前结束。迭代器begin和end必须指向相同的容器。end可以与begin指向相同的位置,但不能指向begin之前的位置。

对构成范围的迭代器的要求

如果满足如下条件,两个送代器begin和end构成一个迭代器范围:

  • 它们指向同一个容命中的元素,或者是容命最后一个元秋之后的位置,且我们可以通过反复递增begini来到达end。换句话说,end不在begin之前。

编译器不会强制这些要求。确保程序符合这些约定是程序员的责任。

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

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

  • 如果begin与end相等,则范围为空
  • 如果begin与end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
  • 我们可以对begin递增若干次,使得begin==end

这些性质意味着我们可以像下面的代码一样用一个循环来处理一个元素范围,而这是安全的:

while(begin!=end){
    *begin=val;//正确:范围非空,因此begin指向一个元素
    ++begin;//移动迭代器,获取下一个元素
}

给定构成一个合法范围的迭代器begin和end,若begin==end,则范围为空。在此情况下,我们应该退出循环。如果范园不为空,begin指向此非空范围的一个元素。因此,在while循环体中,可以安全地解引用begin,因为begin必然指向一个元素。最后,由于每次循环对begin递增一次,我们确定循环最终会结束。

容器类型成员

每个容器都定义了多个类型,如表9.2所示。我们已经使用过其中三种:size_type、iterator和const_iterator除了已经使用过的迭代器类型,大多数容器还提供反向迭代器。简单地说,反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义也都发生了颠倒。例如,对一个反向迭代器执行++操作,会得到上一个元素。

介绍更多关于反向迭代器的内容。

剩下的就是类型别名了,通过类型别名,我们可以在不了解容器中元素类型的情况下使用它。如果需要元素类型,可以使用容器的value_type。如果需要元素类型的一个引用,可以使用reference或const_reference。这些元素相关的类型别名在泛型编程中非常有用,我们将在16章中介绍相关内容。为了使用这些类型,我们必须显式使用其类名;

//iter是通过list<string>定义的一个选代器类型
1ist<string>::iterator iter;
//count是通过yector<int>定义的一个difference_type类型
vector<int>::difference_type count;

这些声明语句使用了作用域运算符来说明我们希望使用

list<string>类的iterator成员及vector<int>类定义的difference_type。

begin和end成员

begin和end操作生成指向容器中第一个元素和尾元素之后位置的迭代器。这两个选代器最常见的用途是形成一个包含容器中所有元素的迭代器范围。

begin和end有多个版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器:

list<string>a = {"Milton","Shakespeare","Rusten"};
auto it1 = a.begin();//  list<string>::iterator
auto it2 = a.rbegin();// list<string>::reverse_iterator
auto it3 = a.cbegin();// list<string>::const_itterator
auto it4 = a.crbegin();//list<string>::const_reverse_iterator

不以c开头的函数都是被重载过的。也就是说,实际上有两个名为begin的成员。一个是const成员,返回容器的const_iterator类型。另一个是非常量成员,返回容器的iterator类型。rbegin、end和rend的情况类似。当我们对一个非常量对象调用这些成员时,得到的是返回iterator的版本。只有在对一个const对象调用这些函数时,才会得到一个const版本。与const指针和引用类似,可以将一个普通的iterator转换为对应的const_iterator,但反之不行。

以c开头的版本是C++新标准引入的,用以支持auto 与begin和end函数结合使用。过去,没有其他选择,只能显式声明希望使用哪种类型的迭代器:

```cpp
//显式指定类型
list<string>::iterator it5=a.begin();
list<string>::const_iterator it=a.begin();
//是iterator还是const_iterator依赖于a的类型
auto it7=a.begin();  //仅当a是const时,it7是const_iterator
auto it8=a.cbegin(); //it8是const_iterator

当auto与begin或end结合使用时,获得的迭代器类型依赖于容器类型,与我们想要如何使用迭代器毫不相干。但以c开头的版本还是可以获得const_iterator的,而不管容器的类型是什么。

容器定义和初始化

每个容器类型都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建一个指定类型的宇容器,且都可以接受指定容器大小和元素初始值的参数。

表9.3:容器定义和初始化

C c默认构造函数。如果c是一个array,则c中元素按默认方式初始化;否则c为空
C c1(c2)c1初始化为c2的拷贝。c1和c2必须是相同类型(即,它们必须是相同的容器类型,日保存的是相同的元素类型;对于array类型,两者还必须具有相同大小
C c1= c2
C c{a,b,c…}初始化为初始化列表中元素的拷贝。列表中元素的类型必须与c的
C c={a,b,c…}元素类型相容。对于array类型,列表中元素数目必须等于或小于array的大小,任何遗漏的元素都进行值初始化
C c(b,e)c初始化为迪代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与c的元素类型相容(array不近用)
只有顺序容器(不包括array)的构造函数才能接受大小参数
C seq(n)sedq包含n个元素,这些元素进行了值初始化;此构造函数是explicit的,(string不适用)
C seq(n,t)sedq包含n个初始化为值t的元素

将一个容器初始化为另一个容器的拷贝

将一个新容器创建为另一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者(array除外)拷贝由一个迭代器对指定的元素范围。

为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且,新容器和原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。

//每个容器有三个元素,用给定的初始化器进行初始化
list<string> authors= {"Milton","Shakespeare","Rusten"};
vector<const char*>articles= {"a","an","the"};
list<string> list2(authors);//正确:类型匹配
deque<string> authList(authors);//错误:容器类型不匹配
vector<string> words(arttcles);//错误:容器类型必须匹配
//正确:可以将constchar*元素转换为string
forward_1ist<string>words(articles.begin(),articles.end());

当将一个容器初始化为另一个客器的挡贝时,两个客嚣的类型和元素类型都必须相同

接受两个选代器参数的构造函数用这两个选代器表示我们想要拷贝的一个元素范围。与以往一样,两个迪代器分别标记想要拷贝的第一个元素和尾元素之后的位置。新容器的大小与范围中元素的数目相同。新容器中的每个元素都用范围中对应元素的值进行初始化。由于两个迭代器表示一个范围,因此可以使用这种构造函数来拷贝一个容器中的子序列。例如,假定迭代器it表示authors中的一个元素,我们可以编写如下代码

//拷贝元素,直到(但不包括)it指向的元素
deque<string>authList(authors.begin(),it);

列表初始化

在新标准中,我们可以对一个容器进行列表初始化

//每个容器有三个元素,用给定的初始化器进行初始化
list<string>authors={"Milton","Shakespeare","Rusten"};
vector<const_char*>articles={"a","anm","then"};

当这样做时,我们就显式地指定了容器中每个元素的值。对于除array之外的容器类型,初始化列表还隐含地指定了容器的大小:容器将包含与初始值一样多的元素。

与顺序容器大小相关的构造函数

除了与关联容器相同的构造函数外,顺序容器(array除外)还提供另一个构造函数,它接受一个容器大小和一个(可选的元素初始值。如果我们不提供元素初始值,则标准库会创建一个值初始化器

vector<int>ivec(10,-1);//10个int元素,每个都初始化为-1
list<string>svec(10,"hi!");//10个strings;每个都初始化为“hi!"
forward_list<int>tvec(10)}//10个元素,每个都初始化为0
deque<string>svec(10);//10个元素,每个都是空string

如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素类型没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。

只有顺序客嚎的构造函数才接受大小参数,关联容器并不支持。

标准库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类型的一部分,array不支持普通的容器构造函数。这些构造函数都会确定容器的大小,要么隐式地,要么显式地。而允许用户向一个array构造函数传递大小参数,最好情况下也是多余的,而且容易出错。

array大小固定的特性也影响了它所定义的构造函数的行为。与其他容器不同,一个默认构造的array是非空的,它包含了与其大小一样多的元素。这些元素都被默认初始化,就像一个内置数组中的元素那样。如果我们对array进行列表初始化,初始值的数目必须等于或小于array的大小。

如果初始值数目小于array的大小,则它们被用来初始化array中靠前的元素,所有剩余元素都会进行值初始化。在这两种情况下,如果元素类型是一个类类型,那么该类必须有一个默认构造函数,以使值初始化能够进行:

array<int,10> ia1; //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,415,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类型的一部分。

赋值和swap

第一个赋值运算后,左边容器将与右边容器相等。如果两个容器原来大小不同,赋值运算后两者的大小都与右边容器的原大小相同。第二个赋值运算后,c1的size变为3,即花括号列表中值的数目。

与内置数组不同,标准库array类型允许赋值。赋值号左右两边的运算对象必须具有相同的类型:

array<int,0> a1{0,1,2,3,4,5,6,7,8,9};
array<int,0> a2{0};//所有元素值均为0
al=a2;//普换a1中的元素
a2={0};//错误:不能将一个花括号列表赋予数组

由于右边运算对象的大小可能与左边运算对象的大小不同,因此array类型不支持assign,也不允许用花括号包园的值列表进行赋值。

表9.4:容器器赋值运算

c1 = c2将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同的类型
c={a,b,c…}将c1中元素替换为初始化列表中元素的拷贝(array不试用)
swap(cl,c2)交换cl和c2中的元素。c1和c2必须具有相同的类型。swap通常比从c2向cl拷贝元素快得多
c1.swap(c2)
assign操作不适用于关联容器和array
seq.assign(b,e)将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素
seq.assign(i1)将seq中的元素替换为初始化列表11中的元素
sedq.assign(n,t)将seq中的元素替换为n个值为t的元素

使用assign(仅顺序容器)

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

list<string>names;
vector<const char*>oldstyle;
names=oldstyle;//错误:容器类型不匹配
//正确:可以将constchaz*转换为string
names.assign(oldstyle.cbegin(),oldstyle.cend());

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

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

//等价于slistl.clear();
//后跟slistl.insert(slistl.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(svec1,svec2);

调用swap后,svec1将包含24个string元素,svec2将包含10个string。除array外,交换两个容器内容的操作保证会很快一一元素本身并未交换,swap只是交换了两个容器的内部数据结构。

除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。

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

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

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

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

容器大小操作

除了一个例外,每个容器类型都有三个与大小相关的操作.成员函数size返回容器中元素的数目;empty当size为0时返回布尔值true,否则返回false;max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值。forward_1ist支持max_size和empty,但不支持size,原因我们将在下一节解释。

关系运算符

每个容器类型都支持相等运算符(==和!=);除了无序关联容器外的所有容器鄄支持关系运算符(>、>=、<、<~)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。即,我们只能将一个vector与另一个vector进行比较,而不能将一个vector与一个list或一个vector进行比较。

比较两个容器实际上是进行元素的逐对比较。这些运算符的工作方式与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};
v1<v2// true; v1和v2在元素[2]处不同:v1[2]小于等于v2[2]
v1<v3//false;所有元素都相等,但v3中元素数目更少
v1==v4//true; 每个元素都相等,且v1和v4大小相同
v1==v2//false;v2元素数目比v1少

容器的关系运算符使用元素的关系运算符完成比较

容器的相等运算符实际上是使用元素的一运算符实现比较的,而其他关系运算符是使用元素的<运算符。如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。例如,我们在第7章中定义的Sales_data类型并未定义一和<运算。因此,就不能比较两个保存Sales_data元素的容器:

vector<Sales_data>storeR,StoreB;
if(storeR<storeB)//错误:Sales_data没有<运算符

相关文章:

  • RocketMq\Kafka如何保障消息不丢失?
  • 【微服务优化】ELK日志聚合与查询性能提升实战指南
  • 【多线程】线程安全
  • [LeetCode]day27 28. 找出字符串中第一个匹配项的下标
  • 音视频入门基础:RTP专题(10)——FFmpeg源码中,解析RTP header的实现
  • Docker仿真宇树狗GO1
  • Spring Security+JWT+Redis实现项目级前后端分离认证授权
  • 【DeepSeek-R1背后的技术】系列九:MLA(Multi-Head Latent Attention,多头潜在注意力)
  • 深入解析适配器模式:软件架构中的接口协调大师
  • printf和 vprintf的区别
  • MongoDB学习
  • CASS11快捷键设置
  • 国内三大知名开源批发订货系统对比
  • 【React】React 基础(2)
  • 深度解读DeepSeek:从原理到模型
  • Cursor不能白嫖还不安全:Cline + DeepSeek V3,最强国产双开源解决方案
  • C语言内存函数
  • 【MATLAB例程】RSSI/PLE定位与卡尔曼滤波NLOS抑制算法,附完整代码
  • 智能自动化新纪元:AI与UiPath RPA的协同应用场景与技术实践
  • vscode软件中引入vant组件
  • 常州市委原常委、组织部部长陈翔调任江苏省民宗委副主任
  • 绿城房地产集团:近半年累计花费20.6亿元购买旗下债券
  • 司法部谈民营经济促进法:对违规异地执法问题作出禁止性规定
  • 美联储连续第三次维持利率不变,警示关税影响
  • 五一期间7名游客接连被困青海荒漠,警方提醒严禁非法穿越
  • 第四轮伊美核问题谈判预计5月11日举行