c++学习 STL
注:进阶版见深入学习c++ c++标准库体系结构与内核分析笔记
STL(standard template library)即标准模板库,顾名思义,是基于模板实现的。
他被内建在编译器中,无需下载即可使用。
广义上分为:容器(container)、算法(algorithm)、迭代器(iterator)。
思想:数据与算法分离,通过迭代器联系。
拥有以下特点:
1、高可重用性
2、高性能
3、高移植性
4、跨平台
容器
序列式容器:
容器中元素的位置是由元素进入的顺序决定的。
关联式容器:
其他排位规则的容器。
迭代器
类似于指针,默认指向容器中第一个元素,对指针的操作几乎都适用于迭代器。
但实际上迭代器是一个类对象,它封装了一个指针,并重载了一些操作符。
迭代器类型:
vector<T>::iterator
算法
算法可以通过迭代器对容器的元素进行操作。
-------------------------------------我是分隔符---------------------------------------------
目录
-------------------------------------我是分隔符---------------------------------------------
string容器
char是一个指针,string是一个类。
string封装了char,管理这个字符串,是一个char*型的容器。
初始化
string str1;
string str2("hello");
str1 = "hello";
str1 = str2;
str1 = 'h';
取单个字符
str1[1];
str1.at(1);
区别:当访问越界时,[]方式直接程序挂掉,而at方式会抛出异常out_of_range。
常用api
拼接:
查找和替换:
比较:
取子串:
插入和删除:
-------------------------------------我是分隔符---------------------------------------------
vector容器
vector是一个动态数组或可变数组。
动态原理:
当vector的容量不足时,系统会开辟一块更大的新空间,将原数据拷贝进
来,加入新数据,之后将原空间释放。
初始化:(包含<vector>
)
初始化例子:
int array[2]= { 1,2 };
vector<int> a(array,array+sizeof(array)/sizeof(int));
vector<int> b(a.begin(),a.end());
vector<int> c(3,4);
vector<int> d(c);
操作基础:
myvector.begin(); //迭代器,指向第一个元素
myvector.end(); //迭代器,指向最后一个元素的后一个位置
myvector.rbegin(); //迭代器,指向倒数第一个元素
myvector.rend(); //迭代器,指向第一个元素的前一个位置
赋值操作:
大小操作:
注:容量>=元素个数
存取操作:
注:取容器的首地址应该是&(myvector[0])
,而不能是myvector
,因为myvector
只是一个类名,它内部维护的一个指针才是指向元素内存空间的。
插入和删除:
注:
1、vector是一个单向容器,适用于频繁处理末尾元素的情况,使用insert时效率将十分低下。
2、添加元素时vector会自动增长容量,但是在删除元素时,vector不会自动缩减容量(resize也不会)。
所以可以使用swap来收缩空间:
vector<t>(v).swap(v);
//v即要收缩的容器
//vector<t>(v)是拷贝v的匿名对象
//交换后,语句结束,匿名对象(此时已经成为了原来的v)被释放
-------------------------------------我是分隔符---------------------------------------------
deque容器
与vector不同的是,deque是一个双向容器。
增加了在对首的存取(push_front和pop_front)。
他双端插入和删除元素效率较高,但指定位置插入也会导致数据元素移动,效率低下。
原理:
就是说,deque将数据储存为多块,需要时开辟一块,不需要后收回一块,所以我们不用考虑容器容量远大于元素数量的情况(收缩空间)。
也由于这种原理,对于deque的排序效率将十分低下,常常将deque中的数据拷贝到vector中,排序后再送回。
构造:
赋值:
大小操作:
插入删除:
数据存取:
-------------------------------------我是分隔符---------------------------------------------
stack容器
stack容器即栈容器,基本规则:先进后出
注:
1、不提供迭代器
2、不能遍历
3、不能随机存取
构造:
赋值:
存取:
大小操作:
-------------------------------------我是分隔符---------------------------------------------
queue容器
queue容器即队列容器,基本规则:先进先出
支持在一端(队尾)插入和在一端(队首)删除。
注:
1、不提供迭代器
2、不能遍历
3、不能随机存取
构造:
存取、插入和删除:
赋值:
大小操作:
-------------------------------------我是分隔符---------------------------------------------
list容器
list容器即链表容器。
特点:
1、非连续存储,因此插入和删除的效率都高。
2、无需考虑收缩内存。
3、不支持随机存取,只能通过首地址++。
构造:
插入和删除:
大小操作:
赋值:
存取:
反转与排序:
注:
对于sort,它是list内置的函数,而不是一个算法。
算法中的sort只支持可随机存取的容器。
若list中存放的是基本数据类型,则sort默认按照从小到大排序。
若想从大到小排序,需:
bool myCompare(int v1,int v2){
return v1 > v2;
}
myList.sort(myCompare);
若list中存放的是类,则:
bool myCompare(const Student& a, const Student& b){
return a.Average > b.Average;
}
myList.sort(myCompare);
//-----------------------------------------------------------
//或者使用
myList.sort(lambda表达式);
-------------------------------------我是分隔符---------------------------------------------
priority_queue容器
https://blog.csdn.net/weixin_52115456/article/details/127606811
set和multiset容器
set和multiset是关联式容器,它所有元素都会根据元素的值自动排序。
set和multiset以红黑树(RB-tree,平衡二叉树的一种)为底层机制,查找效率非常好。
注:
1、set不允许元素重复,multiset允许。
2、set和multiset提供迭代器,但是不能通过迭代器修改数据的值。
3、set和multiset包含<set>
即可。
构造:
赋值:
大小操作:
插入和删除:
注:
insert的默认顺序是从小到大,要想编为从大到小,就要用到:
仿函数
class mycompare
{
public:
bool operator()(int v1,int v2)
{
return v1 > v2;
}
};
将这个仿函数类作为set模板的第二个参数,就可以改变排序顺序:
set<int,mycompare> myset;
查找:
注:
equal_range返回lower_bound和upper_bound两个值,是通过返回pair模板类来实现的。
pair
对组(pair)将一对值合成一个值,这两个值可以类型不同,通过pair.first和pair.second来访问。
使用案例:
专家评分,去最低和最高,并排出名次。
-------------------------------------我是分隔符---------------------------------------------
map和multimap容器
map与set的区别在于map有键值(key)和实值(value),而set不分键值和实值。
map中存放pair(pair见set和multiset容器末尾),其中pair的first是键值,second是实值。
map也是以红黑树为底层实现机制。
map按key值排序,故map中key值不能重复,而multimap可以。
map不允许通过迭代器改变其键值,但是允许通过迭代器改变其实值。
include <map>
构造:
赋值:
大小操作:
插入:
注:
前三种插入方式会返回一个pair<iterator,bool>,可以得到插入位置的迭代器和插入是否成功的结果。且当插入元素的键值已存在时,会插入失败。
而第四种方式,[]里的是key值,当key值不存在时,会插入这个新的元素,当key值存在时会修改其value。
输出时可以:
cout<<mymap[10]<<endl;
但是如果cout的是一个不存在的键值,则会插入这个新元素并默认初始化。
使用案例:
招聘员工时,员工信息分组,使用multimap每个组号是一个键值。
-------------------------------------我是分隔符---------------------------------------------
一些问题
1、STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是元素通过浅拷贝装进容器,所以当我们要把一个含有指针成员的类装进容器时,一定要实现它的深拷贝构造。
另外,建议在一个类中实现拷贝构造时,同时也实现=操作符的重载。(便于他人的使用)
2、通常STL不会抛出异常,需要使用者传入正确参数。
适用场景(仅供参考)
vector的使用场景: 比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
deque的使用场景: 比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
list的使用场景︰ 比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set的使用场景: 比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map的使用场景: 比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
查找算法
如果查找目标为aim。
find
vector<T>::iterator ret = find(myvector.begin(),myvector.end(),aim);
//find返回迭代器
if(ret == myvector.end()){
cout<<"未找到"<<endl;
}
else{
cout<<"找到了"<<endl;
}
find_if
与find不同的是,find_if的第三个参数是一个回调函数(可以自己写),就是说find_if遍历时将每个元素传入回调函数中,如果回调函数返回true,则就是找到了。
binary_search
二分法查找,使用前提是容器元素有序,不返回迭代器。
bool ret = binary_search(myvector.begin(),myvector.end(),aim);
//找到了返回true。
adjacent_find
查找相邻重复元素,使用前提是容器元素有序,返回相邻重复元素第一个位置的迭代器。
vector<T>::iterator ret = adjacent_find(myvector.begin(), myvector.end());
count
查找某元素出现的个数,并返回。
int num = count(myvector.begin(),myvector.end(),aim);
count_if
与find_if同理。
遍历算法
#include<algorithm>
for_each
按照回调函数(_callback)的功能进行遍历。
for_each(myvector.begin(),myvector.end(),_callback);
transform
将指定容器(myvector)区间元素搬运到另一容器(myvector2)中。
注意: transform不会给目标容器分配内存,所以需要我们提前分配好内存。
transform(myvector.begin(),myvector.end(),myvector2.begin(),_callback);
myvector区间元素经过回调函数处理后的返回值送入myvector2。