C++ STL

 简介

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源码剖析,有机会要深入读一下。

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值