STL各组件应用示例
STL六大组件
STL六大组件包括容器(container)、分配器(allocator)、算法(algorithm)、迭代器(iterator)、适配器(adapter)和仿函数(functor).
STL中的区间遵循前闭后开的表示方式,迭代器begin
指向的是第一个元素的起点,end
指向的是最后一个元素的下一个元素.
容器
STL中的容器大体分为序列容器、关联容器和无序容器.
下面测试程序演示STL中各容器的使用,创建辅助函数如下:
1 | const long ASIZE = 500000L; // 数组大小 |
使用array
C++11中将数组抽象成了一个模板类array
.
1 |
|
输出如下:1
2
3
4
5
6
7
8
9test_array()..........
milli-seconds : 13
array.size()= 500000
array.front()= 28060
array.back()= 25634
array.data()= 0x6a7910
target (0~32767):20000
qsort()+bsearch(), milli-seconds : 88
found, 20000
使用vector
1 |
|
输出如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18how many elements:1000000
test_vector()..........
milli-seconds : 211
vector.max_size()= 576460752303423487
vector.size()= 1000000
vector.front()= 30271
vector.back()= 7756
vector.data()= 0x4830040
vector.capacity()= 1048576
target (0~32767):23456
std::find(), milli-seconds : 1
found, 23456
sort(), milli-seconds : 2695
bsearch(), milli-seconds : 1
found, 23456
vector
底层是一段连续的内存空间,当容器满时进行扩容,将容器大小扩容为原来的两倍.
使用list
1 |
|
输出如下:1
2
3
4
5
6
7
8
9
10
11
12how many elements:1000000
test_list()..........
milli-seconds : 406
list.size()= 1000000
list.max_size()= 384307168202282325
list.front()= 31411
list.back()= 7939
target (0~32767):23456
std::find(), milli-seconds : 4
found, 23456
c.sort(), milli-seconds : 3610
程序第44行调用的是list
类的成员函数sort
,而非标准库中的算法sort
.这是因为list
类本身具有sort
方法,容器本身实现的sort
的性能一般比标准库中的算法sort
更好.
使用forward_list
forward_list
是C++11标准引入的,其前身是gcc中的slist
.
1 |
|
输出如下:1
2
3
4
5
6
7
8
9
10how many elements:1000000
test_forward_list()..........
milli-seconds : 296
forward_list.max_size()= 461168601842738790
forward_list.front()= 11513
target (0~32767):23456
std::find(), milli-seconds : 9
found, 23456
c.sort(), milli-seconds : 3706
使用deque
deque
容器可以再双端插入和删除,其底层是分段连续的,对于使用者来说造成了一种连续的假象.
1 |
|
输出如下:
1 | how many elements:1000000 |
vector
容器满时就扩充一个buffer.
使用stack
和queue
stack
和queue
底层是通过deque
实现的,从设计模式上来说,这两种容器本质上是deque
的适配器.
stack | queue |
---|---|
这两个容器的元素进出是有严格顺序的,因此stack
和queue
不支持有关迭代器的操作.
1 |
|
输出如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20how many elements:300000
test_stack()..........
milli-seconds : 57
stack.size()= 300000
stack.top()= 17153
stack.size()= 299999
stack.top()= 31703
how many elements:300000
test_queue()..........
milli-seconds : 54
queue.size()= 300000
queue.front()= 6608
queue.back()= 29870
queue.size()= 299999
queue.front()= 7837
queue.back()= 29870
使用multiset
和multimap
multiset
和multimap
底层是使用红黑树实现的.
1 |
|
因为multimap
支持重复的key,因此不能使用重载的[]
运算符进行插入.输出如下:
1 | how many elements:1000000 |
使用unordered_multiset
和unordered_multimap
unordered_multiset
和unordered_multimap
底层是使用hash+链表实现的.
unordered_multiset
和unordered_multimap
的元素个数小于篮子数目,若元素数目达到篮子个数,则容器扩容,将篮子数组扩充约一倍.
1 |
|
输出如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36how many elements:1000000
test_unordered_multiset()..........
milli-seconds : 1476
unordered_multiset.size()= 1000000
unordered_multiset.max_size()= 384307168202282325
unordered_multiset.bucket_count()= 1832561
unordered_multiset.load_factor()= 0.545684
unordered_multiset.max_load_factor()= 1
unordered_multiset.max_bucket_count()= 384307168202282325
bucket #0 has 0 elements.
bucket #1 has 0 elements.
bucket #2 has 0 elements.
bucket #3 has 0 elements.
bucket #4 has 0 elements.
bucket #5 has 0 elements.
bucket #6 has 0 elements.
bucket #7 has 34 elements.
bucket #8 has 0 elements.
bucket #9 has 0 elements.
target (0~32767):23456
std::find(), milli-seconds : 104
found, 23456
c.find(), milli-seconds : 1
found, 23456
how many elements:1000000
test_unordered_multimap()..........
milli-seconds : 1051
unordered_multimap.size()= 1000000
unordered_multimap.max_size()= 384307168202282325
target (0~32767):23456
c.find(), milli-seconds : 0
found, value=20464
分配器
STL容器默认的分配器是std::allocator
,除此之外gcc额外定义了几个分配器,其头文件均在目录ext
下.
gcc额外定义的分配器均位于__gnu_cxx
命名空间下.分配器一般用于构建容器,不会直接使用.因为分配器想要直接使用也不好用(使用free
关键字时不需要指定回收内存的大小,而分配器的deallocate
函数需要指定回收内存大小).
1 |
|
STL容器源码分析
STL设计模式:OOP和GP
OOP(Object-Oriented Programming)和GP(Generic Programming)是STL容器设计中使用的两种设计模式.
- OOP的目的是将数据和方法绑定在一起,例如对
std::list
容器进行排序要调用std::list::sort
方法. - GP的目的是将数据和方法分离开来,例如对
std::vector
容器进行排序要调用std::sort
方法.
这种不同是因为std::sort
方法内部调用了iterator
的-
运算,std::list
的iterator
没有实现-
运算符,而std::vector
的iterator
实现了-
运算符.
1 | template<typename _RandomAccessIterator> |
运算符重载与模板特化
实现STL的两大基础就是运算符重载和模板特化.
分配器
VC6.0的默认分配器std::allocator
定义如下,可以看到VC6.0的分配器只是对::operator new
和::operator delete
的简单封装.
1 | template<class _Ty> |
gcc2.9中的分配器std::allocator
与VC6.0的实现类似,但std::allocator
并非gcc2.9的默认分配器,观察容器源码,可以看到,gcc2.9的默认分配器为std::alloc
.
1 | template<class T, class Alloc = alloc> |
std::alloc
的代码结构如下:
1 | class alloc { |
std::alloc
内部维护一个链表数组,数组中的每个链表保存某个尺寸的对象,减少了调用malloc
的次数,从而减小了malloc
带来的额外开销.
在gcc4.9以后,默认分配器变为std::allocator
,变回了对::operator new
和::operator delete
的简单封装.gcc2.9中的std::alloc
更名为__gnu_cxx::__pool_alloc
.
容器
STL容器的各实现类关系如下图所示,以缩排形式表示衍生关系(主要是复合关系).
容器list
gcc2.9中list
及相关类的代码如下所示:
1 | template<class T, class Alloc = alloc> |
1 | template<class T> |
为实现前闭后开的特性,在环形链表末尾加入一个用以占位的空节点,并将迭代器list::end()
指向该节点.
迭代器__list_iterator
重载了指针的*
,->
,++
,--
等运算符,并定义了iterator_category
、value_type
、difference_type
、pointer
和reference
5个关联类型(associated types),这些特征将被STL算法使用.
1 | template<class T, class Ref, class Ptr> |
注意在这里前置++
运算符返回左值,而后置++
返回右值,这与基础类型的++
和--
运算一致.
1 | int i(6); |
在gcc4.9以后,list
相关的类使用了继承,增加了不必要的复杂度.
STL的算法传入的参数的一般是迭代器或指针,在算法内部,需要根据传入的迭代器或指针推断出迭代器的关联类型(associated types).
1 | template<typename _ForwardIterator> |
迭代器的5个关联类型在类中均有定义,但是指针类型的关联类型需要根据指针类别进行确定,为了使STL算法同时兼容迭代器和一般指针,就在迭代器(指针)和算法之间加一个中间层萃取器(traits).
迭代器萃取器iterator_traits
能够兼容迭代器和一般指针,获取其5个关联类型:iterator_category
、value_type
、difference_type
、pointer
和reference
.
在实现上,iterator_traits
类使用模板的偏特化,对于一般的迭代器类型,直接取迭代器内部定义的关联类型;对于指针和常量指针进行偏特化,指定关联类型的值.
1 | // 针对一般的迭代器类型,直接取迭代器内定义的关联类型 |
想要在算法内获取关联类型的值,只需像下面这样写:
1 | template<typename T> |
容器vector
容器vector
的代码如下:
1 | template<class T, class Alloc= alloc> |
容器vector
的迭代器start
指向第一个元素,迭代器finish
指向最后一个元素的下一个元素,这两个迭代器对应begin()
和end()
的返回值,维持了前闭后开的特性.
vector
对使用者是连续的,因此重载了[]
运算符.
vector
的实现也是连续的,因此使用指针类型做迭代器(即迭代器vector<T>::iterator
的实际类型是原生指针T*
).
vector::push_back
方法先判断内存空间是否满,若内存空间不满则直接插入;若内存空间满则调用insert_aux
函数先扩容两倍再插入元素.
1 | void push_back(const T &x) { |
insert_aux
被设计用于在容器任意位置插入元素,在容器内存空间不足会现将原有容器扩容.
1 | template<class T, class Alloc> |
容器array
将数组封装成容器array
是为了使之与STL算法兼容,其内部实现只是简单封装了一下数组,甚至没有构造函数和析构函数.与vector
一样使用原生指针做迭代器.
1 | template<typename _Tp, std::size_t _Nm> |
容器deque
容器deque
内部是分段连续的,对使用者表现为连续的.
1 | template<class T, class Alloc =alloc, size_t BufSiz = 0> |
deque::map
的类型为二重指针T**
,称为控制中心,其中每个元素指向一个buffer.
1 | template<class T, class Ref, class Ptr, size_t BufSiz> |
迭代器deque::iterator
的核心字段是4个指针:cur
指向当前元素、first
和last
分别指向当前buffer的开始和末尾、node
指向控制中心.
deque::insert
方法先判断插入元素在容器的前半部分还是后半部分,再将数据往比较短的那一半推.
1 | iterator insert(iterator position, const value_type &x) { |
迭代器deque::iterator
模拟空间的连续性.
1 | template<class T, class Ref, class Ptr, size_t BufSiz> |
容器queue
和stack
stack | queue |
---|---|
容器queue
和stack
作为deque
的适配器(adapter),其内部均默认封装了一个deque
作为底层容器,通过该deque
执行具体操作.
1 | template<class T, class Sequence=deque<T>> |
容器queue
和stack
的元素进出是严格有序的,因此两个容器都不允许遍历,其内部没有定义iterator
.
1 | stack<string>::iterator ite; // 不能通过编译 error: 'iterator' is not a member of 'std::stack<std::__cxx11::basic_string<char> >' |
实际上queue
和stack
的底层容器也可以指定为list
;stack
的底层容器也可以指定为vector
,这些底层容器均实现了queue
和stack
内部用到的方法.
1 | queue<int, list<int>> q1; |
实际上,若指定了错误的底层容器但没有调用不支持的方法的话,程序仍能够编译通过,这说明编译器在处理模板时不会做全面的检查.
1 | queue<int, vector<int>> q2; |
容器rbtree
容器rb_tree
封装了红黑树,是有序容器,提供了迭代器iterator用以遍历,但不应使用iterator直接改变元素值(虽然编程层面并没有禁止这样做).
rb_tree
提供两种插入操作:insert_unique
和insert_equal
,前者表示节点的key
一定在整棵树中独一无二,否则插入失败;后者表示节点的key
可重复.
对于rb_tree
,定义一个概念:节点的value
包括其key
和data
,这里的data
表示一般说法中的value
.
1 | template<class Key, // 指定key类型 |
rb_tree
的header
指向一个多余的空节点,用以维持其前闭后开的特性.
下面程序演示rb_tree
的使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16rb_tree<int, int, identity<int>, less<int>> itree;
cout << itree.empty() << endl; // 1
cout << itree.size() << endl; // 0
itree.insert_unique(3);
itree.insert_unique(8);
itree.insert_unique(5);
itree.insert_unique(9);
itree.insert_unique(13);
itree.insert_unique(5); //no effect, since using insert_unique()
cout << itree.empty() << endl; //0
cout << itree.size() << endl; //5
cout << itree.count(5) << endl; //1
itree.insert_equal(5);
itree.insert_equal(5);
cout << itree.size() << endl; //7, since using insert_equal()
cout << itree.count(5) << endl; //3
容器set
和multiset
容器set
和multiset
以rb_tree
为底层容器,因此其中元素是有序的,排序的依据是key
.set
和multiset
元素的value
和key
一致.
set
和multiset
提供迭代器iterator
用以顺序遍历容器,但无法使用iterator
改变元素值,因为set
和multiset
使用的是内部rb_tree
的const_iterator
.
set
元素的key
必須独一无二,因此其insert()
调用的是内部rb_tree
的insert_unique()
方法;multiset
元素的key
可以重复,因此其insert()
调用的是内部rb_tree
的insert_equal()
方法.
1 | template<class Key, |
set
容器的模板参数推导过程如下:
容器map
和multimap
容器map
和multimap
以rb_tree
为底层容器,因此其中元素是有序的,排序的依据是key
.
map
和multimap
提供迭代器iterator
用以顺序遍历容器.无法使用iterator
改变元素的key
,但可以用它来改变元素的data
,因为map
和multimap
内部自动将key
的类型设为const
.
map
元素的key
必須独一无二,因此其insert()
调用的是内部rb_tree
的insert_unique()
方法;multimap
元素的key
可以重复,因此其insert()
调用的是内部rb_tree
的insert_equal()
方法.
1 | template<class Key, |
map
容器的模板参数推导过程如下:
map
容器重载的[]
运算符返回对应data
的引用
1 | mapped_type& operator[](key_type&& __k) |
容器hashtable
hashtable
最开始只有53个桶,当元素个数大于桶的个数时,桶的数目扩大为最接近当前桶数两倍的质数,实际上,桶数目的增长顺序被写死在代码里:
1 | static const unsigned long __stl_prime_list[__stl_num_primes] = { |
hashtable
代码如下:
1 | template<class Value, |
下面代码演示hashtable
的使用:
1 | hashtable<pair<const string, int>, |
容器unordered_set
、unordered_multiset
、unordered_map
和unordered_multimap
C++11引入的容器unordered_set
、unordered_multiset
、unordered_map
和unordered_multimap
更名自gcc2.9的容器hash_set
、hash_multiset
、hash_map
和hash_multimap
,其底层封装了hashtable
.用法与set
、multiset
、map
和multimap
类似.
STL算法源码分析
STL算法的一般形式如下:
1 | template<typename Iterator> |
STL算法是看不到容器的,算法所需要的信息都是从迭代器取得的,因此迭代器内要存在与容器相关的信息,其中最重要的就是迭代器的5个关联类型.
迭代器对算法的影响
迭代器的iterator_category
类型
迭代器的关联类型iterator_category
表示迭代器类型,共5种,用类表示:input_iterator_tag
、output_iterator_tag
、forward_iterator_tag
、bidirectional_iterator_tag
和random_acess_iterator_tag
.
1 | struct input_iterator_tag {}; |
之所以使用类而非枚举来表示迭代器类型,是出于一下两个考虑:
- 使用类的继承可以表示不同迭代器类型的从属关系.
- STL算法可以根据传入的迭代器类型调用不同版本的重载函数.
下面的程序演示这两点.
下面程序用以演示不同容器的迭代器iterator_category
值:
1 | // 输出迭代器类型,使用函数重载以应对不同类型的迭代器 |
容器array
、vector
、deque
对使用者来说是连续空间,是可以跳跃的,其迭代器是random_access_iterator
类型.
容器list
是双向链表,容器set
、map
、multiset
、multimap
本身是有序的,他们的迭代器都可以双向移动,因此是bidirectional_iterator
类型.
容器forward_list
是单向链表,容器unordered_set
、unordered_map
、unordered_multiset
、unordered_map
哈希表中的每个桶都是单向链表.因此其迭代器只能单向移动,因此是forward_iterator
类型.
迭代器istream_iterator
和ostream_iterator
本质上是迭代器,后文会提到这两个类的源码.
iterator_traits
和type_traits
对算法的影响
STL种的大部分算法都根据传入的迭代器类型以及其他信息调用不同的重载函数,针对特定的数据结构执行特定的优化
- STL中的算法
distance
根据不同的iterator_category
执行不同的重载函数
1 | template<class InputIterator> |
- STL中的算法
advance
根据不同的iterator_category
执行不同的重载函数
1 | template<class InputIterator, class Distance> |
- STL中的算法
copy
根据不同的iterator_category
和type_traits
执行不同的重载函数
STL算法都是模板函数,无法对传入的iterator_category
类型做出限定,但源码中的模板参数名还是对接收的iterator_category
做出了一定的暗示.例如sort
算法的模板参数类型名设为RandomAccessIterator
,暗示了该算法只能接收random_access_iterator_tag
类型的迭代器.
1 | template<typename RandomAccessIterator> |
算法
C语言本身提供了一些算法,如qsort
,不属于STL算法.STL算法应该满足下面的形式:模板函数,接收参数是迭代器和某些标准.
1 | template<typename Iterator> |
算法accumulate
算法accumulate
的默认运算是+
,但是重载版本允许自定义运算,支持所有容器,源码如下:
1 | template<class InputIterator, class T> |
下面程序演示其使用:
1 |
|
算法for_each
算法for_each
支持所有容器,源码如下:
1 | template<class InputIterator, class Function> |
C++11中引入了新的range-based for语句,形式如下:
下面程序演示其使用:
1 | // 自定义函数 |
算法replace
、replace_if
、replace_copy
算法
replace
将范围内所有等于old_value
的元素都用new_value
取代.算法
replace_if
将范围内所有满足pred()
为true
的元素都用new_value
取代.算法
replace_copy
将范围内所有等于old_value
的元素都以new_value
放入新区间,不等于old_value
的元素直接放入新区间.
它们支持所有容器,源码如下:
1 | template<class ForwardIterator, class T> |
算法count
、count_if
算法
count
计算范围内等于value
的元素个数.算法
count_if
计算范围内所有满足pred()
为true
的元素个数.
它们支持所有容器,但关联型容器(set
、map
、multiset
、multimap
、unordered_set
、unordered_map
、unordered_multiset
和unordered_map
)含有更高效的count
方法,不应使用STL中的count
函数.源码如下:
1 | template<class InputIterator, class T> |
算法find
、find_if
算法
find
查找范围内第一个等于value
的元素.算法
find_if
查找范围内第一个满足pred()
为true
的元素.
它们支持所有容器,但关联型容器(set
、map
、multiset
、multimap
、unordered_set
、unordered_map
、unordered_multiset
和unordered_map
)含有更高效的find
方法,不应使用STL中的find
函数.源码如下:
1 | template<class InputIterator, class T> |
算法sort
算法sort
暗示参数为random_access_iterator_tag
类型迭代器,因此该算法只支持容器array
、vector
和deque
.
容器list
和forward_list
含有sort
方法.
容器set
、map
、multiset
、multimap
本身是有序的,容器unordered_set
、unordered_map
、unordered_multiset
和unordered_map
本身是无序的,不需要排序.
1 | template<typename RandomAccessIterator> |
下面程序演示其使用:
1 | // 自定义函数 |
上面程序中的rbegin
和rend
是迭代器适配器,生成一个逆向增长的迭代器,后文会提到这两个类的源码.
算法binary_search
算法binary_search
从排好序的区间内查找元素value
,支持所有可排序的容器.
算法binary_search
内部调用了算法lower_bound
,使用二分查找方式查询元素.
算法lower_bound
和upper_bound
分别返回对应元素的第一个和最后一个可插入位置.
1 | template<class ForwardIterator, class T> |
STL仿函数源码分析
仿函数是一类重载了()
运算符的类,其对象可当作函数来使用,常被用做STL算法的参数.
STL的所有仿函数都必须继承自基类unary_function
或binary_function
,这两个基类定义了一系列关联类型,这些关联类型可被STL适配器使用.为了扩展性,我们自己写的仿函数也应当继承自这两个基类之一.
1 | template<class Arg, class Result> |
前文程序中用到的几个仿函数的源码如下:
1 | // 算术运算类仿函数 |
前文程序中我们自定义的仿函数并没有继承自基类unary_function
或binary_function
,这样虽然能够运行,但是不好,因为没有继承自基类的仿函数无法与STL其他组件(尤其是函数适配器)结合起来.
STL适配器源码分析
容器适配器
STL中:
- 容器
stack
和queue
是容器deque
的适配器. - 容器
set
、map
、multiset
和multimap
是容器rb_tree
的适配器. - 容器
unordered_set
、unordered_multiset
、unordered_map
和unordered_multimap
是容器hashtable
的适配器.
上述源码在前文中均已分析过.
仿函数适配器
仿函数适配器binder2nd
及其辅助函数bind2nd
仿函数适配器binder2nd
可以绑定二元仿函数的第二参数,生成新的仿函数.其源码如下:
1 | // 放函数适配器binder2nd也是仿函数类,因此继承自仿函数基类unary_function |
在binder2nd
源码中,调用了Operation
类的first_argument
、second_argument_type
和result_type
,这些字段都是从STL仿函数基类binary_function
继承得到的.因此我们自己写的仿函数也要继承自基类binary_function
,才能使用适配器binder2nd
进行增强.
binder2nd
适配器增强得到的仍然是一个仿函数,因此也要继承基类unary_function
,以便被其它适配器增强.
使用类模板时必须指定模板参数的取值,因此将binder2nd
封装进函数bind2nd
中,使用函数模板的参数推导功能,简化代码:
1 | // 辅助函数 |
这样就可以像前文例子中那样使用bind2nd
了:
1 | cout << count_if(vi.begin(), vi.end (), bind2nd(less<int>(), 40)); |
仿函数适配器unary_negate
及其辅助函数not1
仿函数适配器unary_negate
将仿函数的结果取反,生成新的仿函数.其源码如下:
1 | // 仿函数适配器unary_negate也是仿函数类,因此继承自仿函数基类unary_function |
这样就可以像前文例子中那样使用not1
了:
1 | cout << count_if(vi.begin(), vi.end (), not1(bind2nd(less<int>(), 40))); |
仿函数适配器bind
在库文件include/c++/backward/backward_warning.h
中列出了一系列C++11中废弃了的STL类及其替代类.
被废弃的类及其头文件位置 | 替代类及其头文件位置 |
---|---|
<strstream> , strstreambuf |
<sstream> , basic_stringbuf |
<strstream> , istrstream |
<sstream> , basic_istringstream |
<strstream> , ostrstream |
<sstream> , basic_ostringstream |
<strstream> , strstream |
<sstream> , basic_stringstream |
<ext/hash_set> , hash_set |
<unordered_set> , unordered_set |
<ext/hash_set> , hash_multiset |
<unordered_set> , unordered_multiset |
<ext/hash_map> , hash_map |
<unordered_map> , unordered_map |
<ext/hash_map> , hash_multimap |
<unordered_map> , unordered_multimap |
<functional> , binder1st |
<functional> , bind |
<functional> , binder2nd |
<functional> , bind |
<functional> , bind1st |
<functional> , bind |
<functional> , bind2nd |
<functional> , bind |
<memory> , auto_ptr |
<memory> , unique_ptr |
其中用于绑定函数参数的类binder1st
和binder2nd
及其辅助函数bind1st
和bind2nd
都被替换为功能更强大的bind
.
函数bind
要和命名空间std::placeholders
中的占位符_1
、_2
、_3
…等占位符配合使用.bind
函数可以绑定:
- 函数和函数对象.
- 成员函数(绑定成员函数时占位符
_1
必须是该类对象的地址). - 成员变量(绑定成员变量时占位符
_1
必须是该类对象的地址).
下面程序演示其使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
double my_divide(double x, double y) { return x / y; }
struct MyPair {
double a, b;
double multiply() { return a * b; }
};
int main() {
using namespace std::placeholders; // 引入占位符_1, _2, _3,...
// 将10和2绑定到函数的第一参数和第二参数上
auto fn_five = std::bind(my_divide, 10, 2); // returns 10/2
std::cout << fn_five() << '\n'; // 5
// 将2绑定到函数的第一参数上
auto fn_half = std::bind(my_divide, _1, 2); // returns x/2
std::cout << fn_half(10) << '\n'; // 5
// 将函数的第一参数和第二参数绑定到第二参数和第一参数上
auto fn_invert = std::bind(my_divide, _2, _1); // returns y/x
std::cout << fn_invert(10, 2) << '\n'; // 0.2
// 将int绑定到函数的返回值上
auto fn_rounding = std::bind<int>(my_divide, _1, _2); // returns int(x/y)
std::cout << fn_rounding(10, 3) << '\n'; // 3
MyPair ten_two{10, 2};
// 将对象ten_two绑定到函数的第一参数上
auto bound_member_fn = std::bind(&MyPair::multiply, _1); // returns x.multiply()
std::cout << bound_member_fn(ten_two) << '\n'; // 20
// 将对象ten_two绑定到函数的成员变量上
auto bound_member_data = std::bind(&MyPair::a, ten_two); // returns ten_two.a
std::cout << bound_member_data() << '\n'; // 10
return 0;
}
迭代器适配器
逆向迭代器reverse_iterator
容器的rbegin()
和rend()
方法返回逆向迭代器reverse_iterator
,逆向迭代器的方向与原始迭代器相反.
1 | class container{ |
逆向迭代器适配器reverse_iterator
与正常迭代器的方向正好相反:逆向迭代器的尾(头)就是正向迭代器的头(尾);逆向迭代器的加(减)运算就是正向迭代器的减(加)运算.因此逆向迭代器取值时取得是迭代器前面一格元素的值.
reverse_iterator
源码如下:
1 | template<class Iterator> |
用于插入的迭代器insert_iterator
及其辅助函数inserter
迭代器适配器insert_iterator
生成用于原地插入运算的迭代器,使用insert_iterator
迭代器插入元素时,就将原有位置的元素向后推.
1 | list<int> foo = {1, 2, 3, 4, 5}; |
insert_iterator
通过重载运算符=
、*
和++
实现上述功能:
1 | // 适配器类insert_iterator |
输出流迭代器ostream_iterator
输出流迭代器ostream_iterator
常用于封装std::cout
.下面程序将容器中元素输出到std::cout
中.
1 | int main() { |
ostream_iterator
重载了运算符=
,*
和++
,其源码如下:
1 | template<class T, class charT=char, class traits =char_traits<charT> > |
输入流迭代器istream_iterator
输入流迭代器istream_iterator
用于封装std::cin
,下面程序从std::in
中读取数据:
1 | std::istream_iterator<double> eos; // 标志迭代器,通过与该迭代其比较以判断输入流是否终止 |
istream_iterator
重载了运算符=
,*
和++
,其源码如下:
1 | template<class T, class charT=char, class traits=char_traits<charT>, class Distance=ptrdiff_t> |
下面程序使用输入流迭代器istream_iterator
从std::cin
中读取数据到容器中:
1 | istream_iterator<int> iit(cin), eos; |
其它标准库相关的话题
容器tuple
使用tuple
1 | // 创建tuple |
tuple
类源码分析
容器tuple
的源码使用可变模板参数,递归调用不同模板参数的tuple构造函数,以处理任意多的元素类型.
1 | // 定义 tuple类 |
调用head
函数返回的是元素m_head
的值.
调用tail
函数返回父类成分的起点,通过强制转换将当前tuple
转换为父类tuple
,丢弃了元素m_head
所占内存.
type traits
类型萃取机制(type traits)获取与类有关的信息,在C++11之前和C++11中分别由不同的实现方式.
C++11之前的类型萃取机制:__type_traits
在C++11之前,类型萃取机制是由__type_traits
实现的.我们每创建一个类,就要以该类为模板参数特化一个__type_traits
类.
1 | template<class type> |
这种实现方式比较麻烦,因此C++11以一种新的方式引入type traits
机制.
C++11中的类型萃取机制:辅助类
C++11在头文件type_traits
中引入了一系列辅助类,这些辅助类能根据传入的模板参数自动进行获取该类的基本信息,实现类型萃取,并不需要我们为自己创建的类手动编写类型萃取信息.
官方网站上列出了所有用于类型萃取的辅助函数:
下面例子展示类型萃取机制的应用:
1 | cout << "is_ void\t" << is_void<T>::value << endl; |
类型萃取机制源码分析
头文件type_traits
中定义了辅助类remove_const
和remove_volatile
用于除去类型中的const
和volatile
关键字.
1 | // remove const |
is_void
类继承自__is_void_helper
类,__is_void_helper
类使用偏特化的形式判断传入的模板参数是否为void
.
1 | template<typename> |
is_integral
类继承自__is_integral_helper
类,同样使用偏特化的方式判断传入的模板参数是否为整数类型
1 | template<typename> |
一些type traits辅助类(如is_enum
、is_union
和is_class
等)是由编译器实现的,STL源码中找不到其实现函数.
1 | // is_enum |