简介
C++ 标准模板库(Standard Template Library,STL)是一套功能强大的 C++ 模板类和函数的集合,它提供了一系列通用的、可复用的算法和数据结构。
STL 的设计基于泛型编程,这意味着使用模板可以编写出独立于任何特定数据类型的代码。
STL 分为多个组件,包括容器(Containers)、迭代器(Iterators)、算法(Algorithms)、函数对象(Function Objects)和适配器(Adapters)等。
使用 STL 的好处:
- 代码复用:STL 提供了大量的通用数据结构和算法,可以减少重复编写代码的工作。
- 性能优化:STL 中的算法和数据结构都经过了优化,以提供最佳的性能。
- 泛型编程:使用模板,STL 支持泛型编程,使得算法和数据结构可以适用于任何数据类型。
- 易于维护:STL 的设计使得代码更加模块化,易于阅读和维护。
容器
容器是 STL 中最基本的组件之一,提供了各种数据结构,包括向量(vector)、链表(list)、队列(queue)、栈(stack)、集合(set)、映射(map)等。这些容器具有不同的特性和用途,可以根据实际需求选择合适的容器。
容器是用来存储数据的序列,它们提供了不同的存储方式和访问模式。
STL容器实际上是一种类模板。
STL 中的容器可以分为三类:
1、序列容器:存储元素的序列,允许双向遍历。
- std::vector:动态数组,支持快速随机访问。
- std::deque:双端队列,支持快速插入和删除。
- std::list:链表,支持快速插入和删除,但不支持随机访问。
2、关联容器:存储键值对,每个元素都有一个键(key)和一个值(value),并且通过键来组织元素。
- std::set:集合,不允许重复元素。
- std::multiset:多重集合,允许多个元素具有相同的键。
- std::map:映射,每个键映射到一个值。
- std::multimap:多重映射,允许多个键映射到相同的值。
3、无序容器(C++11 引入):哈希表,支持快速的查找、插入和删除。
- std::unordered_set:无序集合。
- std::unordered_multiset:无序多重集合。
- std::unordered_map:无序映射。
- std::unordered_multimap:无序多重映射。
vector向量
包含头文件——#include<vector>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// 创建一个向量存储 int
vector<int> vec;
int i;
// 显示 vec 的原始大小
cout << "vector size = " << vec.size() << endl;
// 推入 5 个值到向量中
for(i = 0; i < 5; i++){
vec.push_back(i);
}
// 显示 vec 扩展后的大小
cout << "extended vector size = " << vec.size() << endl;
// 访问向量中的 5 个值
for(i = 0; i < 5; i++){
cout << "value of vec [" << i << "] = " << vec[i] << endl;
}
// 使用迭代器 iterator 访问值
vector<int>::iterator v = vec.begin(); // 这里的类型写auto会好点
while( v != vec.end()) {
cout << "value of v = " << *v << endl;
v++;
}
return 0;
}
- push_back( ) 成员函数在向量的末尾插入值,如果有必要会扩展向量的大小。
-
pop_back() 删除最后一个值
- size( ) 函数显示向量的大小。
- begin( ) 函数返回一个指向向量开头的迭代器。
- end( ) 函数返回一个指向向量末尾的迭代器。
-
empty() 判断是否为空,空则返回真,反之返回假
-
clear() 删除容器中所有元素
-
resize() 重新指定大小
-
erase() 清除数组中的一些元素
-
insert(const_iterator pos,ele); //迭代器指向位置pos插入元素ele
insert(const iterator pos,int count,ele);//迭代器指向位置pos插入count个元素ele
erase(const_iterator pos); //删除迭代器指向的元素
erase(const_iterator start, const.iterator end);//删除迭代器从start到end之间的元素 -
#include<iostream> #include<vector> #include<algorithm> using namespace std; //push_back(ele); //尾部插入元素ele //pop_back(); //删除最后一个元素 //insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele //insert(const iterator pos, int count, ele);//迭代器指向位置pos插入count个元素ele //erase(const_iterator pos); //删除迭代器指向的元素 //erase(const_iterator start, const.iterator end);//删除迭代器从start到end之间的元素 //clear(); //删除容器中所有元素 void fun(int i) { cout << i<<" "; } void test1() { vector<int> a; a.push_back(1); a.push_back(2); a.push_back(3); a.push_back(4); a.push_back(5); //12345,不删除元素的情况下的原始数据 cout << "不删除元素的情况下的原始数据" << endl; for_each(a.begin(), a.end(), fun); cout << endl; //删除了最后一个元素的数据 cout << "删除了最后一个元素的数据" << endl; a.pop_back(); for_each(a.begin(), a.end(), fun); cout << endl; //插入了一个0后的数据 cout << "插入了一个0后的数据" << endl; a.insert(a.begin(), 0); for_each(a.begin(), a.end(), fun); cout << endl; //又插入了两个0后的数据 cout << "又插入了两个0后的数据" << endl; a.insert(a.begin(), 2,0); for_each(a.begin(), a.end(), fun); cout << endl; //删除了开始迭代器指向的位置 cout << "删除了开始迭代器指向的位置" << endl; a.erase(a.begin()); for_each(a.begin(), a.end(), fun); cout << endl; //删除了开始迭代器到迭代器往后的两位之间的数据 cout << "删除了开始迭代器到迭代器往后的两位之间的数据" << endl; a.erase(a.begin(), a.begin() + 2); for_each(a.begin(), a.end(), fun); cout << endl; } int main() { test1(); return 0; }
-
sort(beg,end) 对区间beg到end里的数据从小到大排序
// 遍历
// for循环来遍历
void bl(vector<int>& a)
{
for (vector<int>::iterator it = a.begin(); it != a.end(); it++)
{
cout << *it << endl;
}
}
// 遍历算法——算法头文件#include<algorithms>
for_each(v.begin(),v.end(),myprint)
// 代码示例:
void fun(int i)
{
cout << i<<" ";
}
vector<int> b;
b.assign(3,10);
bl(b);
for_each(b.begin(), b.end(), fun);
//利用迭代器来遍历
vector<int>::iterator be = a.begin();
vector<int>::iterator en = a.end();
while (be != en)
{
cout << *be << " ";
be++;
}cout << endl;
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//vector.assign(beg,end);//将(beg,end)区间中的数据拷贝赋值给本身。注意该区问。
//vector.assign(n, elem);//将n个elem拷贝赋值给本身。
//vector& operator=(const vector& vec);//重载等号操作符
//vector.swap(vec); / 将vec与本身的元素互换。
void bl(vector<int>& a)
{
for (vector<int>::iterator it = a.begin(); it != a.end(); it++)
{
cout << *it<<" ";
}
cout << endl;
}
void fun(int i)
{
cout << i<<" ";
}
void test1()
{
int s[5] = { 1,2,3,4,5 };
vector<int> a;
a.assign(s,s+4);
cout << "第一次赋值得出的值为" << endl;
bl(a);
a.assign(2, 10);
cout << "第二次赋值得出的值为" << endl;
bl(a);
vector<int> b;
b = a;
bl(b);
for_each(b.begin(), b.end(), fun);
cout << endl;
vector<int> c;
c.assign(4, 2);
//交换前的值
cout << "交换前的值" << endl;
bl(a);
bl(c);
a.swap(c);
//交换后的值
cout << "交换后的值" << endl;
bl(a);
bl(c);
}
int main() {
test1();
return 0;
}
vector 的 capacity 和 size 属性区别
size 是当前 vector 容器真实占用的大小,也就是容器当前拥有多少个容器。
capacity 是指在发生 realloc 前能允许的最大元素数,即预分配的内存空间。
当然,这两个属性分别对应两个方法:resize() 和 reserve()。
使用 resize() 容器内的对象内存空间是真正存在的。如果重新指定的比原来长,默认用0填充新的位置,如果重新指定的比原来短,超出部分会删除掉
使用 reserve() 仅仅只是修改了 capacity 的值,容器内的对象并没有真实的内存空间(空间是"野"的)。
此时切记使用 [] 操作符访问容器内的对象,很可能出现数组越界的问题。
下面用例子进行说明:
#include <iostream>
#include <vector>
using std::vector;
int main(void)
{
vector<int> v;
std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl;
v.reserve(10);
std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl;
v.resize(10);
v.push_back(0);
std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl;
return 0;
}
运行结果为:(win 10 + VS2010)
注: 对于 reserve(10) 后接着直接使用 [] 访问越界报错(内存是野的),大家可以加一行代码试一下,我这里没有贴出来。
这里直接用[]访问,vector 退化为数组,不会进行越界的判断。此时推荐使用 at(),会先进行越界检查。
⭐注意:在局部区域中创建vector数组,在堆空间里开——因为栈区比较小,假如放非常长的数组会发生爆栈,因此局部区域不可以开大长度数组,但是可以开大长度vector。
⭐与数组的区别:vector可动态分配
动态分配——vector先开辟一块空间,然后假如需要的空间大于这个开辟的空间了,vector不会在原有空间后面再开辟一块空间,而是在其他地方再找一块空间,然后把原有空间的值给复制到这个新空间,然后插入需要的值,这种方法导致——原有的迭代器将没用了,它们指向的不是这个新的空间,而老的空间已经报废了,因此这个动态内存分配会出现这个问题。
总结:vector 其实就是一个动态数组,它没有具体的大小,它的大小是在动态分配的,在不确定大小或想省内存的话 vector 是一个不错的选择。使用vector实现邻接表,更为简单。
set(集合)
是一个内部自动有序且不含重复元素的容器。
set可以在需要去重复元素的情况大放异彩,节省时间,减少思维量。
要使用set,需要添加头文件:
#include <set>
using namespace std;
1.set的定义
像定义变量一样定义set变量:
set<类型名> 变量名;
类型名可以是int、double、char、struct,也可以是STL容器:vector、set、queue。
用例:
set<int> name;
set<double> name;
set<char> name;
set<struct node> name;
set<set<int> > name;//注意:> >之间要加空格
set数组的定义和vector相同:
set<类型名> array[SIZE];
例如:
set<int> arr[10];
2.容器内元素的访问
set只能通过迭代器(iterator)访问:
set<int>::iterator it;
set<char>::iterator it;
这样,就得到了迭代器it,并且可以通过*it来访问set里的元素。
注意:
除了vector和string之外的STL容器都不支持*(it+i)的访问方式,因此只能按照如下方式枚举:
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
st.insert(5);
st.insert(2);
st.insert(6);
for (set<int>::iterator it = st.begin(); it != st.end(); it++)
{
cout << *it << endl;
}
return 0;
}
输出:
2
5
6
我们可以看到,原本无序的元素,被插入set集合后,set内部的元素自动递增排序,并且自动去除了重复元素。
3.set常用函数实例解析
(1)insert()
插入元素十分简单。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<char> st;
st.insert('C');
st.insert('B');
st.insert('A');
for (set<char>::iterator it = st.begin(); it != st.end(); it++)
{
cout << *it << endl;
}
return 0;
}
(2)find()
find(value)返回的是set中value所对应的迭代器,也就是value的指针(地址)。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
for (int i = 1; i <= 3; i++)
{
st.insert(i);
}
set<int>::iterator it = st.find(2); //在set中查找2,返回其迭代器
cout << *it << endl;
// 以上可以直接x携程
cout << *(st.find(2)) << endl;
return 0;
}
输出:
2
2
(3)erase()
erase()有两种用法:删除单个元素、删除一个区间内的所有元素。
1.删除单个元素
删除单个元素有两种方法:
- st.erase(it),其中it为所需要删除元素的迭代器。时间复杂度为O(1)。可以结合find()函数来使用。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
st.insert(100);
st.insert(200);
st.insert(100);
st.insert(300);
// 删除单个元素
st.erase(st.find(100)); //利用find()函数找到100,然后用erase删除它
st.erase(st.find(200));
for (set<int>::iterator it = st.begin(); it != st.end(); it++)
{
cout << *it << endl;
}
return 0;
}
输出:
300
- st.erase(value),value为所需要删除元素的值。其时间复杂度为O(logN),N为set内的元素个数。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
st.insert(100);
st.insert(200);
st.insert(100);
st.insert(300);
// 删除单个元素
st.erase(100);
for (set<int>::iterator it = st.begin(); it != st.end(); it++)
{
cout << *it << endl;
}
return 0;
}
输出:
200
300
2.删除一个区间内的所有元素
st.erase(iteratorBegin , iteratorEnd)可以删除一个区间内的所有元素。
其中iteratorBegin为所需要删除区间的起始迭代器
iteratorEnd为所需要删除区间的结束迭代器的下一个地址
也即是[iteratorBegin,iteratorEnd)
#include <iostream>
#include <set>
using namespace std;
//2.删除一个区间内的所有元素
int main()
{
set<int> st;
st.insert(100);
st.insert(200);
st.insert(100);
st.insert(300);
set<int>::iterator it = st.find(200);
st.erase(it, st.end());
for (it = st.begin(); it != st.end(); it++)
{
cout << *it << endl;
}
return 0;
}
输出:
100
(4)size()
不难理解,szie()用来实时获得set内元素的个数,时间复杂度为O(1)。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
st.insert(2);
st.insert(5);
st.insert(4);
cout << st.size() << endl;
return 0;
}
输出:
3
deque(双端队列)
1)基本概念和介绍
deque容器为双端队列,可以对其两段的数据进行操作,因为它没有capacity属性,因此不会像vector那样”旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”,因此,deque没有必须要提供所谓的空间保留(reserve)功能。
2)头文件
#include <deque>
3)内部结构
4)常用API操作
①构造函数
deque的构造函数与vector类似,也是四种常见的方式
deque<int> d1;
for (int i = 0; i < 10; ++i)
{
d1.push_back(i);
}
deque<int> d2(d1);
deque<int> d3(10, 100);
deque<int> d4;
d4 = d3;
②赋值操作
deque<int> d1;
for (int i = 0; i < 10; ++i)
{
d1.push_back(i);
}
deque<int> d2;
d2 = d1;
deque<int> d3;
d3.assign(d1.begin(), d1.end());//将[beg, end)区间中的数据拷贝赋值给本身
deque<int> d4;
d4.assign(10, 100);
③大小和容量
上文提到,deque容器无capacity()函数
if (d1.empty())
{
cout << "deque容器为空" << endl;
}
else
{
cout << "deque容器不为空" << endl;
cout << "deque容器的大小为:" << d1.size() << endl;
//deque容器无capacity - 容量
}
//改变大小
//d1.resize(15);
d1.resize(15,1);
print(d1);
d1.resize(5);
print(d1);
③插入和删除
对于双端队列来说,插入和删除时一个亮眼的地方,因为首尾均可操作,有头插push_front(),头删pop_front(),尾插push_back(),尾删pop_back(),以及inset()插入和erase()删除
//首尾操作
deque<int> d;
d.push_back(10);
d.push_back(20); //尾插
d.push_front(100);
d.push_front(200); //头插
print(d); //200 100 10 20
d.pop_back(); //尾删
d.pop_front(); //头删
print(d); //100 10
运行结果:
//插入操作
deque<int> d;
d.push_back(10);
d.push_back(20); //尾插
d.push_front(100);
d.push_front(200); //头插
print(d); //200 100 10 20
d.insert(d.begin(), 100);
//100 200 100 10 20
print(d);
d.insert(d.begin(), 2, 900);
//900 900 100 200 100 10 20
print(d);
运行结果:
//删除操作
deque<int> d;
d.push_back(10);
d.push_back(20); //尾插
d.push_front(100);
d.push_front(200); //头插
print(d); //200 100 10 20
deque<int>::iterator it = d.begin();
it++; //迭代器往后偏移一个位置
d.erase(it);
print(d); //200 10 20
d.erase(d.begin(), d.end());
print(d);
d.clear();
print(d);
运行结果:
④ 数据存取
这里主要是区别[]方式和at()函数的访问情况
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_back(30);
d.push_front(100);
d.push_front(200);
d.push_front(300);
//通过[]方式访问
for (int i = 0; i < d.size(); ++i)
{
cout << d[i] << " ";
}
cout << endl;
//通过at访问
for (int i = 0; i < d.size(); ++i)
{
cout << d.at(i)<< " ";
}
cout << endl;
cout << "第一个元素为:" << d.front() << endl;
cout << "最后一个元素为:" << d.back() << endl;
list(列表[双向循环列表])
1)头文件
#include <list>
2)内部结构
3)常用API操作
这里的一些常规操作与前文类似,因此不做过多展示,只普通地做一些用法讲解
① 构造函数
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
② 赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
swap(lst);//使用之后实现将lst与本身的元素互换。
③ 插入和删除操作
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
3.1.4 forword_list(单向链表)
1)基本概念和介绍
对于forword_list单向链表,虽然它具有和 list 容器相同的特性,擅长在序列的任何位置进行插入元素或删除元素的操作,但对于访问存储的元素,没有其它容器(如 array、vector)的效率高,以及由于单链表没有双向链表那样灵活,因此相比 list 容器,单链表只能从前向后遍历,而不支持反向遍历
2)头文件
#include <forward_list>
3)常用API操作
begin() //返回一个前向迭代器,其指向容器中第一个元素的位置。
end() //返回一个前向迭代器,其指向容器中最后一个元素之后的位置。
assign() //用新元素替换容器中原有内容。
push_front() //在容器头部插入一个元素。
pop_front() //删除容器头部的一个元素。
swap() //交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。
remove(val) //删除容器中所有等于 val 的元素
sort() //通过更改容器中元素的位置,将它们进行排序。
这样对比两个链表容器,可以看出,如果是功能单一,不需要很复杂操作的,应该优先使用单链表,因为单链表耗用的内存空间更少,空间效率更高;但如果是需要向前或向后查找,则应该优先使用高效一些的双向循环链表。
array(数组)
1)基本概念和介绍
array是C++11中新增的容器,它与其他容器不同的是,它的大小是固定的,无法动态扩展或收缩,只允许访问或者替换存储的元素。
2)头文件
#include <array>
3)案例讲解
下面讲解一个简单的案例
从这里可以看出,array的形参列表是需要两个参数,第一个是所要创建的元素的数据类型,第二个就是数组的元素个数,这和直接创建一个a数组其实很类似,比方a[5]={1,2,3,4,5},也是同样的原理,这里的auto i是一个变量,循环时每次获得一次容器a中的变量,相当于省去int i直接循环全部内容,也就相当于for (array<int, 5>::iterator it = a.begin(); it != a.end(); ++it)
#include <array>
#include <iostream>
using namespace std;
int main()
{
array<int,5> a = {1,2,3,4,5};
for(auto i:a)
{
std::cout << "value is " << i << std::endl;
}
return 0;
}
这里是array的其他API操作和迭代器,感兴趣的小伙伴可以了解一下
set/multiset(集合/多重集合)
1)基本概念和介绍
有关set容器,它在用insert()函数插入之后会自行被排序,默认是升序,但不可重复插入,即不能有重复的key(键值),对于multiset,就可以实施多个键值的插入操作,这两个容器和map不一样,它们是简单关联容器,其参数类型只有一个,所以它的元素既是键值(value)又是实值(value)。
2)头文件
#include <set>
注:set/multiset一样,都是使用此头文件
3)常用API操作
对于构造和赋值、大小和交换、插入和删除操作,均与上述容器类似,不做展开
① 查找和统计操作
我们可以看到,在set容器中,你可以通过相关的迭代器去访问容器中相应的元素,利用find()函数,也可以用count()函数去统计相关元素在容器中出现了几次;
这是它们的相关语法:
find:
count
set<int> s1;
s1.insert(10);
s1.insert(60);
s1.insert(30);
s1.insert(20);
s1.insert(90);
s1.insert(70);
print(s1);
//查找
set<int>::iterator pos = s1.find(300);
if (pos != s1.end())
{
cout << "找到了,为:" << *pos << endl;
}
else
{
cout << "没找到" << endl;
}
//统计
set<int> s2;
s2.insert(10);
s2.insert(60);
s2.insert(30);
s2.insert(60);
s2.insert(90);
s2.insert(10);
print(s2);
int num1 = s1.count(30);
cout << "num1=" << num1 << endl;
int num2 = s2.count(10);
//即使插入了两个元素10,因为集合元素的互异性,count统计之后依旧只有一个
cout << "num2=" << num2 << endl;
运行结果:
接下来讲讲pair对组的概念
1.定义
一般我们这里使用第一个就可以了
pair<int, double> p1; //使用默认构造函数
pair<int, double> p2(1, 2.4); //用给定值初始化
pair<int, double> p3(p2); //拷贝构造函数
2.访问
访问两个元素(通过first和second):
pair<int, double> p1; //使用默认构造函数
p1.first = 1;
p1.second = 2.5;
cout << p1.first << ' ' << p1.second << endl;
3.赋值
1)利用make_pair
pair<int, double> p1;
p1 = make_pair(1, 1.2);
2)变量间赋值:
pair<int, double> p1(1, 1.2);
pair<int, double> p2 = p1;
4.应用
像这里就可以利用first和second来访问对组中的两个元素
pair<string, int> p("Tom", 20);
cout << "姓名:" << p.first << "\t年龄:" << p.second << endl;
pair<string, int> p2 = make_pair("Jerry", 25);
cout << "姓名:" << p2.first << "\t年龄:" << p2.second << endl;
不仅如此,对组还可以放入queue(队列)然后配合BFS(广度优先搜索)来解题,感兴趣的小伙伴可以去了解一下;
例:
这里是拿pair充当结构体保存坐标(x,y)的值
queue<pair<int,int> >q;
q.push(make_pair(x,y));
然后这里再说一下仿函数的用法和实例
仿函数,又叫做函数对象或者智能函数,他相当于一个类一样,你需要重载operator()运算符,因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符.
仿函数一般有两种使用方法:
(1)一个办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数。上面的实例就是该做法;
(2)将该“操作”设计为一个仿函数(就语言层面而言是个 class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。
可以看出第二种方法会更高效可靠一些,第一种方法的扩展性较差,这里给出一个简洁的例子:
#include <iostream>
#include <set>
#include "print.h"
using namespace std;
class MyCompare{
public:
//仿函数
bool operator()(int v1, int v2) const
{
return v1 > v2; //降序排列
}
};
int main()
{
set<int> s1;
s1.insert(10);
s1.insert(60);
s1.insert(30);
s1.insert(20);
s1.insert(90);
s1.insert(70);
print(s1); //默认升序
//降序显示
set<int, MyCompare> s2;
s2.insert(10);
s2.insert(20);
s2.insert(90);
s2.insert(70);
for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
运行结果:
从运行结果我们可以看出,对于s1容器的打印,虽然插入的数字的乱序的,但是在显示的时候就会自动进行升序排列。这里我写了一个仿函数,它实际上就相当于一个类,v1 > v2就相当于前一个数比后一个数字大,也就是降序排列
上述例子是一个内置数据类型,其实仿函数也可以写成自定义数据类型,比如定义一个Person类,有年龄和身高两个属性,通过仿函数传入对应的类对象,也可以进行升序或降序的排列,大概类似于这样,这里的const一定要加,否则会报错的,因为设置成常成员函数,数据成员就不会被修改:
class MyCompare {
public:
bool operator()(const Person &p1,const Person &p2) const
{
return p1.m_age > p2.m_age;
}
};
对于仿函数,其实它和回调函数挺像的,回调函数是通过函数指针来进行传参,然后实现的一系列操作,有兴趣的小伙伴可以深入了解一下:回调函数与仿函数
map/multimap(映射/多重映射)
1)基本概念和介绍
map的所有元素是pair对组,同时拥有键值(key)和实值(value),所有元素都会根据键值来自动排序,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,map的使用率还是挺高的,仅此于vector和list
2)头文件
#include <map>
注:同理,map/multimap一样,都是使用此头文件
3)常用API操作
**① **构造和赋值
可以看出,对于map容器,使用其insert()进行插入就需要使用pair对组来实现,不然是插入不进去了,要分别传入它的键值和实值,这里我不是按照键值的顺序插入,但是看运行结果,最后显示出来的还是会按照顺序排列
map<int, int> m; //键值对
//第一个:key(键值) 第二个:value(实值)
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(3, 30));
m.insert(pair<int, int>(2, 20)); //依旧会按照顺序排列
m.insert(pair<int, int>(4, 40));
m.insert(pair<int, int>(5, 30));
print(m);
//拷贝构造
map<int, int> m2(m);
print(m2);
//赋值
map<int, int> m3;
m3 = m2;
print(m3);
运行结果:
②大小和交换
从运行结果可以看出,即使我插入了相同键值对应的实值,但是由于键值的重复,因此80这个数字是放不进容器的
//大小
map<int, int> m;
//第一个:key(键值) 第二个:value(实值)
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(3, 30));
m.insert(pair<int, int>(2, 20)); //依旧会按照顺序排列
m.insert(pair<int, int>(4, 40));
m.insert(pair<int, int>(4, 80)); //无效
print(m);
if (m.empty())
{
cout << "map容器为空" << endl;
}
else
{
cout << "map容器不为空" << endl;
cout << "map容器的大小为:" << m.size() << endl;
}
运行结果:
这里是使用到了swap()函数,对两个map容器中的值进行交换
//交换
map<int, int> m1;
//第一个:key(键值) 第二个:value(实值)
m1.insert(pair<int, int>(1, 10));
m1.insert(pair<int, int>(2, 20));
m1.insert(pair<int, int>(3, 30));
map<int, int> m2;
//第一个:key(键值) 第二个:value(实值)
m2.insert(pair<int, int>(4, 100));
m2.insert(pair<int, int>(5, 200));
m2.insert(pair<int, int>(6, 300));
cout << "交换前" << endl;
print(m1);
print(m2);
m1.swap(m2);
cout << "交换后" << endl;
cout << "---------------------------" << endl;
print(m1);
print(m2);
运行结果:
③查找和统计
对于find()查找函数,在map中可以利用键值来查找相应的实值,比如这里find(3),显示的便是对应的实值30;而对于count统计也是同理,count(3)的意思是统计键值为3的数有多少个,但是对于map容器,一定是0或者1。因为对于重复的键值是插不进去的,可对于mutilmap,利用count()函数去统计的话就可能是>1的数量
map<int, int> m;
m.insert(make_pair(1, 10));
m.insert(make_pair(2, 20));
m.insert(make_pair(3, 30));
m.insert(make_pair(3, 40));
print(m);
map<int, int>::iterator pos = m.find(3);
if (pos!=m.end())
{
cout << "找到到元素了,key=" << pos->first << " value=" << pos->second << endl;
}
else
{
cout << "没有找到改元素" << endl;
}
int num = m.count(3);
cout << "num=" << num << endl;
//map不允许插入重复元素,对于count统计而言,要么为0,要么为1
//mutilmap的count统计可能>1
运行结果:
④插入和删除
对于插入insert(),有着四种方式可以选择,一般我们记住前两种就可以了,第三种需要用迭代器来访问,第四种的话不太建议,因为如果按照这样方式插入,编译器会按照你之前没有的那个数创建一个新的对组出来,就像运行结果一样,我没有插入键值key为5的实值,但是默认显示的是0,这里要注意,一般这种方法可以通过key来访问value,这是可以的,也比较方便。
对于删除erase(),也是可以通过它的key值来删除对应的value值,这在实际的开发中还是会起到一定的作用,不需要一个个去查找
map<int, int> m;
//插入
//①
m.insert(pair<int, int>(1, 10));
//②
m.insert(make_pair(2, 20));
//③
m.insert(map<int,int>::value_type(3,30));
//④
m[4] = 40;
//[]不建议插入,可以用key来访问value
cout << m[5] << endl;
print(m);
//删除
m.erase(m.begin());
print(m);
m.erase(3); //根据键值key来删除
print(m);
m.erase(m.begin(), m.end());
print(m);
m.clear();
print(m);
运行结果:
stack(堆栈)
1)基本概念和介绍
stack为堆栈,上文提到过,其内部元素都是需要先进后出(FILO)的,也就是说只有栈顶的元素top才可以被访问到
2)头文件
#include <stack>
3)内部结构
3)常用API操作
对于堆栈,它的API操作还是比较少的,基本上就是push()[入栈],pop()[出栈],这里不叫插入和删除,对于堆栈有专门的叫法,然后就是empty()判别容器是否为空,如果不为空则返回size()[栈的大小],和top()[栈顶元素];当栈中的全部元素都出栈后,栈的大小为空即size=0;
stack<int> s;
s.push(10);
s.push(20);
s.push(30);
s.push(40); //入栈
cout<<"栈的大小\t栈顶元素" << endl;
while (!s.empty())
{
cout << s.size() <<"\t\t" << s.top() << endl;
s.pop(); //出栈
}
cout << "出栈后的大小为:" << s.size() << endl;
}
运行结果:
3.3.2 queue(队列)
1)基本概念和介绍
queue为队列,它和stack堆栈的正好相反,栈是先进后出,而队列则是先进先出(FIFO)。看到这里是不是想起了我们前面学过的一个顺序性容器deque(双端队列),下面来区分一下他们之间的不同之处:
1、queue可以访问两端但是只能修改队头,而deque可以访问两端并且可以在队首和队尾删除和插入元素
2、deque可以从两端入队,但是queue只能从队尾入队,
3、对于弹出队内元素,deque拥有pop_front(删除队头元素)以及pop_back(删除队尾元素)
2)头文件
#include <queue>
3)内部结构
3)常用API操作
对于队列的API操作,也是和堆栈一样,并不是很多,这里我们举一个例子来说明一下几个常用函数
这里的
push()就是入队
pop()便是出队,
empty()判断容器是否为空,
size()是返回其大小,
front()是返回队首元素
back()是返回队尾元素
#include <iostream>
#include <queue>
#include <string>
using namespace std;
class Person {
public:
Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
void test()
{
queue<Person> q;
//构建数据
Person p1("唐僧", 100);
Person p2("孙悟空", 200);
Person p3("猪八戒", 300);
Person p4("沙僧", 400);
//入队
q.push(p1);
q.push(p2);
q.push(p3);
q.push(p4);
//访问队列元素
cout << "队列大小\t队头元素\t\t队尾元素" << endl;
while (!q.empty())
{
cout << q.size()<<"\t\t";
cout << q.front().m_name<<"\t";
cout<< q.front().m_age << "\t\t";
cout << q.back().m_name << "\t";
cout << q.back().m_age << endl;
//出队
q.pop();
}
cout << "均出队后的队列元素个数为:" << q.size();
}
int main(void) {
test();
return 0;
}
运行结果:
讲解:
本例中,首先是定义了一个Person类,里面存有姓名和年龄两个属性,接着是定义了四个对象进行入队,然后对队内的元素进行判断,如果不为空则一一出队。首先我们来看第一行,因为还未执行到q.pop()这一行,因此队内的元素是4,因为唐僧是第一个插入的,因此它为队首元素,而沙僧则是最后插入的,因而它为队尾元素。看到第二行,因为此时执行了出队操作,所以队首元素则是第二个插入的孙悟空,队尾依旧为沙僧,知道最后一行队列里只剩下沙僧一个元素,出队之后变为0;
pirority_queue(优先队列)
1)基本概念和介绍
所谓优先队列,就是我们可以自定义中数据的优先级, 让优先级高的排在队列前面,优先出队
2)头文件
#include <queue>
注:对于优先队列,它的头文件和队列是一样的
3)参数定义及简单介绍
priority_queue<Type, Container, Functional>
Type 就是数据类型;
Container 就是容器类型;
Functional 就是比较的方式;
这里是两种优先队列的方式:
对于最后的functional,这也是一个模板头文件,这里的greater是大的,也就是呈上升,less是少的,也就是呈下降,自然对应的就是升序队列和降序队列
//升序队列(小顶堆)- 优先输出最小的
priority_queue <int,vector<int>,greater<int> > q;
//降序队列(大顶堆)- 优先输出最大的[默认]
priority_queue <int,vector<int>,less<int> >q;
我们来看一下具体实例:
#include <iostream>
#include <queue>
using namespace std;
int main(void)
{
priority_queue<int> q;
q.push(9);
q.push(2);
q.push(7);
q.push(3);
q.push(-8);
q.push(1);
while (!q.empty())
{
cout << q.top() << " ";
q.pop();
}
cout<<endl;
return 0;
}
运行结果:
可以看出,默认就是大顶堆,优先输出最大的元素
然后我们改一下它的内部参数
priority_queue<int,vector<int>,greater<int>> q;
q.push(9);
q.push(2);
q.push(7);
q.push(3);
q.push(-8);
q.push(1);
运行结果:
从这里可以看出修改内部参数之后呈现出的就是一个小顶堆,也就是优先输出最小的元素
小结
可以看出优先队列,它的功能还是很强大的,在实现算法的时候,你可以替代快排[快速排序sort() ],因为快排虽然很快,但是稳定性不是很好,时间复杂度也是会到达O(nlogn),但这里的复杂度只有O(n),当然具体案例具体分析;
待更新
我有stl源码剖析,有机会要深入读一下。