前言
这人呀,年纪一到脑子就不够用了,每次使用STL中的一些函数时,大概有些映像,但是就是不能具体想出函数名称是啥或者具体参数是啥,百度大法虽然好但是着实影响效率,今天我就整理一下STL中的常用容器和常用函数用法,一篇博文基本解决STL的常用用法。
一、容器
1.序列式容器
1.1 字符串(string)
字符串包括字符操作再leetcode刷题中经常遇到,是处理一些问题的基础,需要重点掌握。
string初始化:https://blog.youkuaiyun.com/VariatioZbw/article/details/116592225
1.1.1 将string转换为char*
方法1:使用c_str()方法
代码如下:
#include <string>
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
string strOutput = "Hello World";
cout << "[cout] strOutput is: " << strOutput << endl;
// string 转换为 char*
const char* pszOutput = strOutput.c_str();
printf("[printf] strOutput is: %s\n", pszOutput);
return 0;
}
编译并执行上述代码,结果如下:
上述代码执行结果说明:
- cout 可直接输出 string 类的对象的内容;
- 使用 c_str() 方法转换 string 类型到 char* 类型时,需要为char*添加 const 关键字;
- printf() 函数不能直接打印 string 类的对象的内容,可以通过将 string 转换为 char* 类型,再使用 printf() 函数打印。
方法2:使用data()方法
data()方法与c_str()方法相似,都返回 const char* 类型。两者区别和联系如下:
- 在C++98版本中,c_str()返回 const char* 类型,返回的字符串会以空字符(null character)结尾;
- 在C++98版本中,data()返回 const char* 类型,返回的字符串不以空字符(null character)结尾;
- 在C++11版本中,c_str()与data()用法相同(Both string::data and string::c_str are synonyms and return the same value.)
注意:使用atoi() 和 atof()时需要先将string转化为const char*格式,即利用c_str()先转为const char *,然后再使用atoi()将字符串转化为int型数,或使用atof转化为float型数
1.1.2 常见函数
s.size() // 求长度
s.empty() // 判断是否为空
s.insert(int index, string str) // 在s[4]的位置插入str
// 举例说明
string s = "I am";
s.insert(4," good"); // 此时s为 I am good
s.substr(int pos,int length) // 从位置pose处开始,截取长度为length的子串
1.1.3 string中的find操作
find
注:学会find操作即可
注意:学会find操作就可以了,其他的不用理会,int find(char c,int pos = 0) const : 默认是从0位置开始找,没有找到返回npos这个关键字,其他的方法不用了解,用不到!!!
// string 中find方法
//从pos开始查找字符c在当前字符串的位置
int find(char c, int pos = 0) const;
//从pos开始查找字符串s在当前串中的位置
int find(const char *s, int pos = 0) const;
//从pos开始查找字符串s中前n个字符在当前串中的位置
int find(const char *s, int pos, int n) const;
//从pos开始查找字符串s在当前串中的位置
int find(const string &s, int pos = 0) const;
//以上查找成功时返回所在位置,失败返回string::npos的值
rfind
// string中的rfind函数
//从pos开始从后向前查找字符c在当前串中的位置
int rfind(char c, int pos = npos) const;
int rfind(const char *s, int pos = npos) const;
int rfind(const char *s, int pos, int n = npos) const;
int rfind(const string &s,int pos = npos) const;
//从pos开始从后向前查找字符串s中前n个字符组成的字符串在当前串中的位置,成功返回所在位置,失败时返回string::npos的值
还有一些其他的查找功能,详见https://blog.youkuaiyun.com/u014203453/article/details/77740423
1.1.4 将char*、char[]转化为string
将 char*、char[] 转换为 string 类型时,直接进行赋值操作,将 char*、char[] 的变量赋值给 string 对象即可。
说明:这里所说的“赋值”操作,实际上是将 char*、char[] 定义的字符串的首地址赋值给 string 对象了。
示例代码(stringtochar.cpp)如下:
#include <string>
#include <iostream>
using namespace std;
int main()
{
const char* pszName = "liitdar";
char pszCamp[] = "alliance";
string strName;
string strCamp;
strName = pszName;
strCamp = pszCamp;
cout << "strName is: " << strName << endl;
cout << "strCamp is: " << strCamp << endl;
return 0;
}
1.1.4.2 单独的一个字符串与string类型的转化
1.
char c = 'a';
string str = {c};
2.
char c = 'a';
string str;
str.push_back(c);
不存在 字符类型 char到string类型的强制类型转化,即 string©会报错。
1.1.5 char型与整型的相互转化
首先需要注意的一点是将char型转化为整型不能直接通过int()强制类型转化,否则的话会转化为对应ascii码值,如下所示:
int main()
{
// char 转为 int
char a_c = '0';
int a = int(a_c);
int b = a_c - '0';
cout << a << endl;
cout << b << endl;
// int 转为 char
char b_c = b + '0';
cout << b_c << endl;
return 0;
}
结果如下:
可以知道 a_c - ‘0’ 才是正确的。
1.2 vector
1.2.1 vector常见函数
注意:erase的用法需要注意,里面的参数必须是迭代器,如arr.erase(arr.begin()+i),或者先用auto iter = find(arr.begin(),arr.end(),num) ,然后删除:arr.erase(iter)
int size() : 返回容器对象中元素的个数
bool empty(): 判断容器对象是否为空
begin():返回指向容器中第一个元素的迭代器。
end():返回指向容器中最后一个元素后面的位置的迭代器。
rbegin():返回指向容器中最后一个元素的反向迭代器。
rend():返回指向容器中第一个元素前面的位置的反向迭代器。
erase(iterator loc): 删除loc位置的元素(https://img2018.cnblogs.com/blog/1169804/201903/1169804-20190310230711138-1115905688.png)
erase(iterator first,iterator last): 删除[first,last)这一段
***需要注意,在进行单个元素删除后,传入的迭代器指向不变,仍然指
向被删除元素的位置,而被删除元素之后的所有元素都向前移动一位,也
就是该迭代器实际上是指向了原来被删除元素的下一个元素***
clear():从容器中删除所有元素。
front():返回容器中第一个元素的引用。
back():返回容器中最后一个元素的引用。
push_back():在容器末尾增加新元素。
pop_back():删除容器末尾的元素。
insert(iterator loc ,size_type num):指定位置loc前面插入元素
// c++ 11新特性
emplace_back(): 对标push_back()
emplace() : 对标insert()
find(vec.begin(),vec.end(),N); //在vec中查找N,返回迭代器
// 如果返回的是 vec.end() 说明没有找到,即数组中不存在这个数
// 此外如果需要使用二分查找则必须是已经排序的数组
1.3 list
list也是顺序容器的一种,知识list底层是一个双向链表,使用list必须包含头文件list。双向链表的每个元素中都有一个指针指向后一个元素,也有一个指针指向前一个元素,如下图所示。
当然,list的用法和vector很类似,也拥有顺序容器中的常用方法,需要注意的是**list不支持使用下标随机存取元素。
在 list 容器中,在已经定位到要增删元素的位置的情况下,增删元素能在常数时间内完成。如下图所示,在 ai 和 ai+1 之间插入一个元素,只需要修改 ai 和 ai+1 中的指针即可
1.3.1 list的相关函数
#include <list>
// 1.头尾的添加移除操作
list.push_back(elem); //在容器尾部加入一个元素
list.pop_back(); //删除容器中最后一个元素
list.push_front(elem); //在容器开头插入一个元素
list.pop_front(); //从容器开头移除第一个元素
// 2.数据存取
list.front(); //返回第一个元素。
list.back(); //返回最后一个元素。
// 3.与迭代器相关
list.begin(); //返回容器中第一个元素的迭代器。
list.end(); //返回容器中最后一个元素之后的迭代器。
list.rbegin(); //返回容器中倒数第一个元素的迭代器。
list.rend(); //返回容器中倒数最后一个元素的后面的迭代器。
// 4.带参构造(和vector类似)
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数
// 5.大小相关(和vector类似)
list.size(); //返回容器中元素的个数
list.empty(); //判断容器是否为空
list.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
list.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
// 6.插入元素(注意是迭代器)
list.insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
list.insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。
list.insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值。
// 7.删除元素
list.clear(); //移除容器的所有数据
list.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
list.erase(pos); //删除pos位置的数据,返回下一个数据的位置。
lst.remove(elem); //删除容器中所有与elem值匹配的元素。
// 8.反序排列
lst.reverse(); //反转链表
// 9.赋值操作
赋值操作
list.assign(beg,end); //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。
list.assign(n,elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
list.swap(lst); //将lst与本身的元素互换。
// 10.排序
sort(): // 排序,注意和vector的区别
// STL中的算法sort可以对vector和deque进行排序,它需要随机访问迭代器的支持,因为list不支持随机访问迭代器,所以不能用sort对list进行排序,因此list容器引入了sort成员函数以完成排序,
其他成员函数:
1.3.2 list的元素访问
不同于之前学过的 STL 容器,访问 list 容器中存储元素的方式很有限,即要么使用 front() 和 back() 成员函数,要么使用 list 容器迭代器。
list 容器不支持随机访问,未提供下标操作符 [] 和 at() 成员函数,也没有提供 data() 成员函数。
通过 front() 和 back() 成员函数,可以分别获得 list 容器中第一个元素和最后一个元素的引用形式。举个例子:
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> mylist{ 1,2,3,4 };
int &first = mylist.front();
int &last = mylist.back();
cout << first << " " << last << endl;
first = 10;
last = 20;
cout << mylist.front() << " " << mylist.back() << endl;
return 0;
}
#include <iostream>
#include <list>
using namespace std;
int main()
{
const std::list<int> mylist{1,2,3,4,5};
auto it = mylist.begin();
cout << *it << " ";
++it;
while (it!=mylist.end())
{
cout << *it << " ";
++it;
}
return 0;
}
1.4 deque
deque是双端队列,deque容器为一个给定类型的元素进行线性处理,像向量一样,它能够快速地随机访问任一个元素,并且能够高效地插入和删除容器的尾部元素。但它又与vector不同,deque支持高效插入和删除容器的头部元素,因此也叫做双端队列。deque类常用的函数如下。
deque():创建一个空deque
deque(int nSize):创建一个deque,元素个数为nSize
deque(int nSize,const T& t):创建一个deque,元素个数为nSize,且值均为t
deque(const deque &):复制构造函数
增加数
void push_front(const T& x):双端队列头部增加一个元素X
void push_back(const T& x):双端队列尾部增加一个元素x
iterator insert(iterator it,const T& x):双端队列中某一元素前增加一个元素x
void insert(iterator it,int n,const T& x):双端队列中某一元素前增加n个相同的元素x
void insert(iterator it,const_iterator first,const_iterator last):双端队列中某一元素前插入另一个相同类型向量的[first,last)间的数据
删除数
Iterator erase(iterator it):删除双端队列中的某一个元素
Iterator erase(iterator first,iterator last):删除双端队列中[first,last)中的元素
void pop_front():删除双端队列中最前一个元素
void pop_back():删除双端队列中最后一个元素
void clear():清空双端队列中所有元素
reference at(int pos):返回pos位置元素的引用
reference front():返回首元素的引用
reference back():返回尾元素的引用
iterator begin():返回向量头指针,指向第一个元素
iterator end():返回指向向量中最后一个元素下一个元素的指针(不包含在向量中)
reverse_iterator rbegin():反向迭代器,指向最后一个元素
reverse_iterator rend():反向迭代器,指向第一个元素的前一个元素
1.5 stack
很简单,没什么好说的,参考链接: https://blog.youkuaiyun.com/summer00072/article/details/80753821
C++ Stack(堆栈) 是一个容器类的改编,为程序员提供了堆栈的全部功能,——也就是说实现了一个先进后出(FILO)的数据结构。
c++ stl栈stack的头文件为:
#include <stack>
c++ stl栈stack的成员函数介绍
操作 比较和分配堆栈
empty() 堆栈为空则返回真
pop() 移除栈顶元素
push() 在栈顶增加元素
size() 返回栈中元素数目
top() 返回栈顶元素
1.6 queue
也很简单,
queue入队,如例:q.push(x); 将x 接到队列的末端。
queue出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。
访问queue队首元素,如例:q.front(),即最早被压入队列的元素。// 不是用的top,而是front
访问queue队尾元素,如例:q.back(),即最后被压入队列的元素。
判断queue队列空,如例:q.empty(),当队列空时,返回true。
访问队列中的元素个数,如例:q.size()
1.7 heap
1.heap概述
heap处于头文件#include<algorithm>
中,STL在<algorithm.h>中实现了对存储在数组或者vector中(是的,没错,堆的底层是vector,堆是基于vector的特殊的数据结构)的元素进行堆操作的函数,包括make_heap,pop_heap,push_heap,sort_heap。
默认情况下是max-heap,该大顶堆实际上是以一个vector表现的完全二叉树。
2.heap函数介绍
算了,队自己写吧,别看这些了。
// heap操作得四个函数
make_heap(_First,_Last,_Comp) : 建立堆(要么大顶堆,要么就是小顶堆)
push_heap(_First,_Last,_Comp) : 在堆中添加元素
pop_heap(_First,_Last,_Comp) : 在堆中删除元素
sort_heap(_First,_Last,_Comp) : 堆排序
相关参数介绍:
_First , _Last : 可以随机访问的迭代器/指针
_Comp : 比较函数(仿函数),其规则是:如果函数的第一个参数小于第二个参数应返回true,否则返回false,默认为less
以数据类型为int为例:若使用大顶堆,则_Comp为 less<int>() , 小顶堆则为 greater<int>()
下面举例说明:
建立堆:
vector<int> min= {10,30,22,6,15,9};
//建立小顶堆
make_heap(min.begin(), min.end(), greater<int>()); //此时min中数据顺序如下:6 10 9 30 15 22
// 插入元素
min.push_back(20);
push_heap(min.begin(),min.end(), greater<int>());//该算法前提:必须在堆的条件下
// 此时 min:6 10 9 30 15 22 20 仍为小顶堆
// 删除堆顶元素
pop_heap(min.begin(),min.end(),greater<int>());
//此时min:9 10 20 30 15 22 6 不为小顶堆 这个pop_heap操作后,实际上是把堆顶元素放到了末尾
min.pop_back(); // 这才彻底在底层vector数据容器中删除堆顶元素
// 此时 min:9 10 20 30 15 22 仍为小顶堆
// 堆排序,保持greater,小顶堆,得到的是降序
sort_heap(min.begin(),min.end(), greater<int>());//试了用less,结果杂乱无章
//min:30 22 20 15 10 9 注意结果是降序的哦!!!其实是调用了很多次pop_heap(...,greater..),每一次都把小顶堆堆顶的元素往末尾放,没放一次end迭代器减1
// 建立大顶堆的例子类时,将greater<int>()换成less<int>()即可
1.7 priority_queue
常用函数
需要注意的一点是,priority_queue的基本函数和queue基本一致,唯一的区别是queue有front()和back() 函数 ,但是priority_queue只有top(),也只能pop()出队列顶端元素。
2.关联式容器
标准的关联式容器分为set(集合)和map(映射表),以及这两大类的衍生体multiset(多键集合)和multimap(多键映射表),这些容器的底层机制均以 RB-TREE (红黑树)完成。
2.1 set
set和map容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。
使用 set 容器存储的各个元素的值必须各不相同。
STL中的set底层也是RB-TREE(红黑树),集合中的每一个元素只出现一次,并且是排好顺序的(默认按照键值升序排列),访问元素的时间复杂度是O(log2 n)
在c++中,set的头文件是 #include<set>
set具有迭代器 set<int>::iterator i
定义一个迭代器,名为i 可以把迭代器理解为C语言的指针
set常用操作
set<int> q; //以int型为例 默认按键值升序
set<int,greater<int>> p; //降序排列 ,默认是升序排列
int x;
q.insert(x); //将x插入q中
q.erase(x); //删除q中的x元素,返回0或1,0表示set中不存在x
q.clear(); //清空q
q.empty(); //判断q是否为空,若是返回1,否则返回0
q.size(); //返回q中元素的个数
q.find(x); //在q中查找x,返回x的迭代器,若x不存在,则返回指向q尾部的迭代器即 q.end()
q.lower_bound(x); //返回一个迭代器,指向第一个键值不小于x的元素
q.upper_bound(x); //返回一个迭代器,指向第一个键值大于x的元素
q.rend(); //返回第一个元素的的前一个元素迭代器
q.begin(); //返回指向q中第一个元素的迭代器
q.end(); //返回指向q最后一个元素下一个位置的迭代器
q.rbegin(); //返回最后一个元素
set单元素应用
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> q; //默认按升序排列
q.insert(5);
q.insert(5);
q.insert(5);
cout<<"q.size "<<q.size()<<endl; //输出 1 ,在set插入中相同元素只会存在一个
q.clear(); //清空set
cout<<"q.size "<<q.size()<<"\n\n";
q.insert(4);
q.insert(4);
q.insert(3);
q.insert(3);
q.insert(2);
q.insert(1);
cout<<"lower_bound "<<*q.lower_bound(3)<<endl; //返回3
cout<<"upper_bound "<<*q.upper_bound(3)<<"\n\n"; //返回4
set<int>::iterator i;
for( i=q.begin();i!=q.end();i++) //set的遍历
cout<<*i<<" "; //输出1 2 3 4,可见自动按键值排序
cout<<endl;
q.erase(4); //删除q中的 4
for(i=q.begin();i!=q.end();i++) //再次遍历set 只输出 1 2 3
cout<<*i<<" ";
cout<<"\n\n";
set<int,greater<int>> p; //降序排列
p.insert(1);
p.insert(2);
p.insert(3);
p.insert(4);
p.insert(5);
for(i=p.begin();i!=p.end();i++)
cout<<*i<<" ";
cout<<endl;
return 0;
}
set多元素应用(结构体)
#include<iostream>
#include<set>
using namespace std;
struct node{
int a,b;
bool operator< (const node W)const
{
return a>W.a; //按a的值升序
}
}t;
int main()
{
set<node> q;
t.a=1;
t.b=2;
q.insert(t);
t.a=4;
t.b=2;
q.insert(t);
t.a=3;
t.b=5;
q.insert(t);
set<node>::iterator i;
for(i=q.begin();i!=q.end();i++)
{
t=*i;
cout<<t.a<<" "<<t.b<<endl;
}
return 0;
}
emsp;此外 STL还提供了unordered_set , unordered_set基于哈希表,数据插入和查找的时间复杂度很低,几乎是常数时间,而代价是消耗比较多的内存,无自动排序功能。底层实现上,使用一个下标范围比较大的数组来存储元素,形成很多的桶,利用hash函数对key进行映射到不同区域进行保存。
set与unordered相比:
1、set比unordered_set使用更少的内存来存储相同数量的元素。
2、对于少量的元素,在set中查找可能比在unordered_set中查找更快。
3、尽管许多操作在unordered_set的平均情况下更快,但通常需要保证set在最坏情况下有更好的复杂度(例如insert)。
4、如果您想按顺序访问元素,那么set对元素进行排序的功能是很有用的。
5、您可以用<、<=、>和>=从字典顺序上比较不同的set集。unordered_set集则不支持这些操作。
一般来说,在如下情况,适合使用set:
1、我们需要有序的数据(不同元素)。
2、我们必须打印/访问数据(按排序顺序)。
3、我们需要知道元素的前任/继承者。
一般来说,在如下情况,适合使用unordered_set:
1、我们需要保留一组元素,不需要排序。
2、我们需要单元素访问,即不需要遍历。
3、仅仅只是插入、删除、查找的话。
2.2 map
现在有两类容器:map和unordered_map,对于多数应用到哈希表查找问题,采用unordered_map效率会高一些。下面对这二者的区别和联系做一个介绍。map的底层用的是红黑树,其是有序的,unordered_map的底层是哈希表,是无序的,整体而言map占用的内存更多但是查找的效率较低,而unordered_map占用的内存更高,但是由于底层是哈希表其查找的效率会高不少。
二、算法
sort(arr.begin(),arr.end(),greater()) // 降序排列
sort(arr.begin(),arr.end(),less()) // 升序排列
其他的百度即可,感觉用的不多,堆以及堆排的相关函数前面介绍了。
三、适配器
待更新!!!
参考文献
[1]. 侯捷,STL源码解析
[2] https://blog.youkuaiyun.com/u013317445/article/details/89680330
[3] https://blog.youkuaiyun.com/liitdar/article/details/80498634