STL学习笔记

概述

六大组件介绍

  • STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。

  • 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。

  • 算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.

  • 迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。

  • 仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template

  • 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。

  • 空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.

  • STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。

在这里插入图片描述

三大组件介绍

  • algorithm(算法) - 对数据进行处理(解决问题) 步骤的有限集合

  • container(容器) - 用来管理一组数据元素

  • Iterator (迭代器) - 可遍历STL容器内全部或部分元素的对象

    容器和算法通过迭代器可以进行无缝地连接。在STL中几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。

1. 容器

数据结构描述实现头文件
向量(vector)连续存储的元素
列表(list)由节点组成的双向链表,每个结点包含着一个元素
双向队列(deque)连续存储的指向不同元素的指针所组成的数组
集合(set)由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序
多重集合(multiset)允许存在两个次序相等的元素的集合
栈(stack)后进先出的元素的排列
队列(queue)先进先出的元素的排列
优先队列(priority_queue)元素的次序是由作用于所存储的值对上的某种优先级决定的的一种队列
映射(map)由{键,值}对组成的集合,以某种作用于键对上的谓词排列
多重映射(multimap)允许键对有相等的次序的映射

2. 算法

算法分为:质变算法和非质变算法。

  • 质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
  • 非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

3. 迭代器

  • 迭代器的设计思维-STL的关键所在,STL的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dzNyqkxh-1664879698114)(D:\冲击offer\博客\C++基础\assets\image-20220929160948743.png)]

常用容器

顺序容器

1. vector容器

vector容器基本概念

  • List和vector是两个最常被使用的容器。

  • vector是将元素置于一个动态数组中加以管理的容器。

  • vector可以随机存取元素,支持索引值直接存取, 用[]操作符或at()方法对元素进行操作

  • vector尾部添加或移除元素非常快速。但是在中部或头部插入元素或移除元素比较费时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wg9bRqYS-1664879698115)(D:\冲击offer\博客\C++基础\assets\watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMzIyMTAz,size_16,color_FFFFFF,t_70.png)]

vector的数据结构

为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是容量的概念。换句话说,一个vector的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下次再有新增元素,整个vector容器就得另觅居所。

vector常用API操作

vector的赋值

v2.assign(2, 888);					//第一种玩法  改变原来vector 中的元素个数和值即改变v2.size()

v2.assign(v3.begin(), v3.end());	//第二种玩法,使用迭代器重新赋值 v3赋值给v2

int test1[]={1, 2, 3, 4, 5};
v2.assign(test1, test1+3);			//第三种玩法,使用保存指针赋值  不包括test1+3

v2 = v3;							//第四种玩法,赋值运算

vector的大小

vector.size();	       //返回容器中元素的个数

vector.empty();	       //判断容器是否为空

//重新指定容器的长度为num,若容器变长,则以默认值(0)填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
vector.resize(num);

//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
vector.resize(num, elem);  

vector末尾的添加移除操作

v2.push_back(1);  //在容器尾部加入一个元素
v2.pop_back();    //移除容器中最后一个元素

vector的数据存取

第一 使用下标操作 v2[0] = 100;

第二 使用at 方法 如: v2.at(2) = 100;

第三 接口返回的引用 v2.front() 和 v2.back()

*注意: 第一和第二种方式必须注意越界*

vector的插入

//vector.insert(pos,elem);在pos位置插入一个elem元素的拷贝,返回新数据的位置。
vector(v2,begin(),888);		

//vector.insert(pos,n,elem);在pos位置插入n个elem数据,无返回值。
vector(v2,begin(),10,888);	

//vector.insert(pos,beg,end);在pos位置插入[beg,end)区间的数据,无返回值
v2.insert(v2.begin(),v3.begin(),v3.end);

vector的删除

//1. 把v2中所有数据都删除
	v2.clear();

//2.删除第二个元素
	v2.erase(v2.begin()+1);

//3. 干掉多个元素   不包括v2.begin()+3中的元素
	v2.erase(v2.begin(), v2.begin()+3);

vector易错点

用默认构造函数易错点

vector<int> v;
v[0] = 10;			//报错
v.push_back(10);	//可行

迭代器失效

所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。这是程序员容易犯的一个错误,务必小心。

删除某个元素

vector<int> v;
for (int i = 0; i < 10; i++)
{
	v.push_back(i);
}
//删除6
for (vector<int>::iterator it = v.begin(); it != v.end();)
{
	if (*it == 6)
	{	
		//删除后返回的是7的迭代器,所以直接就++了
		it = v.erase(it);
	}
	else
	{
		it++;
	}
}

2. deque容器

deque容器基本概念

  • Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。

  • 所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,因为要移动后面所有元素。

img

  • 当然初学的时候可以简单理解为:deque是“double-ended queue”的缩写,和vector一样都是STL的容器,唯一不同的是:deque是双端数组,而vector是单端的。

deque容器实现原理

  • Deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。
  • Deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。

img

deque常用API

deque对象的带参数构造

//方式1:deque(beg,end);          构造函数将[beg, end)区间中的元素拷贝给本身。
//方式2:deque(n,elem);           构造函数将n个elem拷贝给本身。
//方式3:deque(const deque  &deq);拷贝构造函数。

deque<int> deqIntA;
	deqIntA.push_back(1);
	deqIntA.push_back(2);
	deqIntA.push_back(3);
	deqIntA.push_back(4);
		
	deque<int> deqIntB(deqIntA.begin(),deqIntA.end());		//1 2 3 4 
	deque<int> deqIntC(8, 666);								//8 8 8 8 8
	deque<int> deqIntD(deqIntA);							//1 2 3 4

deque头部和末尾的添加移除操作

deque.push_back(element);		//容器尾部添加一个数据
deque.push_front(element);	//容器头部插入一个数据
deque.pop_back();    	    	//删除容器最后一个数据
deque.pop_front();		   	//删除容器第一个数据

deque<int> deqIntA;
	deqIntA.push_back(1);
	deqIntA.push_back(2);
	deqIntA.push_back(3);//1 2 3
	deqIntA.pop_front();//2 3
	deqIntA.push_front(7);//2 3 7
	deqIntA.pop_back();//2 3

deque的数据存取

//第一  使用下标操作 deqIntA[0] = 100;
//第二  使用at 方法 如: deqIntA.at(2) = 100;
//第三  接口返回的引用 deqIntA.front() 和 deqIntA.back()  
//注意  第一和第二种方式必须注意越界

deque与迭代器

deque.begin();  //返回容器中第一个元素的迭代器。
deque.end();   //返回容器中最后一个元素之后的迭代器。
deque.rbegin();  //返回容器中倒数第一个元素的迭代器。
deque.rend();   //返回容器中倒数最后一个元素之后的迭代器。
deque.cbegin();  //返回容器中第一个元素的常量迭代器。
deque.cend();   //返回容器中最后一个元素之后的常量迭代器。

deque<int> deqIntA;
	deqIntA.push_back(1);
	deqIntA.push_back(2);
	deqIntA.push_back(3);

//三种方法遍历容器中元素

//普通迭代器
for(deque<int>::iterator it = deqIntA.begin(); it != deqIntA.end(); ++it){
	(*it)++;  //注意优先级
	cout<<*it;
	cout<<" ";
}

//常量迭代器  注意要对应
deque<int>::const_iterator cit = deqIntA.cbegin();
for( ; cit!=deqIntA.cend(); cit++){
	cout<<*cit;
	cout<<" ";
}

//逆转的迭代器
for(deque<int>::reverse_iterator rit=deqIntA.rbegin(); rit!=deqIntA.rend(); ++rit){
	cout<<*rit;
	cout<<" ";
}

deque的赋值

deque.assign(beg,end);    			//将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。
deque.assign(n,elem);  				//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq);	//重载等号操作符 
deque.swap(deq);  					// 将deque与本身的元素互换

例如:
deque<int> deqIntA,deqIntB,deqIntC,deqIntD;
deque<int> deqIntA;
		deqIntA.push_back(1);
		deqIntA.push_back(2);
		deqIntA.push_back(3);
		deqIntA.push_back(4);
		deqIntA.push_back(5);


deqIntB.assign(deqIntA.begin(),deqIntA.end());	// 1 2 3 4 5 
		
deqIntC.assign(4,888);						   //888 888 888 888 

deqIntD = deqIntA;							   //1 2 3 4 5 

deqIntC.swap(deqIntD);						   //互换

deque的大小

deque.size();	  		 //返回容器中元素的个数
deque.empty();	   		 //判断容器是否为空
//重新指定容器的长度为num,若容器变长,则以默认值0填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num);   	 
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); 


deque<int> deqIntA;
	deqIntA.push_back(1);
	deqIntA.push_back(2);
	deqIntA.push_back(3);
	deqIntA.push_back(4);
	deqIntA.push_back(5);

	int iSize = deqIntA.size();  //5

	deqIntA.resize(7);		//1 2 3 4 5 0 0 
	deqIntA.resize(8,1);	//1 2 3 4 5 0 0 1
	deqIntA.resize(2);		//1 2

deque的插入和删除

插入:
deque.insert(pos,elem);   	//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
deque.insert(pos,n,elem);   //在pos位置插入n个elem数据,无返回值。
deque.insert(pos,beg,end);  //在pos位置插入[beg,end)区间的数据,无返回值

deque.clear();	     		//移除容器的所有数据
deque.erase(beg,end); 		//删除[beg,end)区间的数据,返回下一个数据的位置。
deque.erase(pos);    		//删除pos位置的数据,返回下一个数据的位置。

3. list容器

list容器的概念

  • List和vector是两个最常被使用的容器。

  • list容器不仅是一个双向链表,而且还是一个循环的双向链表。

  • List容器不能像vector一样以普通指针作为迭代器,因为其节点不能保证在同一块连续的内存空间上。

  • 由于list是一个双向链表,迭代器必须能够具备前移、后移的能力。

  • List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效。这在vector是不成立的,因为vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效,甚至List元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受任何影响。
    在这里插入图片描述

list容器的注意事项

  • list不可以随机存取元素,所以不支持at.(position)函数与[]操作符。可以对其迭代器执行++,但是不能这样操作迭代器:it+3

list常用API

list对象的构造

//list对象的默认构造
list<int> lstInt;            	//定义一个存放int的list容器。
list<string> lstString;         //定义一个存放string的list容器。			    
注意:尖括号内还可以设置指针类型或自定义类型。
    
list对象的带参数构造
方式一:list(beg,end);     		//将[beg, end)区间中的元素拷贝给本身。
方式二:list(n,elem);      	    //构造函数将n个elem拷贝给本身。
方式三:list(const list &lst);  //拷贝构造函数。

list<int> lstInt1;
	lstInt1.push_back(1);
	lstInt1.push_back(2);
	lstInt1.push_back(3);

	list<int> lstInt2(lstInt1.begin(),lstInt1.end());	//1 2 3 
	list<int> lstInt3(5,8);								//8 8 8 8 8
	list<int> lstInt4(lstIntA);						    //1 2 3 

list头尾的添加移除操作

list.push_back(elem);	   //在容器尾部加入一个元素
list.pop_back();           //删除容器中最后一个元素
list.push_front(elem);     //在容器开头插入一个元素
list.pop_front();          //从容器开头移除第一个元素

list的数据存取

list.front();   	//返回第一个元素。
list.back();  		//返回最后一个元素。

list<int> lstInt;
	lstInt.push_back(1);
	lstInt.push_back(2);
	lstInt.push_back(3);
	lstInt.push_back(4);
	lstInt.push_back(5);

	int iFront = lstInt.front();	//1
	int iBack = lstInt.back();		//5
	lstInt.front() = 11;			//11
	lstInt.back() = 19;				//19

list与迭代器

list.begin();    //返回容器中第一个元素的迭代器。
list.end();      //返回容器中最后一个元素之后的迭代器。
list.rbegin();   //返回容器中倒数第一个元素的迭代器。
list.rend();     //返回容器中倒数最后一个元素的后面的迭代器。
list.cbegin();   //返回容器中第一个元素的常量迭代器。
list.cend();     //返回容器中最后一个元素之后的常量迭代器。

list<int> lstInt;
	lstInt.push_back(1);
	lstInt.push_back(3);
	lstInt.push_back(5);
	lstInt.push_back(7);
	lstInt.push_back(9);

	for (list<int>::iterator it=lstInt.begin(); it!=lstInt.end(); ++it)
	{
		cout << *it;
		cout << " ";
	}

	for (list<int>::reverse_iterator rit=lstInt.rbegin(); rit!=lstInt.rend(); ++rit)
	{
		cout << *rit;
		cout << " ";
	}

list的赋值

list.assign(beg,end);    			//将[beg, end)区间中的数据拷贝赋值给本身。
list.assign(n,elem);  				//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);	//重载等号操作符。
list.swap(lst);  					// 将lst与本身的元素互换。

	
llist<int> lstIntA,lstIntB,lstIntC,lstIntD;
	lstIntA.push_back(1);
	lstIntA.push_back(3);
	lstIntA.push_back(5);
	lstIntA.push_back(7);
	lstIntA.push_back(9);

	lstIntB.assign(lstIntA.begin(),lstIntA.end());			//1 3 5 7 9
    lstIntB.assign(++lstIntA.begin(),--lstIntA.end());		//3 5 7

	lstIntC.assign(5,8);						//8 8 8 8 8
	lstIntD = lstIntA;							//1 3 5 7 9
	lstIntC.swap(lstIntD);						//互换

list的大小

ist.size();	  //返回容器中元素的个数
list.empty(); //判断容器是否为空
//重新指定容器的长度为num,若容器变长,则以默认值0填充新位置。如果容器变短,则末尾超出容器长度的元素被除。
list.resize(num); 
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
list.resize(num, elem);  

list<int> lstIntA;
	lstIntA.push_back(1);
	lstIntA.push_back(2);
	lstIntA.push_back(3);

	if (!lstIntA.empty())
	{
		int iSize = lstIntA.size();	//3
		lstIntA.resize(5);			//1 2 3 0 0
		lstIntA.resize(7,1);		//1 2 3 0 0 1 1
		lstIntA.resize(5);			//1 2 3 0 0
	}

list的插入

list.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
list.insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。
list.insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。

	list<int> listA;
	list<int> listB;

	listA.push_back(1);
	listA.push_back(2);
	listA.push_back(3);
	listA.push_back(4);
	listA.push_back(5);

	listB.push_back(11);
	listB.push_back(12);
	listB.push_back(13);
	listB.push_back(14);

	listA.insert(listA.begin(), -1);		//{-1, 1, 2, 3, 4, 5}
	listA.insert( ++listA.begin(), 2, -2);	//{-1, -2, -2, 1, 2, 3, 4, 5}
	listA.insert(listA.begin() , listB.begin() , listB.end());	//{11, 12, 13, 14, -1, -2, -2, 1, 2, 3, 4,5}

	for(list<int>::iterator it = listA.begin(); it!=listA.end(); it++){
		cout<< *it<<endl;
	}

list的删除

list.clear();		//移除容器的所有数据
list.erase(beg,end);  //删除[beg,end)区间的数据,返回下一个数据的位置。
list.erase(pos);    //删除pos位置的数据,返回下一个数据的位置。
lst.remove(elem);   //删除容器中所有与elem值匹配的元素。

list的反序排列

reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序

list<int> listA;

	listA.push_back(1);
	listA.push_back(2);
	listA.push_back(3);
	listA.push_back(4);
	listA.push_back(5);

	listA.reverse();			//5, 4, 3, 2, 1
	listA.sort();				//1,2,3,4,5

关联式容器

1. set/multiset容器

set容器基本概念

set和multiset是一个集合容器,其中set所包含的元素是唯一的,集合中的元素按一定的顺序排列。set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。在n个数中查找目标数的效率是 log2 n

红黑树定义 是每个节点都带有颜色属性(颜色为红色或黑色)的自平衡二叉查找树,满足下列性质:

1)节点是红色或黑色;

2)根节点是黑色;

3)所有叶子节点都是黑色节点(NULL);

4)每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)

5)从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QBbP12xt-1664879698117)(D:\冲击offer\博客\C++基础\assets\wps1.jpg)]

Set 和 multiset 特点

  • set中元素插入过程是按排序规则插入,所以不能指定插入位置。

  • set不可以直接存取元素。(不可以使用at.(pos)与[]操作符)。

  • multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次

  • 不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素

  • 头文件 #include

set常用API

set/multiset对象的默认构造

set<int> setInt;        //一个存放int的set容器。

multiset<int> mulsetInt;//一个存放int的multi set容器。

Set/multiset 对象的带参构造函数

set(beg,end);   //将[beg, end)区间中的元素拷贝给本身。

set(const set &s); //拷贝构造函数。

multiset(beg,end);   //将[beg, end)区间中的元素拷贝给本身。

multiset(const multiset &s); //拷贝构造函数。

set对象的拷贝构造与赋值

set(const set &st);		    //拷贝构造函数

set& operator=(const set &st);	//重载等号操作符

set.swap(st);				  //交换两个集合容器

set与迭代器:

set.insert(elem);   //在容器中插入元素。

set.begin();       //返回容器中第一个数据的迭代器。

set.end();         //返回容器中最后一个数据之后的迭代器。

set.rbegin();     //返回容器中倒数第一个元素的迭代器。

set.rend();        //返回容器中倒数最后一个元素的后面的迭代器。

set/multiset的大小:

l set.size();	//返回容器中元素的数目

l set.empty();//判断容器是否为空

//注意事项: 它们没有resize 方法

set/multiset的删除

set.clear();		 //清除所有元素

set.erase(pos);   //删除pos迭代器所指的元素,返回下一个元素的迭代器。

set.erase(beg,end);	 //删除区间[beg,end)的所有元素,返回下一个元素的迭代器。

set.erase(elem);   //删除容器中值为elem的元素。

set/multiset的查找

set.find(elem);  //查找elem元素,返回指向elem元素的迭代器。

set.count(elem);  //返回容器中值为elem的元素个数。对set来说,要么是0,要么是1。对multiset来说,值可能大于1。

set.lower_bound(elem);  //返回第一个>=elem元素的迭代器。

set.upper_bound(elem);	  //  返回第一个>elem元素的迭代器。

set.equal_range(elem);		//返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。以上函数返回两个迭代器,而这两个迭代器被封装在pair中。

2. map/multimap容器

map/multimap基本概念

  • map的特性是,所有元素都会根据元素的键值自动排序。

  • map是标准的关联式容器,一个map里存储的元素是一个键值对序列,叫做(key,value)键值对。它提供基于key快速检索数据的能力。

  • map底层的具体实现是采用红黑树变体的平衡二叉树的数据结构。在插入操作、删除和检索操作上比vector快很多。

  • map可以直接存取key所对应的value,支持[]操作符,如map[key]=value。

  • Map所有的元素都是pair,同时拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值,map不允许两个元素有相同的键值。

multimap与map的区别:

  • map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符。

map/multimap常用API

map/multimap对象的默认构造

//map/multimap	采用模板类实现,对象的默认构造形式:
//map<T1,T2> mapTT; 其中T1,T2还可以用各种指针类型或自定义类型
//multimap<T1,T2>  multimapTT;  

//如:
map<int, char> mapA;
map<string,float> mapB;

map和multimap对象的带参数构造

方式一:map(beg,end);   //将[beg, end)区间中的元素拷贝给本身。

方式二:map(const map &mapObject); //拷贝构造函数。

map的插入与迭代器

//map中插入元素的四种方式:
map<int, string> mapStu;

//方式一、通过pair的方式插入对象
mapStu.insert(  pair<int,string>(1,"张三")  );

//方式二、通过pair的方式插入对象
mapStu.inset(make_pair(2, “李四”));

//方式三、通过value_type的方式插入对象
mapStu.insert(  map<int,string>::value_type(3,"王五")  );

//方式四、通过数组的方式插入值,如果键值对已经存在,则覆盖原值
mapStu[4] = "赵六";

注意:

前三种方法,采用的是insert()方法,该方法返回值为pair<iterator,bool>,可以用于判断是否插入成功

第四种方法非常直观,但碰到相同的键时会进行覆盖操作。

迭代器:

map.begin();  //返回容器中第一个数据的迭代器。
map.end();    //返回容器中最后一个数据之后的迭代器。
map.rbegin(); //返回容器中倒数第一个元素的迭代器。
map.rend();   //返回容器中倒数最后一个元素的后面的迭代器。

map/multimap 排序

map<T1,T2,less<T1> >  mapA;  //该容器是按键的升序方式排列元素。未指定函数对象,默认采用less<T1>函数对象。
map<T1,T2,greater<T1>> mapB; //该容器是按键的降序方式排列元素。
//less<T1>与greater<T1>  可以替换成其它的函数对象functor。
//可编写自定义函数对象以进行自定义类型的比较,使用方法与set构造时所用的函数对象一样。

map对象的拷贝构造与赋值

map(const map &mp);		     //拷贝构造函数
map& operator=(const map &mp);	//重载等号操作符
map.swap(mp);				//交换两个集合容器

map的大小

map.size();	//返回容器中元素的数目

map.empty();//判断容器是否为空

map的删除

map.clear();		//删除所有元素

map.erase(pos);		//删除pos迭代器所指的元素,返回下一个元素的迭代器。

map.erase(beg,end); //删除区间[beg,end)的所有元素,返回下一个元素的迭代器。

map.erase(key);   	//删除容器中key为key的对组,返回删除的对组个数

map/multimap的查找

//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
map.find(key);  
//返回容器中键值为key的对组个数。对map来说,要么是0,要么是1;对multimap来说,值>=0。
map.count(key);   	
//返回第一个key>=keyElem元素的迭代器。
map.lower_bound(keyElem);  
//返回第一个key>keyElem元素的迭代器。
map.upper_bound(keyElem);	
//返回容器中key与keyElem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。
map.equal_range(keyElem);		

map为自定义数据类型排序

当 key 为自定义数据类型时,要注意,必须在构造 map 的时候就指定排序规则。

需求:

1.在函数外新建一个类,在类中定义一个仿函数,来设定排序规则,比如,根据每个人的年龄降序排列。

2.在 map 参数中添加定义了仿函数的类名。

实现:

#include<iostream>
#include<map>
#include<iostream>
#include<string>
#include<map>
using namespace std;
 
class Person
{
public:
	Person(string name, int age) :m_name(name), m_age(age) {}
	string m_name;
	int m_age;
};
 
class myCompare
{
public:
	bool operator()(const Person& p1, const Person& p2) const
	{
		//根据年龄降序排列
		return p1.m_age > p2.m_age;
	}
};
void test01()
{
	//使用map存放自定义数据类型,必须要在map参数中指定排序方式
	map<Person, int, myCompare> m;
	Person p1("张三", 18);
	Person p2("李四", 25);
	Person p3("王五", 30);
	Person p4("赵六", 24);
	m.insert(make_pair(p1, 3));
	m.insert(make_pair(p2, 4));
	m.insert(make_pair(p3, 1));
	m.insert(make_pair(p4, 2));
 
	//输出map内容
	for (map<Person, int, myCompare>::iterator it = m.begin(); it != m.end(); it++)
	{
		cout << "key= " << it->second << "  姓名为: " << it->first.m_name << "   年龄为: " << it->first.m_age << endl;
	}
}
int main()
{
	test01();
	return 0;
}

容器适配器

1. stack容器

stack(栈)容器基本概念

stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,形式如图所示。stack容器允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取stack的其他元素。换言之,stack不允许有遍历行为

  • 有元素推入栈的操作称为:push,将元素推出stack的操作称为pop.

在这里插入图片描述

stack没有迭代器

  • Stack所有元素的进出都必须符合”先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。Stack不提供遍历功能,也不提供迭代器。

stack常用API

stack对象的默认构造

  • stack采用模板类实现, stack对象的默认构造形式: stack stkT;
  • 尖括号内还可以设置指针类型或自定义类型。
stack <int> stkInt;       	//一个存放int的stack容器。

stack <string> stkString;   //一个存放string的stack容器。

stack的push()与pop()方法

stack.push(elem);  //往栈头添加元素

stack.pop();      //从栈头移除第一个元素

stack对象的拷贝构造与赋值

stack(const stack &stk);		   //拷贝构造函数

stack& operator=(const stack &stk);	//重载等号操作符

stack的数据存取

stack.top();	 //返回最后一个压入栈元素

stack的大小

stack.empty();  //判断堆栈是否为空

stack.size(); 	//返回堆栈的大小

2. queue容器

queue(队列)简介

  • queue是队列容器,是一种**“先进先出”**的容器。

  • 默认情况下queue是利用deque容器实现的一种容器。

  • 换言之,queue不允许有遍历行为

  • 它只允许在队列的前端(front)进行删除操作,而在队列的后端(back)进行插入操作

  • #include

  • 有元素推入栈的操作称为:push,将元素推出stack的操作称为pop.

在这里插入图片描述

queue没有迭代器

  • Queue所有元素的进出都必须符合”先进先出”的条件,只有queue的顶端元素,才有机会被外界取用。Queue不提供遍历功能,也不提供迭代器。

queue常用API

queue对象的默认构造

  • queue采用模板类实现,queue对象的默认构造形式:queue queT; 如:
queue<int> queueInt;       //一个存放int的queue容器。

queue<float> queueFloat;   //一个存放float的queue容器。

queue<string> queueString;   //一个存放string的queue容器。	
  • 注意:尖括号内还可以设置指针类型或自定义类型。

queue 对象的带参构造

queue<int, list<int>> queueList; //内部使用list 来存储队列元素的queue 容器.

//错误: queue<int, vector<int>> queueList; //内部不能使用vector来存储队列元素			

queue的push()与pop()方法

queue.push(elem);  //往队尾添加元素

queue.pop();    //从队头处移除队首元素

queue对象的拷贝构造与赋值

queue(const queue &que);		   //拷贝构造函数

queue& operator=(const queue &que);	//重载等号操作符

queue的数据存取

queue.back();  //返回最后一个元素

queue.front();  //返回第一个元素

queue的大小

queue.empty();  //判断队列是否为空

queue.size(); 	//返回队列的大小

3. priority_queue

优先队列: 它的入队顺序没有变化,但是出队的顺序是根据优先级的高低来决定的。优先级高的优先出队。

  • 最大值优先级队列、最小值优先级队列

  • 用来开发一些特殊的应用

  • #include

//demo15_41
#include <queue>
#include <iostream>
#include <list>
#include <vector>
#include <deque>
#include <set>

using namespace std;

int main(void) {
	
	//priority_queue<int>   pqA;//默认情况下是值越大,优先级越大
	//priority_queue<int, vector<int>, greater<int>> pqA;  //使用 vector 值越小,优先级越大
	priority_queue<int, deque<int>, greater<int>> pqA;  //使用deque 值越小,优先级越大
	//priority_queue<int, list<int>, greater<int>> pqA;     //不可以使用list,不兼容

	pqA.push(1);
	pqA.push(2);
	pqA.push(3);
	pqA.push(3);
	pqA.push(4);
	pqA.push(5);
	pqA.push(3);

    //5 4 3 2 1
	while(!pqA.empty()){ 
		cout<<pqA.top()<<" ";//读取队首的元素,但元素不出列   
		pqA.pop();           //出队列    
	}

	cout<<endl;

	system("pause");
	return 0;

}

STL容器使用时机

在这里插入图片描述

  • vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。

  • deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。

vector与deque的比较:

一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。

  • list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。

  • set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。

  • map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

其他

1. 仿函数

概述:

  • 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。

尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。

functor,翻译成函数对象,伪函数,它是是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。

//内置排序规律  以set

//升序 默认形式
set<int,less<int>> set1;
//降序
set<int,greater<int>> set1;

示例:

#include <iostream>
#include<string>
#include<set>
using namespace std;

class Student
{
public:
	Student(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	int getAge() const
	{
		return age;
	}
	string getName() const
	{
		return name;
	}

private:
	string name;
	int age;
};
class BJ
{
public:
	bool operator()(const Student &s1, const Student &s2)
	{
		 ret = s1.getAge() > s2.getAge();
		 return ret;
	}
private:
	bool ret;
};
int main(void) {

	multiset<Student , BJ> s;
	s.insert(Student("小红", 12));
	s.insert(Student("小李", 10));
	s.insert(Student("小小", 20));

	multiset<Student, BJ>::iterator it = s.begin();
	for (; it != s.end(); it++)
	{
		cout << it->getName() << "  " << it->getAge() << endl;
	}
	
	//像函数一样调用
    Student s1("花花", 13);
	Student s2("小小", 18);
	BJ bj;
	cout << bj(s1, s2) << endl;

	system("pause");
	return 0;
}

总结:

1、函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题。

2、函数对象超出普通函数的概念,相对于回调函数仿函数可以有自己的状态即有成员变量。

3、模版函数对象使函数对象具有通用性,这也是它的优势之一 。

2. 对组(pair)

  • pair表示一个对组,它将两个值视为一个单元,把两个值捆绑在一起。

  • pair<T1,T2>用来存放的两个值的类型,可以不一样,也可以一样,如T1为int,T2为float。T1,T2也可以是自定义类。

  • pair.first是pair里面的第一个值,是T1类型。

  • pair.second是pair里面的第二个值,是T2类型。

  • 类模板:template <class T1, class T2> struct pair.

示例1:

set<int>  setInt;

for(int i=5; i>0; i--){
		pair<set<int>::iterator, bool> ret = setInt.insert(i);
		if(ret.second){
			cout<<"插入 "<<i<<" 成功!"<<endl;
	}else {
			cout<<"插入 "<<i<<" 失败!"<<endl;
          }
}

示例2:

//第一种方法创建一个对组
pair<string, int> pair1(string("name"), 20);
cout << pair1.first << endl; //访问pair第一个值
cout << pair1.second << endl;//访问pair第二个值
//第二种
pair<string, int> pair2 = make_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=赋值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;

3. C++11新特性 变参模板、完美转发和emplace

变参模板 - 使得 emplace 可以接受任意参数,这样就可以适用于任意对象的构建

完美转发 - 使得接收下来的参数能够原样的传递给对象的构造函数,这带来另一个方便性

#include <iostream>
using namespace std;
#include<string>
#include <vector>

class student {
public:
	student() {
		this->name = "小明";
		this->age = 20;
	}

	student(int age, string name) {
		this->age = age;
		this->name = name;
	}

	student(const student &s) {
		this->age = s.age;
		this->name = s.name;

	}
public:
	int age;
	string name;
};

int main(void) {
	vector<student>   vectStu(1);   //默认构造先创建第一个参数

    //student会先创建一个对象,然后调用构造函数,拷贝一份对象放在容器中
    //vecStu(student(19,"王大锤"));
    
    //容器把参数先放到内部,然后调用构造函数,创建对象,所以不存在拷贝构造的一个过程
	vectStu.emplace_back(19, "王大锤");   //相当于push_back

	vectStu.emplace(vectStu.end(), 18, "李小花");//相当于insert

	
	for (vector<student>::iterator it = vectStu.begin(); it != vectStu.end(); it++)
	{
		cout << "姓名:" << (*it).name << "  " << (*it).age << endl << endl;
	}

	cout << "vectStu size (1):" << vectStu.size() << endl;
	cout << "vectStu capacity(1):" << vectStu.capacity() << endl;

	system("pause");
	return 0;
}

对deque,和list容器还有头插操作

deque de;
de.emplace_front(19, “王大锤”);

list li;
li.emplace_front(19, “王大锤”);

4. STL常见疑难杂症

注意:任何时候在模板(template)中使用一个嵌套从属类型名称, 需要在前一个位置, 添加关键字typename;

typename list<T>::const_iterator citor;

比如上例中使用迭代器类型时,就要使用typename.虽然在vs2010 和vs2015中没有错误,但在VC++2019和gcc编译器中,都会报错。

#include <iostream>
#include <deque>
#include <string>
#include <vector>
#include <list>
#include <Windows.h>

using namespace std;

template <typename T>
void printInf(const list<T>& object) throw()
{
	string line(50, '-');
	typename list<T>::const_iterator citor;
	for (citor = object.begin(); citor != object.end(); citor++) {
		cout << *citor << endl;
	}
	cout << endl;
	cout << "size:" << object.size() << endl;
	cout << line << endl;
	return;
}

class Student
{
public:
	Student() {
		cout << "默认构造函数" << endl;
		this->m_nAge = 0;
		this->m_sName = "未知";
	}
	Student(int _age, const char* _name) {
		cout << "带参数的构造函数" << endl;
		this->m_nAge = _age;
		this->m_sName = _name;
	}
	Student(const Student& object) {
		cout << "拷贝构造函数" << endl;
		this->m_nAge = object.m_nAge;
		this->m_sName = object.m_sName;
	}
	~Student() {
		cout << "析构函数 " << endl;
	}
	friend ostream& operator<<(ostream& out, const Student& stu);
public:
	string	m_sName;
	int		m_nAge;
};

ostream& operator<<(ostream& out, const Student& stu) {
	out << "年龄:" << stu.m_nAge << "\t" << "姓名:" << stu.m_sName;
	return out;
}

int main(int agrc, char** argv)
{
	Student s1(21, "张大帅");
	Student s2(21, "李小美");
	Student s3(51, "张三");
	Student s4(50, "罗二");
	
	list<Student> stuList;

	printInf<Student>(stuList);

	system("pause");
	return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
侯捷的《STL源码剖析》是一本关于STL(标准模板库)的学习笔记。这本书深入解析了STL的实现原理和设计思路,对于理解STL的内部机制和使用方法非常有帮助。这些学习笔记记录了作者在学习侯捷的《STL标准库和泛型编程》课程时的心得和总结,对于理解STL源码和进行泛型编程都具有指导意义。 这本书涉及了STL的各个模块,包括容器、迭代器、算法等,并解释了它们的实现原理和使用方法。通过学习这本书,你可以更好地理解STL的底层实现和使用技巧。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [STLSourceAnalysis:stl原始码剖析(侯捷)的学习笔记](https://download.csdn.net/download/weixin_42175776/16069622)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [候捷老师STL源码剖析视频课程笔记](https://blog.csdn.net/weixin_46065476/article/details/125547869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [侯捷——STL源码剖析 笔记](https://blog.csdn.net/weixin_45067603/article/details/122770539)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值