微软STL官方开发文档https://docs.microsoft.com/zh-cn/cpp/dotnet/stl-clr-library-reference?view=msvc-170
一、stl介绍与6大模块介绍
- stl的介绍:
- stl就是(standard template library)的简称,定义在std命名空间中,定义了C++常用的容器与算法等。
- 泛型编程的概念:用模板进行编程,可以实现一些其它方式难以实现的功能,但对于新手来说,泛型编程可能会难以理解,摸不着头脑。
也就是说,模板是学习泛型编程的基础。
注意:泛型编程不属于面向对象编程的范畴,泛型编程和面向对象编程是并列的。 - stl作为泛型编程的最典型代表,它实现了其它编程方式难以实现的效果,比如将整个模板库分为六个部分,每个部分可以单独设计。举个最简单的例子,vector和map在数据结构方面完全不一样,但stl可以设计出“迭代器”这个模块,让该模块可以在不同的数据结构中按照同样的方式运行。这种技术没有泛型编程是难以实现的。
- 学习stl的注意事项
- 学习stl一定要有全局观念,不要局限于单个容器,重点在于明白六大组件之间的联系。
- 当然,如果只是单纯为了应付当前的业务,单独学一下某个容器的用法也没有问题。
- SLT的六大容器介绍:
- 容器(container):是一种数据结构,也就是真正用来存储数据的地方。分为三类
- 顺序式容器:
- 关联式容器:
- 无序式容器:其实无序式容器也是一种关联式容器,但是既然C++标准委员会将无序容器与关联式容器平行的列了出来,那么我们这里也就让无序式容器和关联式容器平行吧。
- 迭代器(iterator):提供了可以访问任何容器的方法。
- 算法(alogorithm):用来操作容器中的数据的模板函数。
- 仿函数(functor)
- 适配器(adaptor)
- 分配器(allocator)
二、容器
1. 顺序容器
顺序容器(sequence container):每个元素都有固定的位置,位置取决于插入时间和地点,与元素的值无关
- vector:将元素置于一个动态数组中,可以随机存储元素(也就是用索引直接存取)。
数组尾部添加或删除元素非常迅速。但在中部或头部就比较费时。
- deque:“double end queue”的缩写,也就是双端队列。deque的实现相比于vector有些复杂,但本质仍然是优化过的动态数组,只不过相比于单纯的动态数组,在前面添加或删除元素非常快了。
- 可以随机存储元素。头部和尾部添加或删除元素都非常快(略慢与vector)。但在 中间插入元素比较费时(和vector差不多)。
#include <iostream> |
- list:本质就是链表,所以自然具有了链表的属性。
- 不能随机存取元素(也就是list无法用索引存取元素)。在任何位置插入和删除元 素都比较迅速。(在任何位置插入删除元素的时间相同,在元素头部操作慢于deque,在元素尾部操作慢于deque和vector)
#include <iostream> |
string:没什么好说的,就是把普通字符串封装了一下
#include <iostream>
#include <vector>
#include <deque>
#include <set>
#include <algorithm>
#include<list>
int main()
{
const char *str="hello world";
std::string st1("1111");
return 0;
}forward_list:单项链表,简单来说就是受限的list,凡是list不支持的功能,它都不支持。做各种支持的操作效率都会高于list,最典型的就排序算法了,forword_list要优于list。
- ForwordList 只提供前向迭代器,而不是双向迭代器。因此它也不支持反向迭代器。
- ForwordList不提供成员函数 size()。
- ForwordList 没有指向最末元素的锚点。基于这个原因,不提供用以处理最末元素的成员 back(),push_back(),pop_back()。
2. 关联容器
关联容器(associated container):元素位置取决于元素的值,和插入顺序无关。
- set/multiset:使用“红黑树”实现,是一种高度平衡的二叉树,二叉树的本质决定了set/multiset的元素存取值取决于元素本身的值,和插入顺序无关。
内部元素的值依据元素的值自动排列,与插入顺序无关。set内部相同数值的元素只能出现一次,multiset内部相同数值的元素可出现多次。容器用二叉树实现,便于查找。
#include <iostream> |
- map/multimap:使用“红黑树”实现,是一种高度平衡的二叉树。
内部元素是成对的“key/value”,也就是“键值/实值”,内部元素依据其键值自动排序,map内部相同的键值只能出现一次,multimap则可以出现多次。
#include <iostream> |
3. 无序式容器(unordered container)
(1) unordered_map/unordered_multimap:使用“哈希表”实现的,由于哈希表的特性,实现了真正的无序。如果不理解为什么使用“哈希表”就是真正无序的,可以去百度一下“哈希表”,或者干脆直接记住就可以了。
使用方法也是“key/value”,和map/multimap类似。
(2) unordered_set/unorder_multiset:同样使用“哈希表”实现的。自然具有了哈希表实现的容器的特点。
使用方法和setl/multiset类似。
4. 关联式容器和无序式容器的对比:
- 关联式容器都是有序的,对于那些对顺序有要求的操作,关联式容器效率会高很多。(比如增加元素,删除元素)
- 无序容器都是真正的无序,在查找数据方面有着优势。(比如修改特定元素,查找元素)
- 从内存消耗的角度讲,无序容器要高于关联容器不过这并不重要。
一句话来说,如果从这两类容器中选一个使用的话。如果是增加,删除元素比较频繁,就使用关联式容器。如果修改元素,查找元素比较平凡,就使用无序容器。
5. 我们在处理数据时应该选择什么容器呢?
- 在我们需要使用存储“key/value”的容器时,只能使用map/multimap/unoredered_map/unordered_multimap。如果增加删除频繁,就使用map/multimap,修改,查找频繁,就使用unordered_map/unoredered_multimap。
在真正的大型项目中,常常会对这两种容器进行测试,普通练习靠感觉就可以了
在处理普通元素:
当元素需要频繁插入删除时,选择顺序容器。
- 如果在尾部插入删除,选择vector
- 在头部,尾部插入删除,选择deque
- 在中间插入,删除,选择list
当元素需要频繁查找时,选择.set/multiset/unorder_set/unorder_multiset。
- 频繁增加,删除时,选set,
- 频繁查找,修改时,选ordered_set
我们发现,对于普通元素,容器的选择不怎么容易判断。
其实在真正的大型项目中,要对各种容器进行测试的,普通练习一般选择vector或set就可以了。这两个使用是比较频繁的,
三、迭代器
迭代器介绍:迭代器提供了一种可以顺序访问容器各个元素的方法,可以让我们无视不同容器存储方式的不同,用同一的方式访问数据。经过前面对容器的学习,相信大家已经体会到这一点了。
迭代器的作用:能够让迭代器与容器,算法在设计,使用时互不干扰,又能无缝耦合起来。使用迭代器可以灵活操作各种容器算法,而不需要考虑不同容器间的差异。
四、算法
- stl的算法可以分为九个种类,具体有什么已经在“附录一”中完全列举了。
- 查找算法:
- 排序算法:
- 删除和替换算法:
- 排列组合算法:
- 算数算法:
- 生成和异变算法:
- 关系算法:
- 集合算法:
- 堆算法:
- 查找算法(13个):判断容器中是否包含某个值
- adjacent_find:
在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素forwardIterator。否则返回最后一个元素的forwardIterator。
#include <iostream> |
- binary_search:
在有序序列中查找value,找到返回true。重载的版本实用指定的比较函数对象或函数指针来判断相等。
#include <iostream> |
- count:
利用等于操作符,把标志范围内的元素与输入值比较,返回相等元素个数。 - count_if:
利用输入的操作符,对标志范围内的元素进行操作,返回结果为true的个数。
#include <iostream> |
- equal_range:
注意,必须对有序容器进查找,下面的lower_bound和upper_bound也是同理。
功能类似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。 - find利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回该元素的一个InputIterator。
find_end:
在指定范围内查找”由输入的另外一对iterator标志的第二个序列”的最后一次出现。找到则返回最后一对的第一个迭代器,否则返回输入的”另外一对”的第一个迭代器。重载版本使用用户输入的操作符代替等于操作。#include <iostream>
#include <vector>
#include <deque>
#include <set>
#include <algorithm>
#include <list>
#include <map>
#include <stack>
#include <queue>
int main()
{
std::vector<int> ivec1{4, 5};
std::vector<int> ivec{1, 2, 3, 4, 5, 56, 6, 2, 1, 3, 4, 5, 6, 6};
auto iter = std::find_end(ivec.cbegin(), ivec.cend(), ivec1.cbegin(), ivec1.cend());
std::cout << std::distance(ivec.cbegin(), iter)
<< std::endl;
return 0;
}find_first_of:
在指定范围内查找”由输入的另外一对iterator标志的第二个序列”中任意一个元素的第一次出现。重载版本中使用了用户自定义操作符。- find_if:
使用输入的函数代替等于操作符执行find。
#include <iostream> |
- lower_bound:
返回一个iterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置。重载函数使用自定义比较操作。 - upper_bound:
返回一个iterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志一个大于value的值。重载函数使用自定义比较操作。 - search:
这两个是真的不好描述,去微软官网查看一下吧,简单,比我在这里总结的强多了。 - search_n:
- 排序和通用算法(14个):提供元素排序策略
inplace_merge:
merge:
nth_element:
partial_sort:
partial_sort_copy:
partition:
random_shuffle:
reverse:
reverse_copy:
rotate:
rotate_copy:
sort:
stable_sort:
stable_partition:
#include <iostream> |
- 删除算法(15个)
copy:
copy_backward:
iter_swap:
remove:
remove_copy:
remove_if:
remove_copy_if:
replace:
replace_copy:
replace_if:
replace_copy_if:
swap:
swap_range:
unique:
unique_copy:
#include <iostream> |
- 排列组合算法(2个):提供计算给定集合按一定顺序的所有可能排列组合
next_permutation:
prev_permutation: - 算术算法(4个)
accumulate:
partial_sum:
inner_product:
adjacent_difference: - 生成和异变算法(6个)
fill:
fill_n:
for_each:
generate:
generate_n:
transform:
#include <iostream> |
- 关系算法(8个)
equal:
includes:
lexicographical_compare:
max:
max_element:
min:
min_element:
mismatch: - 集合算法(4个)
set_union:
set_intersection:
set_difference:
set_symmetric_difference: - 堆算法(4个)
make_heap:
pop_heap:
push_heap:
sort_heap:
五、仿函数
- 仿函数定义:就是一个可以调用“()”运算符的类对象,将operator()重载的类的对象就是仿函数。
简单来说,就是我们在用算法时最后一个参数需要一个可调用对象,stl本身已经帮我们定义了很多可调用对象,不用我们自己再去定义了。
#include <iostream> |
六、适配器与分配器
什么是容器适配器:“适配器是使一种事物的行为类似于另外一种事物行为的一种机制”。适配器对容器进行包装,使其表现出另外一种行为。例如:stack
实现了栈的功能,内部默认使用deque 容器来存储数据。 STL的适配器有哪些:标准库提供了三种顺序容器适配器,没有关联型容器的适配器。分别是queue(队列),priority_queue(优先级队列),stack(栈)。
- 适配器的使用:
- 要使用适配器,首先需要引入对应的头文件
- 要使用stack, 需要#include
- 要使用queue或priority_queue, 需要#include
- 要使用stack, 需要#include
- 适配器的初始化:
- 普通的初始化方式: stack
stk。 - 覆盖默认容器类型的初始化方式: stack<int, vector
> stk
- 普通的初始化方式: stack
#include <iostream> |
- 分配器提一下就可以了。在分配动态内存时,直接使用new,delete容易产生内存碎片化的问题,不同的分配器有不同的分配内存的方法,可以大幅提高程序对堆内存的使用效率,我们直接使用默认的分配器就可以了
附录: STL各种容器的操作
1. vector的各种函数
1.构造函数
- vector():创建一个空的vector
- vector(const std::allocator
& al):使用指定的分配器来分配内存。allocator就是一个内存分配器,vector已经指定了默认的分配器了,不需要我们去主动调用,以后设计allocator直接忽略就可以了,其实这个构造函数只不过是用指定的分配器去创建一个空的vector罢了。 - vector(std::vector
&& right, const std::allocator & al):就是移动构造函数,第二个参数表示我们指定分配器。 - vector(const std::vector
& vec, const std::alloctor & al):就是复制构造函数,分配器可以自己指定,当然,一般来说,vector默认的分配器就够用了。 - vector(std::initializer_list
& initList, const std::allocator & al):就是使用initializer_list来初 始化容器,第二个参数表示我们可以指定分配器。 - vector(iter first, iter last, const std::allocator
& al):就是容器初始有迭代器[first, last)的内容(这里使用deque,list的迭代器也可以),第三个参数还是表示我们可以指定分配器。 - vector(const size_t count, const std::alloctor
& al):创建一个vector,元素个数为count。元素均为默认值,如果是普通类型,则赋值为0。如果是类类型,则均使用默认构造函数进行初始化。 - vector(const size_t count,const T& t):创建一个vector,元素个数为count,且值均为t。
#include <iostream> |
2. 增加函数
- void push_back(const T& value):向容器尾部增加一个元素value。
- void push_back(T&& value):向容器尾部增加一个元素value,这不过这次以右值引用的形式添加。
- std::vector
::iterator insert(std::vector ::const_iterator& where, std::initializer_list initList):在where迭代器指定的地方添加initList,返回值为指向新添加的第一个元素的迭代器,insert函数虽然有很多重载,但返回值是完全类似的,所以接下来insert函数的返回值就不介绍了。 - std::vector
::iterator insert(std::vector ::const_iterator& where, iter first, iter last):将迭代器[first, last)添加到迭代器where指定的位置。 - std::vector
::iterator insert(std::vector ::const_iterator& where, size_t count, const int& value):在where处插入count个value。 - std::vector
::iterator insert(std::vector ::const_iterator& where, const T& value):在where处插入value。 - std::vector
::iterator insert(std::vector ::const_iterator& where, T&& value):在where处插入value,只不过这次以右值引用的形式插入了。
#include <iostream> |
3. 删除函数
- std::vector
::iterator erase(std::vector ::const_iterator where):删除容器迭代器指向的元素。返回指向被删除元素后面的那个元素的迭代器。 - iterator erase(iterator first, iterator last):删除容器中[first, last)中的元素。返回指向被删除元素后面的那个元素的迭代器。
- void pop_back():删除容器中最后一个元素。
- clear():删除容器中所有元素。
4. 遍历函数
- T& at(const size_t pos):返回pos位置元素的引用。
- const T& at(const size_t pos) const:at函数的常量版本。
- T& front():返回首元素的引用。
- const T& front() const:front函数的常量版本。
- T& back():返回尾元素的引用。
- const T& back() const:back函数的常量版本。
- std::vector
::iterator begin():返回指向容器第一个元素的迭代器。 - std::vector
::const_iterator begin() const:begin函数的常量版本。 - std::vector
::const_iterator cbegin() const:可以主动调用的begin函数的常量版本。 - std::vector
::iterator end():返回指向容器最后一个元素的下一个元数的迭代器。
end()函数也有两个常量版本,和begin类似,就不写了。 - std::vector
::reverse_iterator rbegin():反向迭代器,指向最后一个元素。
同样有两个常量版本。 - reverse_iterator rend():反向迭代器,指向第一个元素之前的元素。
同样有两个常量版本。
#include <iostream> |
5. 判断函数
- bool empty() const:判断容器是否为空,若未空,则返回true,否则返回false。
#include <iostream> |
6. 大小函数
- size_t size() const:返回当前容器中元素的个数。
- size_t capacity() const:返回当前容器不扩张所能容纳的最大元素数量。
- size_t max_size() const:返回当前机器可以存储的元素数量最大值。
#include <iostream> |
7. 其它函数
- void swap(std::vector
& vec):交换两个同类型容器的的数据。 - void assign(int n, const T& x):将容器设置为n个x。
- void assign(const_iterator first, const iterator last):将当前容器的元素设置为[first, last)。
first,last都是迭代器,可以不是vector类型的迭代器,deque,list类型也可以。 - void assign(const std::initialize_list
initList):将容器元素设置为initialize_list的元素。 - void resize(size_t newSize):将容器的容量设置为newSIze。
#include <iostream> |
2. deque的各种函数:
deque的各种函数与vector类似,我就不再重复一遍了。
这里只介绍vector不同的地方:
deque支持在容器前面插入删除,操作。也就是支持以下的三个函数
- void push_front(const T& value);
- void push_front(T&& value);
- void pop_front();
3. list的各种函数
list和deque类似,只讲一下和deque不同的部分。list都用支持在前面,后面增加,删除。
list和deque在函数上的唯一区别就是不支持随机缩影,也就是不支持at函数。
4. string的各种函数
string虽然也是顺序容器,但因为本质是对字符串的封装,所以和其它容器在用法上有较大区别。
- 获取封装字符串的函数。
- const char* c_str() const:返回string对象内部的函数的指针。注意,c_str()函数返回的直接就是string对象内部的指针,也就是说string对象指向的对象发生了改变,返回的对象也会发生改变的。
- const char* data()const:返回string对象内部的函数指针。和c_str()函数的区别就是返回的字符串后面没有’\0’。
- size_t copy(char* const ptr, size_t count, const size_t off) const:
讲string对象的一部分复制到ptr数组中。
ptr表示复制到哪个数组。
count表示复制string对象的几个字符
off表示从string的哪个字符开始复制。
- 字符串比较函数。
compare函数:这个函数重载比较多,用的时候在vs中查看一下就可以了。可以用string对象的任意部分与另一个字符串进行比较,
其它函数就和vector类似了,同样支持随机选取,支持容器末尾插入。
#include <iostream> |
5. forward_list:
和list差不多,只不过是没有size()函数,没有push_back和pop_back函数。