07 STL(标准模板库)

01 vector(向量容器)

底层数据结构:是动态开辟的数组,每次以原来空间大小的2倍进行扩容。
vector扩充的过程:重新申请⼀块连续空间,将原有的数据拷⻉到新空间中,再释放原有空间,完成⼀次扩充。所以每次扩充是重新开辟空间,扩充后原有的迭代器就会失效。

实例化
vector<int> vec;
操作示例操作描述时间复杂度
增加:vec.push_back(20);末尾添加元素,导致容器扩容O(1)
vec.insert(it,20);it迭代器指向的位置添加一个元素20,导致容器扩容O(n)
删除:vec.pop_back();末尾删除元素O(1)
vec.erase(it);删除it迭代器指向的元素O(n)

查询操作:
vector提供了operator[](中括号运算符重载函数),用来随机访问。vector本身也是一个数组,有着随机访问的特点,可以用下标的随机访问,例如vec[5] 。此外还可以使用以下方法:

  1. iterator迭代器进行遍历;
  2. findfor_each(foreach是通过iterator来实现的)等方法。

注意
对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了。
频繁调⽤ push_back() 影响:
push_back() 向 vector 的尾部添加元素,很有可能引起整个对象存储空间重新分配更⼤的内存后,再将原数据拷⻉到新空间中,再释放原有内存,这个过程是耗时耗⼒的,频繁对 vector 调用push_back()会导致性能的下降。

在 C++11 之后, vector 容器中添加了新的⽅法: emplace_back()。它与 push_back()
⼀样的是,都是在容器末尾添加⼀个新的元素进去,不同的是 emplace_back() 在效率上相⽐
较于 push_back() 有了⼀定的提升。
emplace_back() 函数在原理上⽐ push_back() 有了⼀定的改进,包括在内存优化⽅⾯和
运⾏效率⽅⾯。内存优化主要体现在使⽤了就地构造(直接在容器内构造对象,不⽤拷⻉⼀个
复制品再使⽤)+强制类型转换的⽅法来实现,在运⾏效率⽅⾯,由于省去了拷⻉构造过程,
因此也有⼀定的提升。

常用方法介绍:
size():返回底层容器有效元素的个数。
empty():判断容器是否为空。
reserve(20):vector预留空间的;只给容器底层开辟指定大小的内存空间,并不会添加新的元素。
resize(20):容器扩容用的;不仅给容器底层开辟指定大小的内存空间,还会添加新的元素。
swap():两个容器进行元素交换。

int main() {
    vector<int> vec; // vector<string> vec;
    //vec.reserve(20); // 叫做给vector容器预留空间

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

    // 把vec容器中的所有偶数全部删除
    auto it2 = vec.begin();
    while (it2 != vec.end()) {
        if (*it2 % 2 == 0) {
            it2 = vec.erase(it2); // 更新迭代器
        }
        else {
            ++it2;
        }
    }

    // 通过迭代器遍历vector容器
    auto it1 = vec.begin();
    for (; it1 != vec.end(); ++it1) {
        cout << *it1 << " ";
    }
    cout << endl;

    // 给vector容器中所有的奇数前面都添加一个小于奇数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) {
        cout << *it1 << " ";
    }
    cout << endl;

    return 0;
}

02 deque(双端队列)和list(链表)

deque(双端队列容器)
底层数据结构:是动态开辟的二维数组,一维数组从2开始,以2倍的方式进行扩容,每次扩容后,原来第二维的数组,从新的第一维数组的下标oldsize/2开始存放,上下都预留相同的空行,方便支持deque的首尾元素添加。

实例化
deque<int> deq;
操作示例操作描述时间复杂度
增加:deq.push_back(20);从尾部添加元素O(1)
deq.push_front(20);从首部添加元素O(1)
deq.insert(it,20);it指向的位置添加元素O(n)
删除:deq.pop_back();从尾部删除元素O(1)
deq.pop_front();从首部删除元素O(1)
deq.erase(it);从it指向的位置删除元素O(n)

首位元素的添加:deq.insert(deq.begin(),20)O(n)
查询搜素:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)。

list(链表容器)
底层数据结构:双向的循环列表 pre data next

实例化
list<int> mylist;
操作示例操作描述时间复杂度
增加:mylist.push_back(20);从尾部添加元素O(1)
mylist.push_front(20);从首部添加元素O(1)
mylist.insert(it,20);it指向的位置添加元素O(1)
删除:mylist.pop_back();从尾部删除元素O(1)
mylist.pop_front();从首部删除元素O(1)
mylist.erase(it);从it指向的位置删除元素O(1)

首位元素的添加:mylist.insert(mylist.begin(),20),O(n)

  • 链表中进行insert的时候,先要进行一个query查询操作,对于链表来说查询操作较慢。

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

deque和list,比vector容器多出来的增加删除函数接口:
push_front和pop_front。

03 vecotr,list和deque对比

vector特点:动态数组,内存是连续的,2倍的方式进行扩容;
deque特点:动态开辟的二维数组空间,第二维是固定长度的数组空间,扩容的时候(第一维的数组进行2倍扩容)

面经问题:deque底层内存是否是连续的?不是
容器的纵向考察:容器掌握的深度
容器的横向考察:各个相似容器之间的对比

vector和deque之间的区别

  1. 底层数据结构不同。
  2. 前中后插入删除元素的时间复杂度:中间和末尾都是O(1) ,前端 deque是 O(1) ,vector是 O(n)。
  3. 对于内存的使用效率:vector需要的内存空间必须是连续的;deque可以分块进行数据存储,不需要内存空间必须是一片连续的。
  4. 在中间进行insert或者erase,vector的效率比deque好,是由于deque的第二维内存空间不是连续的,在deque进行元素移动要比vector慢。

vector和list之间的区别
底层数据结构:数组 双向循环链表
数组:增加删除O(n) 查询O(n) 随机访问O(1)
链表:(考虑搜素的时间)增加删除O(1) 查询O(n) 随机访问O(1)

04 容器适配器

标准容器 - 容器适配器 => 有一种设计模式叫做适配器模式
怎么理解适配器?

  1. 适配器底层没有自己的数据结构,它是另外一个容器的封装,它的方法全部由底层依赖的容器进行实现的;
  2. 没有实现自己的迭代器。
// 描述适配器
template<typename T,typename Container=deque<T>>
class Stack {
public:
	void push(const T& val) { con.push_back(val); }
	void pop() { con.pop_back(); }
	T top()const { return con.back(); }
private:
	Container con;
};

stack
stack是⼀种先进后出的数据结构,只有⼀个出⼝,stack 允许从最顶端新增元素,移除最顶端元
素,取得最顶端元素。

push入栈,pop出栈,top查看栈顶元素,empty判断栈空,size返回元素个数

queue
queue是⼀种先进先出的数据结构,有两个出⼝,允许从最底端加⼊元素,取得最顶端元素,从最底端新增元素,从最顶端移除元素。

push入队,pop出队,front查看队头元素,back查看队尾元素,empty判断队空,size返回元素个数

stack和queue都依赖deque, 为什么不依赖vector呢?

  1. vector的内存使用效率太低了!没有deque好。
  2. 对于queue需要支持尾部插入,头部删除,O(1); 如果queue依赖vector,其出队效率很低。
  3. vector需要大片的连续内存,而deque只需要分段的内存,当存储大量数据时,deque对于内存利用率更好。

priority_queue
底层是⼀个 vector,使⽤ heap(大根堆) 形成的算法,插⼊,获取 heap 中元素的算法,维护这个vector,以达到允许⽤户以任何次序将任何元素插⼊容器内,但取出时⼀定是从优先权最⾼
(数值最⾼)的元素开始取的⽬的。

push入队,pop出队,top查看队顶元素,empty判断队空,size返回元素个数

priority_queue底层数据结构默认是大根堆。
priority_queue依赖vector, 为什么依赖vector呢?
底层默认把数据组成一个大根堆结构 ,在一个内存连续的数组上构建一个大根堆或者小根堆。
因为堆中的每一个节点跟它左右子节点的关系是通过下标计算的,通过下标的值访问相应元素的值。


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;
	cout << "------------------------------------------" << endl;

	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;
	cout << "------------------------------------------" << endl;


	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();
	}

	return 0;
}

05 无序适配器

无序关联容器 => 链式哈希表,增删查O(1)
set:集合 key
map:映射表 [key,value]
unordered_set 单重集合
unordered_multiset 多重集合
unordered_map 单重映射表
unordered_multimap 多重映射表

#include <iostream>
// 使用有序关联容器包含的头文件
#include <set> // set multiset
#include <map> // map multimap
// 使用无序关联容器包含的头文件
#include <unordered_set>
#include <unordered_map>

#include <string>
using namespace std;
/*
关联容器:
1.各个容器底层的数据结构  O(1)  O(log2n)
2.常用增删查方法
增加:insert(val)
遍历:iterator自己搜索,调用find成员方法
删除:erase(key) erase(it)
*/

int main()
{
	// 处理海量数据查重复;去重复的时候

	/*
	map中存的都是一个一个的pair对象
	[key,value]
	struct pair
	{
		first; => key
		second; => value
	}

	map的operator[]
	1.查询
	2.如果key不存在,它会插入一对数据[key,string()]
	V& operator[](const K &key)
	{
		insert({key,V()});
	}
	*/
	unordered_map<int, string> map1;
	// 可选的插入方式
	map1.insert(make_pair(1000, "张三"));
	map1.insert({ 1010,"李四" }); // map表增加元素
	map1.insert({ 1020,"王五" });
	map1.insert({ 1030,"王凯" });

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

	/*
	map1.erase(1020); // {1020,"王五"}删除了
	// map1[2000]; key:2000 vaule:""
	map1[2000] = "刘硕"; //相当于map1.insert({2000,"刘硕"});
	map1[1000] = "张三2";
	cout << map1.size() << endl;
	// map operator[](key) => value  查询
	cout << map1[1000] << endl;
	*/
#if 0
	unordered_set<int> set1; // 不会存储key值重复的元素
	// unordered_multiset<int> set1; 多重集合允许重复的元素
	for (int i = 0; i < 50; ++i)
	{
		set1.insert(rand() % 20 + 1); // 由哈希表的函数决定插入函数位置
		// vector/deque/list(线性表要指向位置,需要迭代器)  insert(it(迭代器),val(值)) 
	}

	// cout << set1.size() << endl;
	// cout << set1.count(15) << endl;

	auto it1 = set1.begin();
	for (; it1 != set1.end(); ++it1)
	{
		cout << *it1 << " ";
	}
	cout << endl;

	set1.erase(20); // 按key值删除元素
	for (it1 = set1.begin(); it1 != set1.end(); )
	{
		if (*it1 == 30)
		{
			it1 = set1.erase(it1); // 调用erase,it1迭代器就失效了
		}
		else
		{
			++it1;
		}
	}
	it1 = set1.find(20);
	if (it1 != set1.end())
	{
		set1.erase(it1);
	}

	for (int v : set1)
	{
		cout << v << " ";
	}
	cout << endl;
#endif

	return 0;
}

06 有序关联容器

#include <iostream>
// 使用有序关联容器包含的头文件  红黑树
#include <set> // set multiset
#include <map> // map multimap

#include <string>
using namespace std;

class Student
{
public:
	Student(int id=0,string name="")
		:_id(id),_name(name){}
	
	/*
	map表用k排序,因k是整数自己会排序,所以无序
	bool operator<(const Student& stu)const
	{
		return _id < stu._id;
	}
	*/
private:
	int _id;
	string _name;
	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()
{
	map<int, Student> stuMap;
	stuMap.insert({ 1000,Student(1000,"张雯") });
	stuMap.insert({ 1020,Student(1020,"李广") });
	stuMap.insert({ 1030,Student(1030,"高阳") });

	//stnMap.erase(it) stuMap.erase(1020)  stuMap[2000] [2000,V()]
	// cout << stuMap[1020] << endl;    stuMap.find(key);

	auto it = stuMap.begin();
	for (; it != stuMap.end(); ++it)
	{
		cout << "key:" << it->first << " value:" << it->second << endl;
	}
	cout << endl;

	return 0;
}
int main()
{
	set<Student> set1;
	
	set1.insert(Student(1000, "张雯"));
	set1.insert(Student(1020, "李广"));
	for (auto it1 = set1.begin(); it1 != set1.end(); ++it1)
	{
		cout << *it1 << endl;
	}
	/*
	set<int> set1;
	for (int i = 0; i < 20; ++i)
	{
		set1.insert(rand() % 20 + 1);
	}

	for (int v : set1)
	{
		cout << v << " ";
	}
	cout << endl;
	*/

	return 0;
}

07 迭代器

iterator(迭代器)模式⼜称 Cursor(游标)模式,⽤于提供⼀种⽅法顺序访问⼀个聚合对象中各个元素, ⽽⼜不需暴露该对象的内部表示。或者这样说:iterator模式是运⽤于聚合对象的⼀种模式,通过运⽤该模式,使得我们可以在不知道对象内部表示的情况下,按照⼀定顺序(由iterator提供的⽅法)访问聚合对象中的各个元素。

iterator:普通的正向迭代器
const_iterator:常量的正向迭代器 只能读,而不能写了
reverse_iterator:普通的反向迭代器
const_reverse_iterator:常量的反向迭代器

int main()
{
	vector<int> vec;
	for (int i = 0; i < 20; ++i)
	{
		vec.push_back(rand() % 100 + 1);
	}

	//auto it1 = vec.begin(); // it1的类型是vector<int>::iterator
	vector<int>::const_iterator it1 = vec.begin();
	/*
	const_iterator  <= iterator能赋值的原因;
	class const_iterator
	{
	public:
		const T& operator*(){return *_ptr;}
	class iterator:public const_iterator
	{
		T& operator*(){return *_ptr;}
	}
	派生类的关系
	*/
	for (; it1 != vec.end(); ++it1)
	{
		cout << *it1 << " ";
	
	}
	cout << endl;

	// rbegin():返回的是最后一个元素的反向迭代器表示
	// rend():返回的是首元素前驱位置的迭代器的表示
	// vector<int>::reverse_iterator
	vector<int>::const_reverse_iterator rit = vec.rbegin();
	for (; rit != vec.rend(); ++rit)
	{
		cout << *rit << " ";
	}
	cout << endl;

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

08 函数对象

函数对象 => C语言里面的函数指针
把有operator()小括号运算符重载函数的对象,称作函数对象或者仿函数

// 使用C的函数指针解决方案
template<typename T>
bool mygreater(T a, T b)
{
	return a > b;
}
template<typename T>
bool myless(T a, T b)
{
	return a < b;
}
  1. 通过函数对象调用operator(),可以省略函数的调用开销,比通过函数指针调用函数(不能够inline内联调用)效率高
  2. 因为函数对象是用类生成的,所以可以添加相关的成员变量,用来记录函数对象
    使用时更多的信息
//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;
	}
};

// compare是C++的库函数模板
template<typename T,typename Compare>
bool compare(T a, T b,Compare comp)
{
	// 通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数调用开销
	return comp(a, b);	// operator(a,b)
}

int main()
{
	cout << compare(10, 20, mygreater<int>) << endl;
	cout << compare(10, 20, myless<int>) << endl;


	return 0;
}
#endif


int main()
{
	// 举例1
	priority_queue<int> que1; // vector
	for (int i = 0; i < 10; ++i)
	{
		que1.push(rand() % 100);
	}
	while (!que1.empty())
	{
		cout << que1.top() << " ";
		que1.pop();
	}
	cout << endl;

	using MinHeap = priority_queue<int,vector<int>, greater<int>>;
	MinHeap que2; // vector
	for (int i = 0; i < 10; ++i)
	{
		que2.push(rand() % 100);
	}
	while (!que2.empty())
	{
		cout << que2.top() << " ";
		que2.pop();
	}
	cout << endl;

	// 举例2
	set<int, greater<int>> set1;
	for (int i = 0; i < 10; ++i)
	{
		set1.insert(rand() % 100);
	}
	for (int v : set1)
	{
		cout << v << " ";
	}
	cout << endl;

	return 0;
}

09 泛型算法和绑定器

泛型算法 = template + 迭代器 + 函数对象
特点一:泛型算法的参数接受的都是迭代器
特点二:泛型算法的参数还可以接受函数对象(C函数指针)
sort,find,find_if,binary_search,for_each

绑定器: + 二元函数对象 =》 一元函数对象
bind1st:把二元函数对象的operator()(a,b)的第一个形参绑定起来
bind2nd:把二元函数对象的operator()(a,b)的第二个形参绑定起来

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

int main()
{
	int arr[] = { 12,4,78,9,21,43,56,52,42,31 };
	vector<int> vec(arr, arr + sizeof(arr) / sizeof(arr[0]));

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

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

	// 有序容器进行二分查找
	if (binary_search(vec.begin(), vec.end(), 21))
	{
		cout << "binary_search 21存在" << endl;
	}

	auto it1 = find(vec.begin(), vec.end(), 21);
	{
		cout << "find21存在" << endl;
	}

	// 传入函数对象greater,改变容器元素排序时的比较方式
	sort(vec.begin(), vec.end(), greater<int>());
	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	// 78 56 52 43 42 31 21 12 9 4
	// 把48按序插入到vector容器当中,找第一个小于48的数字
	// find_if需要的是一个一元函数对象
	// greater a > b    less a < b
	auto it2 = find_if(vec.begin(), vec.end(),
		//bind1st(greater<int>(),48));
		//bind2nd(less<int>(), 48);
		[](int val)->bool {return val < 48; });
	vec.insert(it2, 48);
	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	// for_each可以遍历容器的所有元素,可以自行添加合适的函数对象对容器
	// 的元素进行过滤
	for_each(vec.begin(), vec.end(),
		[](int val)->void
		{
			if (val % 2 == 0)
			{
				cout << val <<" ";
			}
		});
	cout << endl;

	return 0;
}
  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值