C++ STL 重要组件

C++ STL 标准模板库

既然叫标准模板库了,那么下面这些组件自然是通过模板来实现的

一.标准容器(C++11以后还提供了array和forward_listSTL容器总结
1.顺序容器
vector
deque
list

​ 2.容器适配器

​ stack
​ queue
​ priority queue

​ 3.关联容器
​ 3.1无序关联容器(底层是链式哈希表,增删查的时间复杂度可以达到O(1))
​ unordered_set
​ unordered_multiset
​ unordered_map
​ unordered_multimap

​ 3.2有序关联容器(底层是红黑树, 增删查时间复杂度可以达到O(log2n), 2是底数)
​ set
​ multiset
​ map
​ multimap

二.近容器
数组, string, bitset(位容器)

三.迭代器
iterator和const_iterator
reverse_iterator和const_reverse_iterator

四.函数对象
greater,less

五.泛型算法
sort, find, find_if, binary_search, for_each


vector容器(向量容器)

vector:向量容器
底层数据结构:动态开辟的数组,每次以原来空间大小的2倍进行扩容

vector vec;
增加:
vec.push_back(20); 末尾添加元素 导致容器扩容
vec.insert(it, 20); 迭代器指向的位置添加一个元素 O(n) 导致容器扩容

​ 删除:
​ vec.pop_back(); 末尾删除元素O(1)
​ vec.erase(it); 删除it迭代器指向的元素 O(n)

​ 查询:
​ operator[] 下标的随机访问
​ iterator 迭代器进行遍历
​ find
​ C++11提供的语法糖:foreach => 通过iterator来实现

注意:
对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了。

其他常用方法介绍:
size() 容器实际大小(存放元素个数)
empty()
reserve(20) vector预留空间
resize(20) 重置大小–>容器扩容用的
swap 两个容器进行元素交换 ---- 多了解一下

emplace ---- 也要清楚

  • 基本使用
int main() 
{

	vector<int> vec;
	for (int i = 0; i < 20; ++i) 
	{
		vec.push_back(rand() % 100 + 1);
	}
	// 通过vector的operator[]运算符重载函数遍历vector容器
	int size = vec.size();
	for (int i=0;i<size;i++) 
	{
		cout << vec[i] << " ";
	}
	cout << endl;

	
	// 把vecx容器中所有的偶数全部删除
	auto it2 = vec.begin();
	for (; it2 != vec.end(); )
	{
		if (*it2 % 2 == 0)
		{

			it2 = vec.erase(it2);
			// 删除后,后面的元素都往前挪了,所以需要在当前位置继续进行判断
		}
		else
		{
			++it2;
		}
	}
	
	auto it1 = vec.begin();
	for (; it1 != vec.end(); ++it1) // ++it执行效率快,直接返回引用
	{
		cout << *it1 << " ";
	}
	cout << endl;



	// 给vec容器中所有的奇数前面都添加一个小于奇数小于1的偶数
	for (it1 = vec.begin(); it1 != vec.end(); ++it1) 
	{
		if (*it1 % 2 != 0) 
		{
			it1 = vec.insert(it1, *it1-1);
            // 插入后,原来的元素往后挪了,所以要再进行一次++
			++it1;
		}
	}

	for (it1 = vec.begin(); it1 != vec.end(); ++it1) // ++it执行效率快,直接返回引用
	{
		cout << *it1 << " ";
	}
	cout << endl;

	return 0;
}
  • 常用方法
int main() 
{
	vector<int> vec; 
	vec.reserve(20); // 给vector容器底层开辟指定大小的内存空间,并不会添加元素,作用是为了避免频繁扩容
	// resize不仅给容器底层开辟指定大小的内存空间,还会添加新的元素,这段代码中添加的元素值为0
	// vec.resize(20); 

	cout << vec.empty() << endl;// 1  判空
	cout << vec.size() << endl;// 0	  容器中有效元素个数

	for (int i=0;i<20;++i) 
	{
		vec.push_back(rand()%100+1);
	}
	cout << vec.empty() << endl;
	cout << vec.size() << endl;
}

在大致知道容器要处理的数据量的时候,可以使用reserve方法来预留空间(开辟指定大小的内存空间,并不会添加元素),从而避免插入元素时出现频繁地扩容操作;

注意reserve和resize方法区别


deque容器和list容器

deque容器

双端队列(有两个队头和两个队尾,前后都支持元素添加),底层数据结构是一个动态开辟的二维数组

  • 一维数组从2开始,以2倍的方式进行扩容;
  • 每次扩容后,原来第二维的数组,从新的第一维数组的下标olsize/2开始存放,上下都预留相同的空行,以方便支持deque的首尾元素添加

deque deq;
增加:
deq.push_back(20); 从末尾元素添加 O(1)
deq.push_front(20); 从首部元素添加 O(1)
deq.insert(it, 20); it指向的位置添加元素 O(n)

删除:
deq.pop_back(20); 从末尾元素删除 O(1)
deq.pop_front(20); 从首部元素删除 O(1)
deq.erase(it); 从it指向的位置删除元素 O(n)

查询搜索:
使用迭代器iterator进行遍历(连续的insert和erase一定要考虑迭代器失效的问题,需要进行迭代器的更新)

vector从首部添加元素的时间复杂度为O(n),当实际应用场景中涉及大量的首部和尾部插入时,可以考虑使用deque

在这里插入图片描述

list容器

链表容器,底层的数据结构是双向循环链表(每个节点具有数据域,以及指向下一个节点的next域和指向前一个节点的pre域)

在这里插入图片描述

list mylist;

增加:
mylist.push_back(20); // 从末尾元素添加 O(1)
mylist.push_front(20); // 从首部元素添加 O(1)
mylist.insert(it, 20); // it指向的位置添加元素 O(n) ,不涉及元素的挪动,插入本身的操作是快的O(1),但是插入时要执行查询操作,链表查询操作的效率是比较低的(从头节点向后遍历)

删除:
mylist.pop_back(20); // 从末尾元素删除 O(1)
mylist.pop_front(20); // 从首部元素删除 O(1)
mylist.erase(it); // 从it指向的位置删除元素 ,只从erase本身来讲,,不涉及元素的挪动,时间复杂度为O(1),但是删除时要执行的查询操作效率比较低

查询搜索:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)

deque,list 和 vector容器比起来多出了一组增加删除函数接口:
push_front()
pop_front()


顺序容器vector,deque,list之间的区别

  • vector的特点:

    • 动态数组,内存是连续的,以2倍的方式进行扩容

      当我们默认定义一个vector容器时,比如vector vec, 它并没有去开辟空间,当我们通过push_back或者insert等方法往vector中添加数据时候,它才以0–1----2—4----8这样的形式去扩容数组,这样的效率并不高(用老内存的对象在新的内存上去拷贝构造新的对象,然后释放老内存),

      我们可以使用reserve函数去为vector预留空间,但是并没有给容器添加元素!!

  • deque特点:

    • 动态开辟的二维数组空间,第二维是固定长度的数组空间(是独立new出来的),扩容的时候(对第一维的数组进行2倍扩容,然后把原来的二维的数组,去放到新的一维数组的中间(oldsize/2))
    • deque底层内存并不是连续的,每一个第二维的数组是连续的(或者说是分段连续)
  • vector和deque之间的区别

    • 底层数据结构不同

      • vector底层是动态开辟的数组
      • deque底层是动态开辟的二维数组
    • 前中后插入/删除元素的时间复杂度:

      • 末尾插入/删除都是O(1) ,
      • 在前面插入和删除,deque是O(1),vector是O(n)
      • 中间插入和删除是O(n)
    • 对于内存的使用效率:

      • vector需要的内存空间必须是连续的
      • deque可以分块进行存储,不需要内存空间必须连续,内存的使用效率比较高
    • 中间执行insert和erase时,vector和deque的效率谁会好一点?

      虽然都是O(n),但是移动的方便性还是存在区别的

      vector的效率会好一点,因为vector底层是动态数组, 内存是连续的,所以元素挪动比较方便;deque第二维的数组内存不是连续的,发生insert或者erase时,元素移动的过程需要借助第一维的数组,移动过程会比vector麻烦

在这里插入图片描述

  • vector和list之间的区别

    • 底层数据结构不同
      • vector底层数据结构是动态开辟的数组
      • list底层数据结构是双向循环链表

    所以vector和list的区别,主要就是数组和链表的区别

    vector—数组
    数组:
    增加删除—操作本事是O(n),查询也需要O(n)
    查询—O(n)
    随机访问—O(1) 由cpu下发地址,所以随机访问任何一个元素的速度是一样的

    list----链表
    链表:
    增加删除(要考虑搜索时间)—O(1) (如果找到要增加或者删除的位置,那么不需要移动其他元素,所以是O(1),但实际我们还要考虑搜索时间)
    查询—O(n)
    没有随机访问[]


容器适配器(stack,queue,priority_queue)

  1. 适配器底层没有自己的数据结构,它是另外一个容器的封装,它的方法全部是由底层依赖的容器进行实现的
  2. 没有实现自己的迭代器
template<typename T, typename Container=deque<T>>
class Stack 
{
public:
    ....
	void push(const T& val) { con.push_vack(val); }
	void pop() { con.pop_back(); }
	T top()const { return con.top(); }
    ....
private:
	Container con;
};
  • 使用C++STL提供的容器适配器
#include <iostream>
#include <deque>
#include <stack>// stack
#include <queue>// queue和priority_queue
using namespace std;
  • stack的使用
int main() 
{
	stack<int> s1;
	for(int i=0;i<20;++i)
	{
		s1.push(rand() % 100 + 1);
	}
	cout << s1.size() << endl;

	while (!s1.empty()) 
	{
		cout << s1.top() << " ";
		s1.pop();
	}
	cout << endl;
	return 0;
}
  • queue的使用
int main() 
{
	queue<int> que;
	for (int i = 0; i < 20; ++i)
	{
		que.push(rand() % 100 + 1);
	}
	cout << que.size() << endl;
	while (!que.empty())
	{
		cout << que.front() << " ";
		que.pop();
	}
	cout << endl;
	return 0;
}
  • priority_queue的使用
int main()
{
	priority_queue<int> pque;
	for (int i = 0; i < 20; ++i)
	{
		pque.push(rand() % 100 + 1);
	}
	cout << pque.size() << endl;
	while (!pque.empty())
	{
		cout << pque.top() << " ";
		pque.pop();
	}
	cout << endl;
	return 0;
}

stack (依赖deque): push入栈 pop出栈 top查看栈顶元素 empty判栈空 size返回元素个数

queue(依赖deque): push入队 pop出队 front查看队头元素 back查看队尾元素 empty判队空 size返回元素个数

priority_queue(依赖vector ): push入队 pop出队 top查看队顶元素 empty判队空 size返回元素个数

  • 为什么stack和queue依赖deque,而不依赖vector?

    • vector的初始内存效率太低(需要频繁扩容),而deque初始一个二维数组,第二维的数组的初始内存大小为4096/sizeof(T);

    • 对于queue来说,需要支持尾部的插入和头部的删除,保证时间复杂度都是O(1),如果queue依赖vector,那么其出队效率就很低了;

    • vector需要大量的连续内存块,而deque只需要分块的内存,当存储大量的数据时,显然deque对于内存的利用率会更好一些。

  • 为什么priority_queue底层依赖vector ?

    优先级队列默认会将数据组织成一个大根堆,而大根堆的构建一般都是在内存连续的数组上利用元素下标的关系来构建的,而vector底层是动态开辟的内存连续的数组


关联容器

无序关联容器

无序关联容器(底层是链式哈希表,增删查的时间复杂度可以达到O(1))

  • unordered_set
  • unordered_multiset
  • unordered_map
  • unordered_multimap
// 无序关联容器包含的头文件
#include <unordered_set>
#include <unordered_map>

常用的增删查方法

  1. 增加: insert(val)

  2. 删除: erase(key), erase(it)

  3. 遍历: iterator自己搜索,调用find成员方法


unordered_set

不允许元素值重复

int main() 
{
	unordered_set<int> set1; // 不存放重复的元素(key)
	for (int i=0;i<50;i++) 
	{
		set1.insert(rand() % 20 + 1);  // 参数中没有插入位置,无序关联容器底层是链式哈希表,插入位置是由哈希函数计算得到的
	}

	// 利用迭代器来遍历容器元素(迭代器能透明遍历容器元素)
	auto it = set1.begin();
	for (;it!=set1.end();++it) 
	{
		cout << *it << " ";
	}
	cout << endl;

	set1.erase(20); // 按照key值删除

	for (it = set1.begin(); it != set1.end();) 
	{
		if (*it == 30) 
		{
			it = set1.erase(it); // 传入迭代器进行删除,注意更新迭代器
		}
		else 
		{
			++it;
		}
	}

	// 利用迭代器删除指定容器元素
	it = set1.find(20); // 在容器找key为20的元素存不存在,存在返回迭代器,不存在返回末尾迭代器
	if (it != set1.end()) 
	{
		set1.erase(it);
	}

	// for each方法底层通过迭代器实现
	for (int v:set1) 
	{
		cout << v << " ";
	}
	cout << endl;
	return 0;
}
unordered_map

单重映射表,不允许key重复

插入:insert(pair对象)

删除:erase(key)

[]运算符重载:

  • 按key值查询,如map[1000],1000是key
  • 如果key不存在,它会插入一对数据{key, string()},并返回插入的数据(pair对象)的引用
  • 通过[]返回的引用可以修改key对应的value

查找:auto it = map1.find(key)

  • 返回值指向pair对象(it->first, it->second分别为key值和value值)
  • 没找到返回map1.end()

应用场景:无序关联容器底层使用的是哈希表,增删查的时间复杂度为O(1),常见的一个应用是海里数据的查重和去重

存放的是一个键值对,需要将key和value打包成一个pair类型,才能够插入到unordered_map中

// pair类型的定义大致如下:
strut pair
{
public:
	first;==>key
    second;==>value
};
// 将键值对打包成pair对象,然后插入unordered_map
map.insert(make_pair(1000, "张三"));

// C++11支持:对于struct定义,访问限定为public的类,对象的构造可以使用{}来表示
map.insert({1000, "张三"});
  • 基本使用(unordered_multimap和unordered_map所提供的方法是一样的)
int main() 
{
 
	// unordered_multimap<key数据类型, value数据类型> map1; 多重映射表
	unordered_map<int, string> map1; // 单重映射表
	map1.insert(make_pair(1000, "张三")); // make_pair(key, value) --> 将键值对打包
	// 另一种插入方式
	map1.insert({ 1000, "李四" });
	map1.insert({ 1020, "王五" });
	map1.insert({ 1000, "王凯" }); // 单重映射表是不允许key值重复的,但是多重映射表是允许的

	map1.erase(1020); // 通过key值进行删除

	/*
	map的operator[]
		1.查询
		2.如果key不存在,它会插入一对数据[key, string()]\  string()零构造
			
	*/

	// [](中括号)运算符重载函数来查询元素
	cout << map1[1000] << endl;
	map1[2000] = "刘硕";  // 相当于 map1.insert({ 2000, "刘硕" });
	map1[1000] = "张硕";  // 修改1000key所对应的value


	auto it = map1.find(1020);
	if (it != map1.end()) 
	{
		// 注意it => pair对象
		cout << "key: " << it->first << " value: " << it->second << endl;
	}
	
}

海量数据查重复 —>使用哈希表,数据的规模和增删查0(1)没有关系

int main() 
{
	// 海量数据查重复,去重复--->使用哈希表,数据的大小和增删查0(1)没有关系

	const int ARR_LEN = 100000;
	int arr[ARR_LEN] = { 0 };
	for (int i=0;i<ARR_LEN;i++) 
	{
		arr[i] = rand() % 10000 + 1;
	}
#if 0
	// 从上面10万个整数中,统计哪些数字重复了,并且统计数字重复的次数
	unordered_map<int, int> map1;
	for (int k:arr) 
	{
		/* 
		auto it = map1.find(k);
		if (it == map1.end()) // 不存在key值为k的键值对 
		{
			map1.insert({k, 1});
		}
		else 
		{
			it->second++;
		}
		*/
		map1[k]++; // 上面的代码和这行代码效果是一样的,注意中括号重载运算符函数的应用
	}
	
    // 常引用,不允许修改pair对象
    // 避免引用拷贝构造
	for (const pair<int, int> &p : map1) 
	{
		if (p.second > 1) 
		{
			cout << "key: " << p.first << " value: " << p.second << endl;
		}
	}
	// 也可以直接使用迭代器

海量数据去重复—>使用哈希表,数据的规模和增删查0(1)没有关系

int main() 
{
	const int ARR_LEN = 100000;
	int arr[ARR_LEN] = { 0 };
	for (int i=0;i<ARR_LEN;i++) 
	{
		arr[i] = rand() % 10000 + 1;
	}
    
    // 遍历arr中的数,并放入unordered_set
    unordered_set<int> set1;
	for (int v : arr) 
	{
		set1.insert(v);
	}
	
	for (int v : set1) 
	{
		cout << v << " ";
	}
	cout << endl;
    
    return 0;
}

有序的关联容器

有序关联容器(集合和映射表),底层数据结构是红黑树,增删查时间复杂度可以达到O(log2n), 2是底数

  • set
  • multiset
  • map
  • multimap

插入数据时,会进行比较排序(默认升序),删除数据后也会保证其余数据是有序存储的

//包含有序关联容器的头文件
#include <set>
#include <map>
集合set的使用
int main()
{
	set<int> set1; // 不重复存储key值
	for (int i = 0; i < 20; i++)
	{
		set1.insert(rand() % 20 + 1);
	}
	for (int v : set1) // 迭代器遍历-->对容器底层红黑树进行一个中序遍历的过程
	{
		cout << v << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

可以看到,在输出时,迭代器遍历的过程实际上是对容器底层的红黑树进行了一个中序遍历,所以输出的结果是有序的。

自定义Student类型

class Student 
{
public:
    // 由于形参都带了默认值,所以Student(),Student(0,"sss"), Student(0)都是调用的该构造函数
	Student(int id=0, string name="")
		:_id(id),_name(name)
	{}
	bool operator<(const Student& stu)const 
	{	/*
			如果对于对象成员变量只是进行只读操作,成员方法定义成常方法,const Student* this 
		*/
		return _id < stu._id;
	}
private:
	string _name;
	int _id;
	friend ostream& operator<<(ostream& out, const Student& stu);
};

// 输出运算符重载
ostream& operator<<(ostream& out, const Student& stu) 
{
	out << "id: " << stu._id << " name: " << stu._name << endl;
	return out;
}
int main() 
{
	// 类型参数是自定义的Student类型,Set存储的是Student对象
    set<Student> set1;
	// multiset<Student> set1; 多重集合,可以存储重复的元素

	// 存放对象时默认是按小于(<)进行比较排序,所以我们需要为自定义类提供默认的<运算符重载函数 
	set1.insert(Student(1000, "张三"));
	set1.insert(Student(1020, "李四"));

	for (auto it = set1.begin(); it != set1.end(); ++it) 
	{
		// it -> stu 
		cout << *it << endl;
	}
	return 0;
}

在这里插入图片描述

可以看到,我们定义了Student类中的<运算符重载函数,按照id进行比较,最后的输出结果是id值小的对象先输出

映射表map的使用

下面的Student还是上面我们自定义的类

int main() 
{
    // map<type(key).name(),type(value).name()>,排序是按照key来排的
	map<int, Student> map1;// 定义一个映射表时,需要Student类提供一个默认的构造函数
	map1.insert({1000, Student(1000, "张三")});
	map1.insert({1020, Student(1020, "李四")});
	map1.insert({1040, Student(1040, "王五")});

    // 删除
	// map1.erase(it), map1.erase(key)
    
    /*
	当key值不存在时,会插入一个键值对[key, Student()] 
	需要Student类提供一个默认构造函数; 
	*/
	cout << map1[1000] << endl; 
    
    auto it = map1.begin();
	for (; it != map1.end(); ++it) 
	{
		// -> <==>*it.first
        cout << "key: " << it->first << " " << "value: " << it->second << " ";
	}
	cout << endl;
	 
	return 0;
}

有序关联容器和无序关联容器所提供的接口是一样的,不同的是从前者拿数据时,数据有序存储,后者的数据是无序存储的,这也是由于两类关联容器底层的数据结构不一致所造成的


迭代器iterator

容器的迭代器(无论是顺序容器还是关联容器都支持了迭代器(正向迭代器 和 反向迭代器))

  • 普通的正向迭代器:iterator(既可以读容器的元素,也可以对容器的元素进行修改)
  • 常量迭代器:const_iterator
  • reverse_iterator: 普通的反向迭代器
  • const_reverse_iterator: 常量的反向迭代器

在迭代器设计的时候,const_iterator是基类,而iterator是派生类,所以基类类型的对象可以接收派生类类型对象

class const_iterator{}

class iterator:public const_iterator
{}
vector<int>::const_iterator it = vec.begin(); // const_iterator <==  iterator

对const_iterator类型的迭代器使用*解引用,返回的是容器底层存储元素的常引用,不允许修改


反向迭代器

  • vec.rbegin() 返回的是容器最后一个元素的反向迭代器表示
  • vec.rend() 返回的是首元素前驱位置的迭代器的表示(vec.end()返回的是最后一个元素的后继位置的迭代器表示),这样才可以保证能够访问到首元素
int main()
{
    vector<int> vec;
    vector<int>::reverse_iterator rit =  vec.rbegin();
	// auto rit = vec.rbegin(); 使用auto,让编译器自动推理it的数据类型
	// vector<int>::const_reverse_iterator rit =  vec.rbegin();

	for (; rit != vec.rend();++rit) 
	{
		cout << *rit << " ";
	}
	cout << endl;
    return 0;
}

函数对象

可以认为函数对象就类似于C语言中的函数指针

在C++中,把有operator()小括号符重载函数的对象,称作函数对象或者仿函数。

在这里插入图片描述

举个例子:我们现在希望通过定义一个函数模板,能够实现采用不同方式(> 或者<)的对元素进行比较

定义函数模板的版本的实现

template<typename T>
bool mygreater(T a, T b) 
{
    return a > b;
}

template<typename T>
bool myless(T a, T b) 
{
    return a > b;
}

/* 
// myless<T>是函数名,即函数指针,并不作类型名,这样写编译报错
template<typename T, typename Compare=myless<T>>
bool compare(T a, T b, Compare comp)
{
    return comp(a, b);
}
*/

template<typename T, typename Compare=myless<int>>
bool compare(T a, T b, Compare comp)
{
	return comp(a,b);
}
int main() 
{
    // 传入两个函数指针(函数名)mygreater<int>, myless<int>,进行模板的实参推演
	cout << compare(10, 20, mygreater<int>) << endl;
	cout << compare('x', 'y', myless<int>) << endl;
	return 0;
}

这种方法虽然可以实现我们的需求,但是有一个问题:由于我们需要传入函数指针,而在编译阶段通过函数指针调用对应的函数是无法进行内联展开的,效率低,因为有函数调用开销

以下是C++函数对象的版本的实现

之前类中定义的方法会被实现成为内联函数

template<typename T>
class mygreater 
{
public:
	bool operator()(T a, T b) 
	{
		return a > b;
	}
};

template<typename T>
class myless
{
public:
	bool operator()(T a, T b)
	{
		return a < b;
	}
};


template<typename T, typename Compare>
bool compare(T a, T b, Compare comp)
{
    // 这里调用的是函数对象的小括号运算符重载函数,编译过程中编译器对于到底调用的是哪个函数是非常明确的,所以可以进行内联展开,以节省函数的调用开销
	return comp(a,b);  // comp.operator()(/*&comp,*/ a,b) 
	// c函数指针, comp只是一个指针,在编译时期编译器不知道到底调用哪个函数,程序执行时通过指针访问对应的函数
	
}
int main() 
{
    // 传入两个对象mygreater<int>(), myless<int>(),进行模板的实参推演
	cout << compare(10, 20, mygreater<int>()) << endl;
	cout << compare('x', 'y', myless<int>()) << endl;
	return 0;
}

使用函数对象的好处:

  • 通过函数对象调用operator(),可以省略函数的调用开销,比通过函数指针调用函数(不能够inline内联调用)效率高;
  • 因为函数对象是由对应的类实例化来的,所以可以添加相关的成员变量,用来记录函数对象使用时需要的更多的信息

如果函数对象中()运算符重载函数的参数有两个(不包括this指针),那么这个函数对象叫做二元函数对象,有一个参数就叫做一元函数对象

函数对象的应用

  • 构造一个底层是小根堆的优先级队列
#include <iostream>
#include <queue>
using namespace std;

int main() 
{
    // C++中使用using 代替typedef
    using MinHeap = priority_queue<int, vector<int>, greater<int>>;
    MinHeap minheap;
    for (int i = 0; i < 20; ++i) 
    {
        minheap.push(rand() % 100 + 1);
    }

    // 数据从小到大有序输出
    while (!minheap.empty()) 
    {
        // 输出堆顶元素
        cout << minheap.top() << endl;
        minheap.pop();
    }
     
    return 0;
}

在这里插入图片描述

从该图可以看到,priority_queue容器适配器可以传入三个类型参数:

  • 第一个参数优先级队列存储数据的数据类型
  • 第二个参数是优先级队列(容器适配器)所依赖的容器,默认是vector
  • 第三个参数是C++STL提供的函数对象,优先级队列默认使用的是less<_Ty>,实现大根堆

在这里插入图片描述

我们要实现小根堆,就需要传入第三个类型参数:greater<_Ty>

  • 有序关联容器set(底层数据结构为红黑树),输出时,数据从小到大—>从大到小
int main() 
{
    set<int, greater<int>> set1;
    for (int i = 0; i < 20; ++i) 
    {
        set1.insert(rand() % 100 + 1);
    }

    for (int val : set1) 
    {
        cout << val << " ";
    }
    cout << endl;

    return 0;
}

在这里插入图片描述

有三个参数:

  • 第一个参数是键值的数据类型
  • 第二个参数是函数对象
  • 第三个参数是容器的空间配置器

泛型算法和绑定器

(参考资料:C++ Primer 第三版 泛型算法)

// 包含泛型算法的头文件
#include <algorithm>

泛型算法的特点:

  • 特点一:泛型算法的参数接收迭代器

  • 特点二:泛型算法的参数还可以接受函数对象(C函数指针)

sort, find, find_if, binary_search, for_each

泛型算法 = template + 迭代器 + 函数对象(模板实现,通过迭代器遍历所有容器的元素,并通过传入函数对象能够去更改算法中的某些默认的功能,常见的就是排序方式)

绑定器: 二元函数对象 + 绑定器 ==》 一元函数对象

  • bind1st: 把二元函数对象的operator()的第一个形参和固定值进行绑定
  • bind2nd: 把二元函数对象的operator()的第二个形参和固定值进行绑定
// 包含了函数对象和绑定器的头文件
#include <functional>  
// greater<int>  a>b
// bind1st将函数对象的第一个参数a和48做了绑定 
auto it2 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 48))

lambda表达式:

lambda表达式底层就是函数对象的实现

// []捕获外部遍历,(int val)是函数对象()运算符重载函数的形参列表, bool是其返回值类型, {...}是它的函数体
// 使用lambda表达式后就不用特意为了临时的使用,而定义一个函数对象,而且使用起来比绑定器更加灵活
[](int val)->bool{return val<48;}

看个例子,综合了泛型算法+绑定器+lambda表达式

#include <iostream>
#include <algorithm> // 包含了C++ STL里面的泛型算法
#include <functional> // 包含了函数对象和绑定器
#include <vector>
using namespace std;
 

int main() 
{
	int arr[] = { 12, 4, 78, 9, 21, 43, 56, 52, 42, 31 };
	// vector所提供的其中一个构造函数(构造函数重载)
    vector<int> vec(arr, arr+sizeof(arr)/sizeof(arr[0]));  // 把整形数组的元素放入vector

	for (int v:vec) 
	{
		cout << v << " ";
	}
	cout << endl;

	// sort泛型算法,默认从小到大排序
	sort(vec.begin(), vec.end());
	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	// binary_search泛型算法,有序容器中进行二分查找(效率比较高) O(log2n)
	if (binary_search(vec.begin(), vec.end(), 21)) 
	{
		cout << "binary_search: 21存在" << endl;
	}

	auto it = find(vec.begin(), vec.end(), 21); // 顺序查找
	if (it != vec.end()) 
	{
		cout << "find: 21存在" << endl;
	}
	
	// 传入函数对象,改变容器元素排序时的比较方式
	sort(vec.begin(), vec.end(), greater<int>());
	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	// find_if需要的是一个一元函数对象
	// greater a>b   less a<b 都是二元函数对象,可以使用绑定器去绑定二元函数对象的参数
 	auto it2 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 48)); // 找第一个小于48的元素
	vec.insert(it2,48); // 只执行一次insert,不用更新迭代器
    
	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	// for_each遍历容器的所有元素,可以自行添加合适的函数对象对容器的元素进行过滤
	for_each (vec.begin(), vec.end(),
		[](int val)->void   // lambda表达式(比绑定器更加灵活)
		{
			if (val % 2 == 0)
			{
				cout << val << " ";
			}
		});
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下酒番陪绅士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值