深入学习STL标准模板库

C++ STL standard template libaray 标准模板库

一、标准容器

顺序容器

vector

底层数据结构:动态开辟的数组,每次以原来空间大小的2倍进行扩容(开辟两倍大小的新数组,拷贝构造原数组对象,析构原数组对象,回收原数组内存)

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)
查询:
operator[] 下标的随机访问 vec[5] O(1)
iterator 迭代器进行遍历
find,for_each
foreach => 底层通过iterator来实现的
常用方法介绍:
size()
empty()
reserve(20):vector预留空间的 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize(20):容器扩容用的 不仅给容器底层开辟指定大小的内存空间,还会会添加新的元素
swap:两容器进行元素交换

注意1:resize和reserver的区别
reserve预留空间 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize:容器扩容 不仅给容器底层开辟指定大小的内存空间,还会添加新的元素

	vector<int> ve;
	ve.reserve(20);//预留内存空间 
	cout<<ve.empty()<<endl;//输出 1 
	cout<<ve.size()<<endl;//输出 0
	
	ve.push_back(1); 
	ve.push_back(2); 
	ve.push_back(3); 
	for(int x:ve) cout<<x<<" ";//输出 1 2 3
	vector<int> ve;
	ve.resize(10);//开辟空间 添加元素
	cout<<ve.empty()<<endl;//输出 0 
	cout<<ve.size()<<endl;//输出 10 
	
	ve.push_back(1); 
	ve.push_back(2); 
	ve.push_back(3); 
	for(int x:ve) cout<<x<<" ";//输出 0 0 0 0 0 0 0 0 0 0 1 2 3

注意2:vector调用clear()或者size(0),会清除容器内的所有元素,但是不会释放内存,如果想释放内存需要调用shrink_to_fit()函数

	vector<int> ve{1,2,3,4,5,6,7,8,9,10};
	cout<<ve.size()<<endl;//输出10 
	cout<<ve.capacity()<<endl;//输出10
	
	ve. clear(); //ve.resize(0);
	cout<<ve.size()<<endl;//输出0 
	cout<<ve.capacity()<<endl;//输出10
	
	cout<<"ve[2]="<<ve[2]<<endl;//输出3 因为元素清空了 内存没有清空 
	
	ve.shrink_to_fit();
	cout<<ve.size()<<endl;//输出0 
	cout<<ve.capacity()<<endl;//输出0

注意3:对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了,比如下面代码经典错误

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> ve{1,2,3,4,5,6,7,8,9,10};
	//把容器中所有的偶数删除
	auto it = ve.begin();
	for(;it!=ve.end();it++) 
	{
		if(*it%2==0)
		{
			ve.erase(it);//错误迭代器已经失效了 
		}
	}
	for(int x:ve)
	{
		cout<<x<<" ";//输出为空
	}
    return 0;
}

正确写法:

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> ve{1,2,3,4,5,6,7,8,9,10};
	//把容器中所有的偶数删除
	auto it = ve.begin();
	while(it!=ve.end())
	{
		if(*it%2==0)
		{
			it = ve.erase(it);//删除之后返回迭代器位置 
		}else//迭代器指向的不是偶数才后移 
		{
			++it;
		}
	}
	
	for(int x:ve)
	{
		cout<<x<<" ";//输出1 3 5 7 9 
	}cout<<endl;
	
	//给vector容器中所有的奇数前面都添加一个小于奇数1的偶数 
	for(it=ve.begin();it!=ve.end();++it)
	{
		if(*it%2!=0)
		{
			it = ve.insert(it,*it-1);//插入之后返回新的迭代器位置 
			++it;//迭代器需要向后多移动一次保证不重复插入 
		}
	}
	for(int x:ve)
	{
		cout<<x<<" ";//输出0 1 2 3 4 5 6 7 8 9 
	}
    return 0;
}

deque

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

注意:deque底层每一个第二维本身是连续的,但是所有的二维不是连续的,是重新new出来的(分段连续)

底层原理:
一般编译器默认大小
#define MAP_SIZE 2 第一维度大小
#define QUE_SIZE 4096/sizeof(T) 第二维度大小 T为数据类型

1.初始的队头队尾指针在队列中间(双端队列方便两头都可以插入删除)
在这里插入图片描述
2.随着元素的插入队头队尾指针移动
在这里插入图片描述
3.当队列满的时候,开辟新的二维空间
在这里插入图片描述
4.当所有队列满的时候进行2倍扩容(二维会放在一维中间位置)
在这里插入图片描述
在这里插入图片描述
操作:
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)

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

list

底层数据结构:双向的循环列表

list mylist;
增加:
mylist.push_back(20); 从末尾添加元素 O(1) 可能扩容
mylist.push_front(20);从首部添加元素 O(1) 可能扩容
mylist.insert(it,20); it指向的位置添加元素 O(1) 可能扩容 往往需要先query查询操作
对于链表来说,查询操作效率就比较慢

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

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

vector deque list对比

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

vector和deque之间的区别?

1.底层数据结构:
2.前中后插入删除元素的时间复杂度:中间O(n),末尾都是O(1), 前插deque O(1) vector O(n)
3.对于内存的使用效率:vector需要的内存空间必须连续,deque可以分块进行数据存储,不需要内存必须连续
4.在中间进行insert或者erase,虽然时间复杂度都是O(n),但是vector都是连续空间比较方便,deque的第二维内存空间不是连续的,所以在deque中间进行元素的inset或者erase,指令肯定更多,造成元素移动的时候比vector要慢
deque和list比vector容器多出来的增加删除函数接口:push_front() pop_front()

vector和list之间的区别?
数组和链表的区别
数组:增加删除O(n) 查询O(n) 随机访问O(1)
链表:增加删除O(1) 查询(n)

容器适配器

怎么理解适配器?
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

先进后出
push():入栈
pop():出栈
top():查看栈顶元素
empty():判断栈空
size():返回元素个数

queue

先进先出
push():入队
pop():出队
front():查看队头元素
back():查看队尾元素
empty():判断队空
size():返回队列元素个数

priority_queue

底层数据结构:大根堆
push():入队
pop():出队
top():查看队顶元素
empty():判断队空
size():返回元素个数

面试问题:

stack和queue底层依赖deque,为什么不依赖vector?
1.vector的初始内存使用效率太低了!没有deque好(vector需要二倍扩容,deque初始二维就开辟了1024个数组空间)
2.对于queue来说,需要支持尾部插入,头部删除O(1),如果queue依赖vector,其出队效率很低
3.vector需要大片连续内存,而deque只需要分段的内存,当存储大量的数据时,显得deque对于内存的利用率更好一些

priority_queue底层依赖vector,为什么不依赖deque?
底层默认把数据组成一个大根堆结构,在一个内存连续的数组上构建一个大根堆和小根堆(父子节点需要通过下标计算)

关联容器

(1)无序关联容器

底层数据结构:链式哈希表 增删查O(1)

unordered_set

#include<unordered_set>
增加:insert(val)
遍历:iterator自己搜索,调用find成员方法
删除:erase(key) erase(it)
存在:count()

unordered_multiset

#include<unordered_set>
#include<unordered_set>
增加:insert(val)
遍历:iterator自己搜索,调用find成员方法
删除:erase(key) erase(it)
个数:count()

unordered_map

#include<unordered_map>

unordered_multimap

#include<unordered_map>

#include<iostream>
#include<unordered_map>
#include<string> 
using namespace std;
/*map存储的是pair对象 
class pair
{
	first;
	second;
}
*/
int main()
{
	unordered_map<int,string> map;
	
	//插入 
	map.insert(make_pair(1000,"张三"));
	map.insert({1010,"李四"});
	map[1020] = "王五";
	
	cout<<map.size()<<endl;
	
	
	map.erase(1020);//删除 
	
	//查询 方式1 
	cout<<map[1020]<<endl;
	//查询 方式2 
	auto it = map.find(1000);
	if(it!=map.end())
	{
		cout<<" key:"<<it->first<<" value:"<<it->second;
	}
	
	//遍历方式一 
	for(const pair<int,string> &p:map)
	{
		cout<<" key:"<<p.first<<" value:"<<p.second;
	} 
	
	//遍历方式二 
	it = map.begin();
	for(;it!=map.end();it++)
	{
		cout<<" key:"<<it->first<<" value:"<<it->second;
	} 
	return 0;
} 

注意:
1.map的operator[]不仅有查询功能,如果key不存在,它会插入一对数据[key,type()]

2.map存储的是pair对象

(2)有序关联容器

底层数据结构:红黑树 增删查 O(log2n) n是底数(树的层数 树的高度)

set

multiset

#include
增加:insert(val)
遍历:iterator自己搜索,调用find成员方法
删除:erase(key) erase(it)

注意自定义类型需要重载运算符

#include<iostream>
#include<set>
#include<map>
#include<string> 
using namespace std;
class Student
{
public:
	Student(int id,string name):_id(id),_name(name){
		
	}
	//重载小于运算符 
	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()
{
	set<Student> set;
	set.insert(Student(1020,"李四"));
	set.insert(Student(1000,"张文"));
	for(auto it=set.begin();it!=set.end();it++)
	{
		cout<<*it<<endl;
	}
	return 0;
} 

map

#include

mutimap

#include

注意使用operator[]需要提供默认参数构造函数

#include<iostream>
#include<set>
#include<map>
#include<string> 
using namespace std;
class Student
{
public:
	Student(int id=0,string name=""):_id(id),_name(name){
		
	}
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> mp;
	mp.insert({1000,Student(1000,"张文")});
	mp.insert({1020,Student(1000,"李广")});
	mp.insert({1030,Student(1000,"高阳")});
	
	cout<<mp[1020]<<endl;//使用[]需要提供默认参数构造函数 (因为可能查询的key不存在,则会构造一个新的对象)
	
	auto it = mp.begin();
	for(;it!=mp.end();it++)
	{
		cout<<"key:"<<it->first<<" value:"<<it->second;
	} 
	return 0;
} 

二、迭代器

iterator和const iterator

const_iterator:常量的正向迭代器 只能读不能写
iterator:普通的正向迭代器

两种迭代器其实是继承关系
	class const_iterator
	{
	public:
		const T& operator*(){//返回常引用 所以不能赋值 
			return *_ptr;
		}
	};
	
	class iterator : public const_iterator
	{
		public:
		T& operator*(){//返回普通引用 可以赋值 
			return *_ptr;
		}
	}
	
vector<int> const_iterator it = vec.begin();//子类的值赋值给父类 没毛病

reverse_iterator和const_reverse_iterator

reverse_iterator:普通的反向迭代器
const_reverse_iterator:常量的反向迭代器 只能读不能写

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	vector<int> vec;
	for(int i=0;i<20;i++)
	{
		vec.push_back(i);
	}
	
	//rbegin():返回的是最后一个元素的反向迭代器表示
	//rend():返回的是首元素前驱位置的迭代器的表示 
	auto rit = vec.rbegin();
	for(;rit!=vec.rend();++rit)
	{
		cout<<*rit<<" ";
	}
	return 0;
} 

三、函数对象

(1)什么是函数对象
把有operator()小括号运算符重载函数的对象,称作为函数对象或者称为仿函数
(小括号运算符重载函数一个参数叫一元函数对象,二个参数叫二元函数对象)

C语言实现两数之和

int sum(int a,int b)
{
	return a+b;
}
int ret = sum(10,20);

C++实现两数之和(函数对象)

class Sum
{
public:
	operator()(int a,int b)
	{
		return a+b;
	}
};
Sum sum;
int ret = sum(10,20);

(2)为什么使用函数对象
假如我们要实现比较两个数的大小,使用C语言实现

template<typename T>
bool greater(T a,T b)
{
	return a>b;
}
template<typename T>
bool less(T a,T b)
{
	return a<b;
}
cout<<greater(10,20)<<endl;
cout<<less(10,20)<<endl;

使用函数指针实现

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

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

template<typename T,typename Compare>
bool compare(T a,T b,Compare comp)//第三个参数接收函数指针 
{
	//通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数调用开销 
	return comp(a,b);
}
int main()
{
	cout<<compare(10,20,mygreater<int>)<<endl;
	cout<<compare(10,20,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 Func>
bool compare(T a,T b,Func fun)//第三个参数为函数对象 
{
	return fun(a,b);
}
int main()
{
	cout<<compare(10,20,mygreater<int>())<<endl;//第三个参数为函数对象 
	cout<<compare(10,20,myless<int>())<<endl;
	return 0;
} 

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

(3)STL函数对象的使用
greater:函数对象 从大到小
less:函数对象 从小到大

举例1:priority_queue 模板类原型,第一个参数为类型,第二个参数为容器,第三个参数为函数对象(默认less)
在这里插入图片描述

//默认大根堆 从小到大排序 
	priority_queue<int> queMax;
	for(int i=0;i<10;i++)
	{
		queMax.push(rand()%100+1);
	}
	while(!queMax.empty())
	{
		cout<<queMax.top()<<" ";
		queMax.pop(); 
	}
	
	//小根堆 从大到小排序 
	using MinHeap =  priority_queue<int,vector<int>,greater<int>>;
	MinHeap queMin; 
	for(int i=0;i<10;i++)
	{
		queMin.push(rand()%100+1);
	}
	while(!queMin.empty())
	{
		cout<<queMin.top()<<" ";
		queMin.pop(); 
	}

举例2:set模板类原型,第一个参数是类型,第二个参数是函数对象(默认less),第三个参数是容器空间配置器
在这里插入图片描述

	set<int,greater<int>> set;//小根堆 从大到小排列 
	for(int i=0;i<10;i++)
	{
		set.insert(rand()%100);
	}
	for(int v:set)
	{
		cout<<v<<endl;
	}

四、泛型算法

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

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

sort

sort(vec.begin(),vec.end());//从小到大排序 
sort(vec.begin(),vec.end(),greater<int>());//从大到小排序 

find
遍历查找 O(n)

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

binary_search
有序容器二分查找 O(log2n)

	if(binary_search(vec.begin(),vec.end(),21))
	{
		cout<<"52存在"<<endl;
	}

for_each
可以遍历容器的所有元素,可以自行添加合适的函数对象对容器的元素进行过滤

//输出所有偶数 
	for_each(vec.begin(),vec.end(),[](int val)->void{
		if(val%2==0)
		{
			cout<<val<<" ";
		}
	});

测试代码:

#include<iostream>
#include<vector>
#include<algorithm>
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<<" ";
	} 
	
	sort(vec.begin(),vec.end());//从小到大排序 
	for(int v:vec)
	{
		cout<<v<<" ";
	} 
	
	
	//有序容器二分查找 O(log2n)
	if(binary_search(vec.begin(),vec.end(),21))
	{
		cout<<"52存在"<<endl;
	}
	
	//遍历查找 O(n)
	auto it1 = find(vec.begin(),vec.end(),43);
	if(it1!=vec.end())
	{
		cout<<"43存在"<<endl;
	}
	
	sort(vec.begin(),vec.end(),greater<int>());//从大到小排序 
	for(int v:vec)
	{
		cout<<v<<" ";
	} 
	
	//输出所有偶数 
	for_each(vec.begin(),vec.end(),[](int val)->void{
		if(val%2==0)
		{
			cout<<val<<" ";
		}
	});
	
} 
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值