C++ primer 第九章

本文围绕C++顺序容器展开,介绍了顺序容器的类型、使用规则,如vector、list等。阐述了容器库的概览,包括迭代器、容器定义和初始化等操作。还详细说明了顺序容器的添加、访问、删除元素等操作,以及vector的内存增长机制、string的额外操作和容器适配器的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 顺序容器概述

顺序容器提供了控制元素存储和访问顺序的能力,容器是一些特定类型对象的集合。

访问顺序不依赖于元素的值,与元素加入容器时的位置相关。

顺序容器类型
vector可变大小数组,支持快速随机访问。
deque双端队列,支持快速随机访问,在头尾位置插入/删除速度很快
list双向链表,只支持双向顺序访问。
forward_list单向链表,只支持单向顺序访问。
array固定大小数组,支持快速随机访问,不能添加或删除元素。
string可变大小,专门用于保存字符,能随机访问。

string和vector将元素保存在连续的内存空间中,可以用元素的下标来计算其地址。

list和forward_list能在容器的任何位置添加或删除元素,但不支持元素的随机访问。

array与内置数组相比,更安全、更容易、性能更优良。

使用哪种顺序容器规则 

通常,使用vector是最好的选择。

如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list。

如果程序要求随机访问元素,应使用vector或deque。

如果程序要求在容器的中间位置插入或删除元素,应使用list或forward_list。

如果不确定应该使用哪种容器,在程序中可以只使用vector和list,使用迭代器来进行容器的操作。

2.容器库概览 

容器类型上的操作基本上分为公有操作和私有操作。

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

容器均定义为模板类。

顺序容器可以保存任意类型的元素。

vector<vector<string> > lines;
类型别名
iterator容器类型的迭代器类型
const_iterator可以读取元素,但不能修改元素的迭代器类型
size_type无符号整数类型,足够保存容器类型最大的大小
difference_type带符号整数类型,足够保存两个迭代器之间的距离
value_type元素类型,list<string>读取元素
reference元素的左值类型,类似于元素的引用
const_reference元素的const左值类型
添加/删除元素操作(array除外)
c.insert(args)将args中的元素拷贝进c
c.emplace(inits)使用inits构造c中的一个元素
c.erase(args)删除args指定的元素
c.clear()删除c中的所有元素,返回void
反向容器的额外成员(forward_list除外)
reverse_iterator按逆序寻址元素的迭代器
const_reverse_iterator不能修改元素的逆序迭代器
c.rbegin() ,c.rend()返回指向c的尾元素和首元素之间位置的迭代器
c.crbegin() , c.crend()返回const_reverse_iterator

 2.1、迭代器

forward_list迭代器不支持递减运算符。

一个迭代器范围由一对迭代器表示,迭代器指向同一个容器中的首元素或尾元素之后的位置。

这种元素范围被称为左闭合区间

[begin,end)

确保一对迭代器指向同一个容器,且end不在begin之前,是程序员的责任。

2.2、容器类型成员 

 每个容器都定义了多个类型:如size_type、iterator、const_iterator等。

在使用这些类型时,必须显式使用其类名:

list<string>::iterator iter;
//iter是通过list<string>定义的一个迭代器类型

2.3、begin和end成员 

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

begin和end有多个版本:带r的版本返回反向迭代器,带c的版本返回const迭代器。

当auto与begin或end结合使用时,获得的迭代器类型依赖于容器类型。

使用以c开头的版本可以获得const版本,而不管容器的类型是什么。

2.4、容器定义和初始化

每个容器类型都定义了一个默认构造函数。

容器定义与初始化

C c;

默认构造函数,c为空容器

C c1(c2)

C c1 = c2

c1初始化为c2的拷贝。c1和c2是相同类型,保存元素也是相同类型

(对于array,两者也具有相同大小)

C c = {a,b,c...}c初始化为初始化列表中元素的拷贝
C c(b,e)c初始化为迭代器b和e指定范围中的元素的拷贝
C seq(n)seq包含n个元素,这些元素进行了值初始化,该构造函数是explicit的
C seq(n,t)seq包含n个初始化为值t的元素

将容器中的元素通过一对迭代器指定的元素范围拷贝给新容器中,不要求容器、元素类型相同。

新容器的大小应与范围中元素的数目相同。

在新标准中,可以对一个容器进行列表初始化,初始化列表还隐含地指定容器大小。

list<string> authors = {"milton","austen","shakespeare"};

顺序容器提供了另一个构造函数,它接受一个容器大小和一个元素初始值。

vector<int> ivec(10,-1); //10个int元素,每个初始化为-1

如果元素类型没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。

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

当定义array时,要显式指定元素类型和容器大小。

array<int,10>; //类型为:保存10个int的数组
array<int,10>::size_type io; 

array大小固定的特性影响了它所定义的构造函数的行为;默认构造的array是非空的。 

虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但array并无这样的限制。

array<int,10> digits = {1,2,3,4,5,6,7,8,9};
array<int,10> copy = digits;

 该操作要求初始值的类型必须要与创建的容器类型相同,元素类型和大小一致。

2.5、赋值和swap

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

如果两边对象的大小不同,array类型不支持assign,也不允许用花括号包围的值列表进行赋值。

容器赋值运算
c1 = c2将c1中的元素替换成c2中元素的拷贝,c1与c2具有相同的类型
c = {a,b,c...}将c1中元素替换为初始化列表中元素的拷贝

swap(c1,c2)

c1.swap(c2)

交换c1和c2中的元素,c1与c2具有相同的类型

swap速度比拷贝更快

seq.assign(b,e)

将seq中的元素替换为迭代器b和e的范围内的元素

迭代器b和e不能指向seq

seq.assign(i)将seq中的元素替换为初始化列表i中的元素
seq.assign(n,t)将seq中的元素替换为n个值为t的元素

 赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。

swap操作将容器内容交换不会导致上述情况发生。(array和string除外)

顺序容器定义了assign成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。

list<string> names;
vector<const char*> olds;
names.assign(olds.cbegin(),olds.cend());  //成员元素类型不同但相容,不能直接拷贝

由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。

assign的重载版本接受一个整型值和一个元素值,用指定的数目与元素替换原有元素。

list<string> slist(1); //一个空string
slist.assign(10,"hello"); //10个hello

swap操作交换两个相同类型容器的内容,元素并未交换,只是交换两个容器的内部数据结构。

使用swap操作意味着指向容器的迭代器、引用和指针不会失效,指向同样的元素,但元素属于不同的容器。

 与其他容器不同,swap两个array会真正交换它们的元素。

2.6、容器大小操作

 每个容器类型都有三个与大小相关的操作:size、empty、max_size。

forward_list支持max_size和empty,但不支持size。

2.7、关系运算符

 每个容器类型都支持相等运算符(==或!=)。

除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=)。

关系运算符左右两边的运算对象必须是相同类型的容器、且保存相同类型的元素。

比较两个容器实际上是进行元素的逐对比较。

只有当其元素类型也定义了相应的比较运算符时,才可以使用关系运算符来比较两个容器。

3.顺序容器操作

3.1、向顺序容器添加元素

 除了array,所有标准库容器都提供灵活的内存管理,可以动态添加或删除元素来改变容器大小。

添加元素操作

c.push_back(t)

c.emplace_back(args)

在c的尾部创建一个值为t或由args创建的元素,返回void

c.push_front(t)

c.emplace_front(args)

在c的头部创建一个值为t或由args创建的元素,返回void
c.insert(p,t)在迭代器之前创建一个值为t的元素,返回指向新添加的元素迭代器
c.insert(p,n,t)

在迭代器p指向的元素之前插入n个值为t的元素,

返回指向新添加的第一个元素的迭代器,若n为0,返回p

c.insert(p,b,e)

将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素之前。

b和e不能指向c中的元素。

c.insert(p,i1)

i1是一个元素值列表,将这些给定值插入到迭代器p指向的元素之前

除array和forward_list之外,每个顺序容器都支持push_back。

push_back在string末尾添加字符。

当我们用一个对象来初始化容器或插入容器中,实际上放入容器中的是拷贝,而不是对象本身。

list、forward_list和deque容器支持push_front操作。

insert成员允许我们在容器中任意位置插入0个或多个元素。

每个insert函数都接受一个迭代器p来指出元素具体插入位置,p能指向任意位置,包含容器尾部之后的下一个位置。

insert函数将元素插入到迭代器所指定的位置之前。 

虽然某些容器不支持push_front操作,但它们对于insert操作并无类似的限制。

如果我们传递给insert一对迭代器,他们不能指向添加元素的目标容器。

接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。

当我们调用一个emplace成员函数时,是将参数传递给元素类型的构造函数,在容器中构造元素。

emplace函数的参数根据元素类型而变化,参数必须与元素类型的构造函数相匹配。 

 3.2、访问元素

访问元素的操作
c.back()返回c中尾元素的引用。若c为空,函数行为未定义
c.front()

返回c中首元素的引用。若c为空,函数行为未定义

c.[n]

返回c中下标为n的元素的引用,n是无符号整数,且n<c,size()

c.at(n)返回下标为n的元素的引用,如果下标越界,则抛出out_of_range异常

at和下标操作只适用于string、vector、deque和array;back不适用于forward_list。

访问成员函数返回的是引用,不是迭代器。 

保证下标有效是程序员的责任,编译器并不检查这种错误。

3.3、删除元素 

顺序容器的删除操作
c.pop_back()删除c中尾元素。若c为空,则函数行为未定义,返回void
c.pop_front()删除c中首元素,若c为空,则函数行为未定义,返回void
c.erase(p)

删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器,

若p指向尾元素,则返回尾后迭代器。

c.erase(b,e)

删除迭代器b和e所指定范围内元素,返回一个指向最后一个被删除的元素之后

元素的迭代器,若e本身是尾后迭代器,则返回e

c.clear()删除c中的所有元素,返回void

forward_list不支持pop_back;vector和string不支持pop_front。

删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。

删除元素的成员函数并不检查其参数,因此我们必须确保参数是存在的。

3.4、特殊的forward_list操作 

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

forward_list定义了before_begin,它返回一个首前迭代器。

forward_list插入或删除元素操作
list.before_begin()

返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用。

cbefore_begin()返回一个const_iterator.

list.insert_after(p,t)

list.insert_after(p,n,t)

list.insert_after(p,b,e)

list.insert_after(p,il)

在迭代器p之后插入元素。

t是一个对象,n是数量,b和e是表示范围的一对迭代器(b和e不能指向list),il是一个列表,返回一个指向最后一个插入元素的迭代器。

若范围为空,返回p。

emplace_after(p,args)使用args在p指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义。

list.erase_after(p)

list.erase_after(b,e)

删除p指向的位置之后的元素。

删除从b之后到e(不包含e)之间的元素,返回一个指向被删元素之后的元素的迭代器。(包含尾后迭代器)

当我们在forward_list中添加或删除元素时,关注指向要处理的元素、指向其前驱 的两个迭代器。

3.5、改变容器大小 

顺序容器大小操作
c.resize(n)

调整c的大小为n个元素,若n<c,size(),则多出的元素被丢弃。

若必须添加新元素,对新元素进行值初始化。

c.resize(n,t)调整c的大小为n个元素,任何新添加的元素都初始化为值t

resize操作接受一个可选的元素值参数,用来初始化添加到容器中的元素。

list<int>ilist(10,42); //10个int,值为42
ilist.resize(15);      //将5个0添加到容器末尾
ilist.resize(20,-1);   //将5个-1添加到容器末尾
ilist.resize(5);       //从ilist末尾删除15个元素

如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效。

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

对于vector或string来说,如果存储空间被重新分配,那么迭代器、指针和引用都失效;

若空间未被重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但之后的将会失效

对于deque来说,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效;

在首尾位置添加元素,迭代器会失效,但指向存在的元素的指针和指针不会失效。

对于list和forward_list,指向容器的迭代器,指针和引用仍有效。

由于向迭代器添加或删除元素的操作可能会使迭代器失效,因此在每次改变容器的操作之后都应该重新定位迭代器。

insert函数在给定位置之前插入新元素,然后返回指向新插入元素的迭代器。

在我们添加/删除元素时,end返回迭代器总是会失效,因此不要保存end返回的迭代器。 

4.vector对象是如何增长的 

为了支持快速随机访问,vector将元素连续存储在一起。

vector和string的实现通常会分配比新的空间需求更大的内存空间,容器预留这些空间作为备用。

容器大小管理操作
c.shrink_to_fit()将capacity()减少为与size()相同大小
c.capacity()不重新分配内存空间的话,c可以保存多少元素
c.reserve()分配至少能容纳n个元素的内存空间

shrink_to_fit只适用于vector、string和deque。

capacity和reserve只适用于vector和string。

reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。

只有当需要的内存空间超过当前的容量时,reserve调用才会改变vector的容量。

如果需求大小小于或等于当前容量时,reserve不会执行任何指令,容器不会退回内存空间,因此调用reserve永远不会减少容器占用的内存空间。 

resize成员函数只改变容器中元素的数目,而不是容器的容量。 

调用shrink_to_fit只是一个请求,具体实现可能会忽略,并不保证一定会退回内存空间。

capacity是在不分配新的内存空间的前提下它最多可以保存多少元素。

 vector在每次需要重新分配新内存空间时将当前容量翻倍。

5.额外的string操作  

标准库string类型定义了大量函数,可以在需要使用时再回头查看。

5.1、构造string的其他方法

构造string
string s(cp,n)s是cp指向的数组中前n个字符的拷贝
string s(s2,pos2)

s是string s2从下标pos2开始的字符的拷贝,

若pos2>s2.size(),构造函数未定义。

string s(s2,pos2,len2)

s是string s2从下标pos2开始len2个字符的拷贝。

pos2>s2.size()且构造函数最多拷贝s2.size() - pos2个字符

通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。

当从一个string拷贝字符时,可以提供一个可选的开始位置和一个计数值,若开始位置大于size,则构造函数抛出out_of_range异常。

子字符串操作
s.substr(pos,n)

返回一个string,包含s中从pos开始的n个字符的拷贝。

pos默认值为0,n的默认值为s.size() - pos.

5.2、改变string的其他方法

除了接受迭代器的insert和erase版本外,string还提供了接受下标的版本。

s.insert(s.size(),5,'o'); //在s末尾插入5个o
s.erase(s.size() - 5,5);  //在s删除最后5个字符

标准库string类型提供接受C风格字符数组的insert和assign版本。

const char *cp = "hello world";
s.assign(cp,5);          //s = "hello"
s.insert(s.size(),cp+5); //s = "hello world"

允许指定将来自其他string或子字符串的字符插入到当前的string或赋予当前string。

string s = "hello world";
string s1 = " some string";
s.insert(0, s1);  //在s中位置0之后插入s1的拷贝
s.insert(0,s1,0,s1.size()); //在s[0]之后插入s1[0]开始的s1.size()个字符

string类的成员函数append用于在string末尾进行插入操作的一种简化。

s2.append("world"); //将world追加到s2末尾

string的成员函数replace是调用erase和insert的一种简写方式,类似替换作用。

s2.replace(5,3,"gas"); 
//从位置5开始,删除3个字符并插入"gas"

replace函数的第三个参数文本不一定要与删除文本的长度一致。

assign总是替换string中的所有内容,append总是将新字符追加到string末尾。

 5.3、string搜索操作

 string提供了多个搜索函数,每个搜索操作都返回一个string::size_type值,表示搜索值的下标。

若搜索失败,则返回一个名为string::npos的static成员。

npos类型为const string::size_type,并初始化值为-1,是unsigned类型。

搜索(以及其他string操作)是大小写敏感的。

string搜索操作
s.find(args)查找s中args第一次出现的位置
s.rfind(args)查找s中args最后一次出现的位置
s.find_first_of(args)在s中查找args中任意字符第一次出现的位置
s.find_last_of(args)在s中查找args中任意字符最后一次出现的位置
s.find_first_not_of(args)在s中查找第一个不在args中的字符
s.find_last_not_of(args)在s中查找最后一个不再args中的字符

rfind成员函数从右向左搜索,相当于反转来查找。

5.4、compare函数

string的compare函数类似C的strcmp函数,根据s是等于、大于还是小于参数指定的字符串,返回0、正数或负数。

compare函数有以下6个版本:

compare的参数形式
s2比较s和s2
pos1,n1,s2

将s中从pos1开始的n1个字符与s2进行比较

pos1,n1,s2,pos2,n2将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较
cp比较s与cp指向的以空字符结尾的字符数组
pos1,n1,cp将s中从pos1开始的n1个字符与cp指向的字符数组进行比较
pos1,n1,cp,n2

将s中从pos1开始的n1个字符与指针cp指向的地址的n2个字符进行比较

 5.5、数值转换

字符串中常常包含表示数值的字符,新标准引入多个函数来实现数值数据与标准库string之间的转换。

int i = 42;
string s = to_string(i);  //整数转换成字符表示形式
double d = stod(s);       //将字符串s转换成浮点数

如果string不能转换为一个数值,这些函数会抛出invalid_argument异常。

string和数值之间的转换
to_string(val)一组重载函数,返回数值val的string表示,val可以是任何算术类型
stoi(s,p,b)

返回s的起始子串的数值,返回类型是int。

p是size_t指针,用来保存s中第一个非数值字符的下标。

stol(s,p,b)返回类型是long
stoul(s,p,b)返回类型是unsigned long
stoll(s,p,b)long long
stoull(s,p,b)unsigned long long
stof(s,p)float
stod(s,p)double
stold(s,p)long double

6.容器适配器 

 适配器是标准库中一个通用概念,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物。

容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。

标准库中定义了三个顺序容器适配器:stack、queue和priority_queue。

容器适配器共有的操作和类型
size_type一种类型,足以保存当前类型的最大对象的大小
value_type元素类型
container_type实现适配器的底层容器类型
A a;创建一个名为a的空适配器
A a(c);创建一个适配器,带有容器c的一个拷贝
a.empty()若a包含任何元素,返回false,否则返回true
a.size()返回a中的元素数目

swap(a,b)

a.swap(b)

交换a和b的内容,a和b必须有相同的类型,底层容器类型也必须相同

默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。

在创建一个适配器时,将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。

stack<string,vector<string>> str;  //在vector上实现的空栈

stack可以使用除array和forward_list之外的任何容器类型来构造。

queue可以构造于list或deque之上,但不能基于vector构造。

priority_queue可以构造于vector或deque之上,但不能基于list构造。

stack类型定义在stack头文件中。

栈的操作
s.pop()删除栈顶元素,但不返回该元素值

s.push(item)

s.emplace(args)

创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或由args构造
s.top()返回栈顶元素,但不将元素弹出栈

 每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作,不能使用底层容器的操作。

queue和priority_queue适配器定义在queue头文件中。

队列适配器操作
q.pop()

返回queue的首元素或priority_queue的最高优先级元素,

但不删除该元素

q.front()返回首元素,但不删除
q.back()返回尾元素,但不删除
q.top()返回最高优先级元素,但不删除

q.push(item)

q.emplace(args)

在queue末尾或priority_queue中创建一个元素,其值为item,或由args构造

标准库queue使用一种先进先出的存储和访问策略。

priority_queue允许我们为队列中的元素建立优先级,新加入的元素会排在所有优先级比它低的已有元素之前。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值