C++、STL标准模板库和泛型编程 ——适配器 (侯捷)--- 持续更新
- 适配器(Adapters)
- 容器适配器(Container Adapters)
- 仿函数适配器(Functor Adapters)
- bind2nd(绑定第二实参)
- not1
- bind(新型适配器)
- 迭代器适配器(Iterator Adapters)
- reverse_iterator
- inserter
- X适配器
- ostream_iterator
- istream_iterator
- 补充
- Hash Function
- tuple
- type traits
- cout
- movable
使用一个东西,却不明白它的道理,不高明!—— 林语堂
阶段学习
使用C++标准库
认识C++标准库(胸中自有丘壑!)
良好使用C++标准库
扩充C++标准库
所谓 Generic Programming
(GP
,泛型编程),就是使用 template
(模板)为主要工具来编写程序。
-
GP
是将datas
和methods
分开来;Containers
和Algorithms
可各自闭门造车﹐其间以Iterator
连通即可·Algorithms
通过Iterators
确定操作范围﹐也通过Iterators
取用Container
元素。
-
OOP(Object-Oriented Programming)
,企图将datas
和methods
关联在一起。
C++标准模板库Standard Template
最重要的六大部件(Components
):容器、算法、仿函数、迭代器、适配器、分配器
- 容器(
Containers
)是class template
- 算法(
Algorithms
)是function template
(其内最终涉及元素本身的操作,无非就是比大小!) - 迭代器(
Iterators
)是class template
- 仿函数(
Functors
)是class template
- 适配器(
Adapters
)是class template
- 分配器(
Allocators
)是class template
关系图:
适配器(Adapters)
可以把它理解为改造器,它要去改造一些东西;也可以理解为实现换肤功能。
已经存在的东西,改接口,改函数名等。。。
实现适配,可以使用继承(is a)、复合(has a) 的两种方式实现。
共性:STL使用 复合(has a) 来实现适配!
容器适配器(Container Adapters)
例如:stack
和queue
具体定义查看:序列式容器的stack
和queue
容器
- 只使用一部分以及改接口,改函数名等。。。
- 把 复合(内涵) 的东西换一个风貌换一种风格出来!
仿函数适配器(Functor Adapters)
bind2nd(绑定第二实参)
把东西记起来,以备后面使用!
可以看到下面的这个例子,使用算法count_if
:
- 第三个参数是一个
predicate
,也就是判断条件,有一个仿函数对象less<int>()
,但是他被仿函数适配器bind2nd
(将less
的第二个参数绑定为40
)和not1
(取反)修饰,从而实现判断条件为是否小于40。
bind2nd
调用binder2nd
:
- 图上灰色的东西就是仿函数适配器和仿函数之间的问答!
- 这里就体现了仿函数为什么要继承适合的
unary_function
或者binary_function
等类的原因!
- 这里就体现了仿函数为什么要继承适合的
- 还有一个细节:适配器适配之后的仿函数也能够继续被适配:
- 所以适配器要继承
unary_function
或者binary_function
等类,这样才能回答另外一个适配器的问题。 - 问 bianry_fucntion 三个参数
first_argument_type
、second_argument_type
、result_type
。 - 提问前面都要加上
typename
,是为了让编译通过!
- 所以适配器要继承
- 所以,仿函数必须能够回答适配器的问题,这个仿函数才是可适配的!
相对绑定第二实参,绑定第一实参为
bind1st
not1
对一个Predicate
取反。
not1
是构造一个与谓词结果相反的一元函数对象。not2
是构造一个与谓词结果相反的二元函数对象。
一层套一层,像乐高积木一样!
bind(新型适配器)
替换了一些过时(bind1st、bind2st)的仿函数适配器!
std::bind 可以绑定:
functions
function objects
member functions
,_1
(占位符号)必须是某个object
地址。data members
,_1
必须是某个object
地址。
返回一个function object ret
。调用ret
相当于调用上述的1,2,3或者相当于取出4.
示例:
// bind example
#include <iostream> // std::cout
#include <functional> // std::bind
// a function: (also works with function object: std::divides<double> my_divide;)
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; // adds visibility of _1, _2, _3,...
//---------------------绑定function,也就是前面的1---------------------
// binding functions:
auto fn_five = std::bind (my_divide,10,2); // returns 10/2
std::cout << fn_five() << '\n'; // 5
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
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};
//---------------------绑定member functions,也就是前面的3---------------------
// binding members:
//member function 其实有一个看不见的实参argument :this
auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
std::cout << bound_member_fn(ten_two) << '\n'; // 20
//---------------------绑定member data,也就是前面的4---------------------
auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
std::cout << bound_member_data() << '\n'; // 10
//-------------------------上面的bind2nd就可以替换了-------------------------
vector<int> v {15, 37, 94, 50, 73, 58, 28, 98};
int n = count_if(v.cbegin(), v.cend(), not1(bind2nd(less<int>(), 50)))
cout << "n=" << n << endl; //5
//替换
auto fn_ = bind(less<int>(), _1, 50);
cout << count_if(v.cbegin(), v.cend(), fn_) << endl; //3
return 0;
}
迭代器适配器(Iterator Adapters)
reverse_iterator
reverse_iterator
rbegin(){//取逆向的头,就是正向的尾巴
return reverse_iterator(end());
}
reverse_iterator
rend(){//取逆向的尾巴,就是正向的头
return reverse_iterator(begin());
}
也有五种关联类型:
inserter
可以不用担心copy
到的目的容器大小不匹配的问题。
copy
是写死的,我们调用copy
,希望完成在容器指定位置插入一些值!具体的实现:
- 把相应的容器和迭代器传入
inserter
,对容器的迭代器中的=
运算符进行重载,就能改变copy
的行为! - 因为这个是对迭代器的
=
运算符行为进行重定义,所以是迭代器的适配器。
X适配器
X
表示未知:(容器、迭代器、函数,三大类之外的!)
- 包括
ostream
、istream
迭代器适配器
ostream_iterator
copy
都是已经写好的,不能再改了!- 该适配器适配的是
basic_ostream
,也是重载了=
运算符,添加输出操作!
istream_iterator
ostream_iterator
的兄弟,cin >> x
被替换为了 x = *iit
,适配 basic_istream
。
- 不断
++
,就不断读内容。
copy
都是已经写好的,不能再改了!
当创建iit(cin)
,已经读入数据了!
不断++
,就不断读内容。
补充
标准库STL周边还有一些东西需要知道。
Hash Function
如果有一个我们自己的类,我们要怎么给这个类设计hash
函数呢?
使用类中的成员变量的hash
函数得到hash
值,然后相加,(下面左上角)这个太naive
了,可能会产生很多冲突。
所以使用右边那个!
args
是C++11的新特性,任意多个参数都行,n
个参数的args
作为另外一个函数的输入的时候- 先调用①,分配一个种子
seed
,再调用②; - 在②里面拆分
args
,拆分成1
+n-1
的形式,递归调用自身,直到args
只剩下一个参数时,调用③; - 在②中拆分时,会不断改变种子
seed
:基本类型的hash函数
+
0x9e3779b9+
...
(越乱越好,没有数学可言,)。
- 先调用①,分配一个种子
也是使用想法一的思想,但是加入了更多的复杂的操作,使得得到的hash code冲突更少。
tuple
一组东西的组合,可以任意指定多少个元素,这些元素可以是任意的类型。
使用示例:
tuple<string, int, int, complex<double>> t;
sizeof(t); // 32, 为什么是32,而不是28呢?啊~侯捷也无法理解啊!
tuple<int, float, string> t1(41, 6.3, "test");
cout << "t1:" << get<0>(t1) << ' ' << get<1>(t1) << ' ' << get<2>(t1) << endl;
auto t2 = make_tuple(22, 44, "test2"); // t2也是一个tuple,自动推导类型
tuple_size< tuple<int, float, string> >::value; // 3
tuple_element< tuple<int, float, string> >::type; // 取tuple里面的类型
继承的是自己,会自动形成一个类的继承关系,注意有一个空的 tuple
类。
type traits
trivial:琐碎的,平凡的,平淡无奇的,无关痛痒的,无价值的,不重要的。
泛化模板类,包括五种比较重要的typedef
: 默认的回答都是重要的!
typedef _false_type has_trivial_default_constructor; //默认构造函数是不重要吗?
typedef _false_type has_trivial_copy_constructor; //拷贝构造函数是不重要嘛?
typedef _false_type has_trivial_assignment_operator; //拷贝赋值构造函数是不重要嘛?
typedef _false_type has_trivial_destructor; //析构函数是不重要嘛?
typedef _false_type is_POD_type; //是不是旧格式(struct,只有数据,没有方法)?
比如说对于int
的type traits
,五个问题的回答都不重要。一般是算法会对traits
进行提问。实用性不高。
type traits
现在的 traits机 ,非常智能:
- 只要把自己写的或者系统自带的类,作为
is_()::value
就能得到问题的答案,这些问题包括下面几种,不全:
测试:
//global function template
template <typename T>
void type_traits_output(const T& x)
{
cout << "\ntype traits for type:" << typeid(T).name() << endl;
cout << "is_void\t" << is_void<T>::value << endl;
cout << "is_integral\t" << is_integral<T>::value << endl;
cout << "is_array\t" << is_array<T>::value << endl;
cout << "is_class\t" << is_class<T>::value << endl;
cout << "is_function\t" << is_function<T>::value << endl;
cout << "is_pointer\t" << is_pointer<T>::value << endl;
cout << "is_object\t" << is_object<T>::value << endl;
...
}
类型萃取机这么强的功能,是怎么实现的呢?下面以is_void
为例:
- 首先去掉
const
和volatile
这两种对得到类特征无用的修饰关键字,做法如下(主要是用模板技巧); - 然后将去掉
cv
(就是const
和volatile
)关键字之后,再传入__is_void_helper
模板类中,让其自己匹配是不是空类型,匹配到不同的模板类,返回不同的bool
值。