本文涉及:
向量容器部分:
标准顺序容器 vector(向量容器) 的介绍 向量数组和数组的关系
向量的特性
向量容器对象的初始化
向量容器对象常用的操作方法
二维向量容器的初始化 获取二维向量容器的大小 赋值和输出 元素的查改和遍历操作
二维向量容器的增加操作 删除操作
迭代器和算法部分:
迭代器的介绍 迭代器种类
迭代器的操作 迭代器的功能分类 不同容器的迭代器的功能分类表 迭代器的辅助函数
算法的简介 容器对象的常见算法
列表容器部分:
标准顺序容器 list(列表容器)列表容器的结构图
列表容器和向量容器的关系列表容器的特点
列表容器对象的初始化列表容器对象的基本操作方法
列表容器对象的算法操作方法列表容器对象的总结
双端队列容器部分:
标准顺序容器 deque(双端队列容器) 双端队列容器的结构
deque容器迭代器 双端队列容器的定义和初始化
双端队列容器的基本操作方法 双端队列容器的增加,删减和插入
关联容器部分:
标准关联容器 map类(关联数组容器,键值对容器) map类关联容器的类型介绍及其所需的头文件 和 排序规则
pair类型的介绍 声明 初始化 pair类型的成员访问
map容器的介绍 声明 初始化 map容器额外的类型别名
map容器的使用 map容器的元素遍历操作 模板函数的使用(不定类型作为参数传入函数)
map容器的使用 map容器的元素插入操作 访问和查询操作 删除操作
类型别名的实际运用
map容器部分:
map容器的元素的区间操作
multmap容器 multmap和map容器的不同 添加多个同关键字的元素
set容器部分:
标准关联容器 set类(集合容器) set容器的特点 set容器的类型 set容器的头文件
set容器和map容器的异同 set容器的初始化 set容器元素的遍历 multiset容器
容器总结部分:
容器的总结🎯 标准容器类特点说明表 标准容器中顺序容器和关联容器的共有函数表
所有标准容器的共有函数表
容器
什么是容器:
容器都是类模板.它们实例化后就成为容器类,用容器类定义的对象称为容器对象,例如vector<int>
是一个容器类的名字而vector<int> a
就定义了一个容器对象 a
.
此前我们发觉编写的每个程序都或多或少的要存储一些数据,此前C++在这方面只提供了几种最基本的方法:
方法一:创建局部或全局变量来保存单个数据的值
方法二:使用数组来保存多个数据的值.
数组作为容器的弊端:
上面关于容纳两个或更多值的数据结构我们提到过数组,数组是C++唯一直接支持的容器,但是数组不适合用来解决所有的问题.
例如若现在需要编写一个简单的拼写检查程序,若使用数组,则需要在此数组中放入一个相当长的单词表,需要把语法中每一个合法的单词都写入这个数组,通过检查这个给定的单词是否在数组内来判断单词是否合法…
由于利用数组我们不得不遍历每一个数组元素并将此元素与给定单词进行比较,所以用数组这个容器来处理这样庞大复杂的数据是不现实的,因为数组的效率此时将十分低下,此时便需要别的容器来解决这个问题.
为了解决上述的问题,在数据存储上,有一种对象类型,它可以持有其他对象或持有指向其他对象的指针,这种对象类型就叫做容器.也就是说,容器就是一种保存其他对象的对象,容器这种对象还包含了一系列处理其他对象的方法.
此篇文章引出的概念:能容纳多个元素值且存储的元素数不在编译其间确定的数据结构,我们通常称之为容器(container)
在C++标准库中有许多现成的容器,这个容器都经过不断的精心设计和测试封装,可以直接拿来使用,不过需要注意的是,找到最合适的容器只是编程工作的一部分,要实现程序的最优效率,还需一些适当的函数(算法)来处理这个容器内的数据.
容器的种类:
C++常用的容器类型分为标准顺序容器和关联容器和容器适配器三个大的类型.
标准顺序容器
它们之所以被称为顺序容器,是因为元素在容器中的位置同元素的值无关,即容器不是自动排序的,顺序容器将元素插入容器时,指定在什么位置(尾部、头部或中间某处)插入,元素就会位于什么位置.
用术语来说,标准顺序容器是一种各元素之间有顺序关系的线性表,一种线性结构的可序群集,即顺序性容器中的每个元素均有固定的位置,除非使用删除或插入的操作改变这个位置.顺序容器的元素排列次序和元素的值无关,而是由元素添加到容器内的先后次序决定.
顺序容器包括: vector(向量),list(列表),deque(队列)
关联容器
对比顺序容器,关联容器是按照 关键字 来保存和访问的,而顺序容器是按照元素在容器中的位置来顺序保存和访问的
关联容器内的元素是自动排序的.插入元素时,容器会按一定的排序规则将元素放到适当的位置上,因此关联容器插入元素时不能指定其元素的具体位置
默认情况下,关联容器中的元素是从小到大排序(或按关键字从小到大排序)的,而且能用运算符比较元素或关键字大小.因为是排好序的,所以关联容器在查找时具有非常好的性能.
用术语来说,关联式容器是非线性的树结构(更准确的来说是二叉树结构),其各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序.但是,关联式容器提供了另外一种根据元素特点进行排序的功能,这样迭代器就能根据元素的特点"有序的"获取关联容器中的元素.
关联容器包括: map(集合),set(映射),multimap(多重集合),multiset(多重映射)
容器适配器
除了以上顺序和关联两类容器外,STL标准库还在两类容器的基础上为了屏蔽一部分功能,突出或增加另一部分功能而实现了三种容器适配器.
适配器本质上是使一种事物的行为类似于另外一种事物行为的一种机制,容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现,适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现(即先用适配器保存一个容器,这个容器再保存所有元素)
通俗点来说,容器适配器就是适用于容器的接口转换装置,能让我们使用特定的方法去操作一些我们本来无法进行操作的东西.例如我们上述的容器(vector,list,deque
等),这些容器支持的操作很多,比如元素插入,删除,迭代器访问等等…但此时,我们需要vector
(先进先出)这个容器可以像操作栈一样操作元素(先进后出),这时我们没有必要重新写一个数据结构,而是 把原来的vector
容器重新进行封装(改变其接口),就能把vector
容器当作栈用了.
STL库中包含三种容器适配器: stack(栈),queue(队列),priority_queue(优先级队列)
这三种容器适配器的数据结构为:
stack
栈(先进后出)
queue
队列(先进先出)
priority_queue
优先级队列(按照优先级"<"符号进行元素排序)
标准顺序容器 vector(向量容器)
数组这个数据结构最大的先天不足就是它受限于一个固定的长度,若在程序里用int array[40]
这样的语句定义一个数组时,程序将遇到两个问题:
问题一:array
这个数组中只能存储最多40个整型数据
问题二:不管程序是不是真的需要存储40个整型数据,编译器都会为其分配40个整型数据的空间,造成内存资源的浪费.
而由C++标准库提供的向量(vector)类型则从根本上解决了数组先天不足的问题,我们可以像创建各种不同类型的数组一样创建各种不同类型的向量.
在使用向量(vector)这个数据结构之前,我们必须在程序中包含其相关的头文件
#include <vector>
向量容器和数组的关系:
首先,向量容器类似于数组,有一段连续的内存,能非常好的支持随即存取(访问元素的效率高),有固定的起始地址,可进行随机存取元素的操作(即可以像数组一样用[ ]
操作符进行容器内元素的随机访问)
但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝(中间插入元素的删除效率不高),
向量容器是一个能存放任意类型的动态数组,是动态连续空间,是一种顺序的容器
而数组是一段静态的连续空间,其大小长度都在程序编译时被确定
但是向量容器若插入过多的元素后,预留的内存空间可能也会不够,此时需要重新申请一块足够大的内存并把原来的数据拷贝到新的内存空间,在重新分配空间时它会做这样的动作:
首先,vector 会申请一块更大的内存块
然后,将原来的数据拷贝到新的内存块中
其次,销毁掉原内存块中的对象(调用对象的析构函数)
最后,将原来的内存空间释放掉
虽然这些操作影响了向量容器的效率,但是实际上用的最多的还是向量容器,大多数时候使用它效率一般是不错的
另外,向量容器和数组一样,都可以存放任意对象,但除了引用,即 不存在存储引用的数组和向量容器
数组名不仅表示数组的名称,还代表了数组的首地址,数组名可看作指针,并可以使用数组名进行一些类似于指针的操作,而容器的名称没有类似于数组名的那些操作.
执行效率上数组高于向量,主要原因是向量容器的扩容过程要消耗大量的时间.
数组存储在相同的内存区域( 栈 )中,而向量容器对象存储在自由存储区( 堆 )中
数组对比向量容器的的缺点:
数组的长度必须是常量表达式,并且在初始化的时候就要给出来,而向量容器初始化可以不给长度.
数组之间不能够进行整体之间的复制(即数组对于交换只能通过遍历的方式逐个交换元素),但是向量容器可以进行整体之间的赋值.
数组调用元素的时候下标容易越界,且数组只能通过下标访问,写程序中很容易出现越界的错误.虽然向量容器也可通过下标来访问容器内的元素,但其还可以是由较多的机制控制元素(比如迭代器 at()
,front()
和back()
),所以向量容器相较于数组更安全.
向量容器在声明变量后,在声明周期完成后,会自动地释放其所占用的内存.而对于数组如果用new
申请的空间,必须用对应的delete[ ]
释放其内存.
总结:🎯
向量容器只用默认初始化时,不能通过下标进行添加元素
向量容器初始化可以不给长度.
向量容器可以进行整体之间的赋值.
向量容器相较于数组更安全(有效的避免越界等问题)
向量容器的名称没有类似于数组名的那些操作(即容器名不指代容器的地址)
向量容器的名称没有类似于数组名的那些操作,向量名不可被看作指针
向量容器不能存储引用类型的数据
向量的特性:
我们无须对一个向量能容纳多少元素做出限定,因为向量可以动态的随着压入元素的增加而相对无限增大(前提是内存足够)
定义一个向量后,可以用向量的.push_back()方法往向量中添加元素,且还可以用访问数组元素的语法来访问某给定向量中的各个元素,即把一些元素放到向量里之后,我们就可以像数组一样用赋值操作符对向量中的元素进行操作了,例如vectorA[0]="B"
我们还可以使用向量的.size()方法来查知某已给定向量的当前长度(即此向量当前包含的元素的个数)
还可以使用向量的.begin()方法来获取这个向量容器的第一个元素的地址(vector.begin()
即指向向量容器顶部元素的地址的指针,其元素值为*vector.begin()
)
还可以使用向量的.end()方法来获取这个向量容器的底部的地址(vector.end()
即指向向量容器最后一个元素下一个位置的地址后一个地址的指针)
向量的语法格式:
vector<type> vectorName;
其中vector
是声明向量数据类型关键字,要使得这个关键字有效,必须包含#include <vector>
头文件.<type>
中的type
是这个向量容器即将存放的数据的类型,而vectorName
则是这个向量数组的名字.
向量容器对象的初始化:
若vector
向量容器没有指定元素的初始化式(未初始化赋值),那么标准库将自行提供一个元素初始值进行容器的初始化,具体提供声明类型的元素初始值,取决于存储在vector
中的元素的数据类型,即若容器内存储的类型为int
类型,则使用0
值创建元素初始化式
若vector
保存的是含有构造函数的类类型(例如string
)的元素,标准库将用该类型的默认构造函数创建元素初始化式
若vector
保存的元素类型是没有定义任何构造函数的类类型,这种情况下标准库仍然产生一个带初始值的对象,这个对象对每个成员进行了值初始化.
默认初始化:
vector<int> vec1;
//🔥执行默认初始化,此时vec1为空,只用默认初始化时,不能通过下标进行添加元素
使用一个向量容器初始化另一个向量容器:
vector<int> vec2(vec1);
//🔥使用空的向量容器vec1初始化vec2这个向量容器,此时vec2中包含vec1所有元素的副本
//也可写做:
vector<int> vec2(vec1.begin(), vec1.end());
//和
vector<int> vec2=vec1;
//运行结果一致
初始化向量容器的元素个数(并利用for
循环和vector
的.size()
方法打印其元素值):
vector<int> vec3(3); //🔥初始化vec3容器中有3个值为0(默认初始化为0)的元素
//其中3为3个重复元素,每个元素的值都是0
for(int i=0;i<vec3.size();i++)
{
cout << vec3[i]<< endl;
}
运行结果:
"0 0 0"
初始化向量容器的元素个数和其全部元素的值:
vector<int> vec4(3,4); 初始化vec4容器中有3个值为4的元素
//其中3为3个重复元素,每个元素的值都是4
运行结果:
"4 4 4"
初始化并定义向量容器的元素个数和其各个元素的值:
vector<int>vec2 = {
1,2,3,4,5 }; 初始化vec4容器中有5个值分别为1,2,3,4,5的元素
也可写做:
vector<int>vec2{
1,2,3,4,5 };
运行结果:
"1 2 3 4 5 "
注意:🎯
注意vector<T> vec(n)
和vector<T> vec{n}
的区别:前者说的是vec
中有n
个相同的元素,至于值为多少要看相应对象的初始化值.而后者,则是说vec
中只有一个元素,其值为n
.
不能使用包含着多个值的括号去初始化vector对象,例如vector<int>vec(1,2,3) //错误
不能和vector<int>vec{1,2,3}
混淆,后者意为容器内有三个元素为123
的int类型的值.
向量容器对象常用的操作方法
定义一个int
类型的向量容器vec
:
vector<int>vec;
再定义一个以向量容器引用为传递参数的打印vec
向量容器中所有元素的函数CoutVector
void CoutVector(vector<int>& vec) //打印容器元素函数定义
{
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << endl;
}
cout << "\n\n" << endl;
}
CoutVector(vec);//打印容器元素函数调用
向量队列容器内元素的访问 :
下标访问容器内元素([])
cout << vec[2] << endl; //🌟使用下标访问容器内元素时并不会检查访问是否越界
at方法访问容器内元素(.at)
cout << vec.at(2) << endl; //🌟使用.at访问容器内元素时并会检查访问是否越界,如果访问越界是则抛出 out of range 异常
访问容器内第一个和最后一个元素(.front() 和.back() )
cout << vec.back() << endl; //其返回容器内第一个元素的引用
cout << vec.front() << endl;//其返回容器内最后一个元素的引用
1.指向容器内首尾部元素位置的各种迭代器(.begin()和.end()…)
vector<int>::iterator VecIterB = vec.begin();
//🔥.begin() 返回一个迭代器,它指向容器vec的第一个元素
vector<int>::iterator VecIterE = vec.end();
//🔥.end() 返回一个迭代器,它指向容器vec的最后一个元素的下一个位置
vector<int>::reverse_iterator VecIterRB = vec.rbegin();
//🔥.rbegin() 返回一个逆序迭代器,它指向容器vec的最后一个元素
vector<int>::reverse_iterator VecIterRE = vec.rend();
//🔥.rend() 返回一个逆序迭代器,它指向容器vec的第一个元素前面的位置
2.在向量容器的尾部添加元素(.push_back())
vec.push_back(1);//给向量容器中添加一个元素 元素值为1
//🔥此push_back方法每被调用一次,向量容器中便被添加一个元素
打印结果:"1"
注意:向量容器vector
只允许在容器尾部进行元素的直接添加,而不能在容器头部直接进行元素的添加(即只支持push_back()
方法而不支持push_front()
方法)
4.获取向量容器的大小信息(.size())
int Size = vec.size();//🔥获取向量容器的大小信息,其返回值为int类型
或
int Size = vec[1].size();//🔥获取向量容器中某个元素的大小信息,其返回值为int类型
4.1获取向量容器的最大容量(.max_size())
vector<int>V1(5,8); //其容器内值为 "8 8 8 8 8"
cout << V1.size();//🔥得出的V1这个容器的大小为 "5"
cout << V1.max_size() << endl;
//🌟max_size用于获取向量容器能容纳元素的最大值
//若容器为int类型(4字节),其能容纳的元素最大值为 "4611686018427387903"
//若容器为char类型(1字节),其能容纳的元素最大值为"9223372036854775807"
5.判断向量容器是否为空容器(.empty())
bool Emp = vec.empty();//🔥获取向量容器的大小信息,其返回值为int类型判断向量容器是否为空容器,其返回值为bool类型
6.从向量容器某位置插入元素(.insert())
🔥若此时vec容器内有一个元素 "1"
🔥重载方式一 :
vec.insert(vec.end(),3);//🔥从向量容器的.end迭代器的位置(容器末尾元素再后一个位置),插入一个值为3的元素
打印结果:"1 3"
🔥重载方式二 :
vec.insert(vec.end(),5, 3);//🔥从向量容器的.end迭代器的位置(容器末尾元素再后一个位置),插入5个值为3的元素
打印结果:"1 3 3 3 3 3"
vec.insert(vec.begin(),5, 3);//🔥从向量容器的.begin迭代器的位置(容器顶部),插入5个值为3的元素
打印结果:"3 3 3 3 3 1"
🔥若此时vec容器内有6个元素 "3 3 3 3 3 1"
vec.insert(vec.end()-2,2,8);//🔥从向量容器的.end迭代器前两位的位置(容器倒数第二个元素),插入2个值为8的元素
打印结果:"3 3 3 3 8 8 3 1"
vec.insert(vec.begin()+2, 3);//🔥从向量容器的.begin迭代器后两位的位置(容器第二个元素),插入1个值为8的元素
打印结果:"3 3 8 3 3 3 1"
🔥重载形式三:
此时向量容器V1为: " 8 8 8 8 8 " 向量容器V2元素为: " 9 9 9 9 "
V1.insert(V1.begin(), V2.begin(), V2.end());//🌟利用指向V1头部位置的迭代器和另外一个容器的首尾迭代器,插入另一个容器V2到V1中到V1头部位置
调用CoutVector函数打印V1列表元素结果: "9 9 9 9 8 8 8 8 8"
调用CoutVector函数打印V2列表元素结果: "9 9 9 9" 🔥V2并没有变为空容器
5.删除处于向量容器末尾的元素(.pop_back())
🔥若此时vec容器内有6个元素 "3 3 3 3 3 1"
vec.pop_back(); //🔥删除处于向量容器末尾的元素,无返回类型
打印结果:"3 3 3 3 3 "
向量容器vector的成员函数
pop_back()
可以删除向量容器中的最后一个元素而函数
erase()
可以删除由一个iterator
(迭代器)指出的元素,也可以删除一个指定范围的元素
除此之外还可以采用通用算法remove()
来删除vector容器中的元素不同的是:采用
remove
一般情况下不会改变容器的大小,而pop_back()
与erase()
等成员函数会改变容器的大小,所以remove不建议使用,详见vector删除元素🔍
7.从指定容器删除指定位置的元素或某段范围内的元素,其他元素前移(.erase())
🔥vector::erase()方法有两种重载形式
重载形式一 :
iterator erase ( iterator position );//🔥删除position处的一个字符(position是个string类型的迭代器)
🔥若此时vec容器内有6个元素 "3 3 3 3 3 1"
vec.erase(vec.begin()+2); //使用重载形式一,把容器顶部位置后方第二位元素删除
打印结果:"3 3 3 3 1"
重载形式二 :
iterator erase(iterator first, iterator last);//🔥删除从first到last之间的字符(first和last都是迭代器)
🔥若此时vec容器内有6个元素 "3 3 3 3 3 1"
vec.erase(vec.begin(),vec.end()); //使用重载形式二,自容器顶部至底部所有元素被删除
打印结果:" "//元素全部被删除
8.判断两个向量容器是否相等和对比其大小( == != >= <=…)
vector<int>vec2;
vec2.insert(vec2.begin(),5,3);//🔥新建一个用于做对比的向量容器vec2,其被插入为4个值为4的元素(和vec不一致)
此时:
vec的值为 : 3 3 3 3 3 1
vec2的值为 : 3 3 3 3 3
if (vec == vec2) //🔥判断两个向量容器是否相等
{
cout << "vec和vec2向量一致";}
else{
cout << "vec和vec2向量不一致";}
结果:"vec和vec2向量不一致"
9.清空向量容器中的所有元素(.clear())
vec.clear();
二维向量容器
二维向量容器的初始化:
首先我们使用#define
宏定义几个多维向量容器需要使用的参数(之所以定义为宏参数,是为了方便后期在 局部作用域 方便调用)
#define row 3 // 二维数组3行
#define col 5