1. Adapter 适配器
1.1 适配器原理
- 适配器,改造器更为贴切。换肤工程,小变化。出现在三个地方,仿函数、容器、迭代器的适配器。为了实现一个桥梁的作用,就是A代替B给大家使用,但是底层是A调用B。这里的关系是复合(拥有的关系),不是继承。所以迭代器适配器内含了一个迭代器,容器适配器内含了一个容器。为了实现仿函数适配器与仿函数之间的回答与提问。必须包括三个typedef。
1.2 容器适配器
- STL提供了两个容器适配器,分别是 stack(栈) 和 queue(队列)。他们是修饰 deque(双端队列)的接口而呈现出另一种容器风貌。
1.3 仿函数适配器
- 仿函数适配器,是去修饰仿函数,然后还要表现出函数的性质。所以最终还是要重载(),以表现出函数的性质。
1.3.1 binder2nd
- binder2nd来绑定第二个参数,之前less是x<y,现在是x<40。less< int>()是创建了一个函数对象,不是调用。
bind2nd调用了binder2nd,那么最终的效果是less< int>()传进去变成op,40传进去变成value。 - bind2nd调用过程,重点: 当执行pred(*first)调用的是operator()这个小括号重载函数,才真正调用了less。左下角的bind2nd(less< int>,40):这里调用的是bind2nd的构造函数,并把less< int>和40分别赋值给op和value。
所以,适配器只是先记下来,也没有绑定。只有在真正被调用,才会绑定起来。
binder2nd< Operation >是一个类型,加了()是创建一个临时对象。 - 接下来注意灰色的部分,灰色部分会问三个问题:less的第一个实参是什么type,第二个,和返回的类型都是什么参数。如果能回答这三个问题的函数,被称为函数适配器。
- 灰色部分的前面有typename,是为了在编译过程中帮助编译器理解后面紧跟的是一个typename(类型),防止编译器出现困惑。
- 这里还有个细节:binder2nd继承了unary_function。binder2nd本身适配了函数,而成为一个新的函数。但是它也可以被适配,由于现在已经确定了一个参数40,所以只剩下一个待传参数。为了能够让_not1适配,就要继承unary_function,然后回答那些问题。
#include "vector" #include "iostream" namespace test { template<typename Operation> class binder2nd : public std::unary_function<typename Operation::first_argument_type, typename Operation::result_type> { protected: Operation op; typename Operation::second_argument_type value; public: binder2nd(const Operation& _x, const typename Operation::second_argument_type& _y) : op(_x), value(_y) { } typename Operation::result_type operator()(const typename Operation::first_argument_type& _x) const { return op(_x, value); } typename Operation::result_type operator()(typename Operation::first_argument_type& _x) const { return op(_x, value); } }; //count_if template <typename InputIterator, typename Predicate> //模板参数,迭代器类型与条件类型 typename std::iterator_traits<InputIterator>::difference_type count_if(InputIterator first, InputIterator last, Predicate pred) { typename std::iterator_traits<InputIterator>::difference_type n = 0; //通过迭代器萃取机得到difference_type for(;first != last; ++first) { //调用运算符(),传递的参数为*first if(pred(*first)) n++; } return n; } //bind2nd,辅助函数,帮助user使用bind2nd template<typename Operation, typename T> inline binder2nd<Operation> bind2nd(const Operation& op, const T &x) { typedef typename Operation::second_argument_type arg2_type; //Operation是一个STL仿函数,完成二元运算,继承了binary_function return binder2nd<Operation>(op, arg2_type(x));//返回一个binder2nd<Operation>对象 } } int main() { std::vector<int> v{1,2,3,4,5}; std::cout << test::count_if(v.begin(), v.end(), test::bind2nd(std::less<int>(), 4)); }
输出:
3
1.3.2 _not1
- _not1适配器:辅助函数调用unary_negate的构造函数创建一个unary_negate对象,初始化pred,然后count_if中执行pred(*first)调用的是operator()这个小括号重载函数,在重载函数中调用bind2nd的重载函数,然后再在bind2nd的重载函数中调用less(less里也重载了小括号)。
1.3.3 新型适配器bind
- 新型适配器bind():
- 由于要用到占位符,所以using namespace std::placeholders。
_1,_2
就是占位符,占位符是为了表示第一、第二参数。 - bind可以传入一个模板参数,用来指定返回的类型,例如
bind<int>
指定返回int类型。 - 绑定成员函数时,绑定的第一个参数必须是某个object的地址。另外,成员函数都有一个默认的实参
this
,调用时也必须明确 - 绑定数据成员时,绑定的第一个参数也必须是某个object的地址。
using namespace std::placeholders; int main() { auto fn_five = bind(my_divide, 10, 5); cout << fn_five() << endl; auto fn_half = bind(my_divide, _1, 2); cout << fn_half(10) << endl; auto fn_invert = bind(my_divide, _2, _1); cout << fn_invert(10, 2) << endl; auto fn_rounding = bind<int>(my_divide, _1, _2); cout << fn_rounding(10, 3) << endl; MyPair ten_two{10, 2}; auto bound_memfn = bind(&MyPair::multiply, ten_two); auto bound_memfn2 = bind(&MyPair::multiply, _1); cout << bound_memfn() << " " << bound_memfn2(ten_two) << endl; auto bound_memdata1 = bind(&MyPair::a, _1); auto bound_memdata2 = bind(&MyPair::b, ten_two); cout << bound_memdata1(ten_two) << " " << bound_memdata2() << endl; vector<int> vec{15,50,12,69,63,32,14}; int n = count_if(vec.begin(), vec.end(), not1(bind2nd(less<int>(), 50))); cout << n << endl; auto fn = bind(less<int>(), _1, 50); cout << count_if(vec.begin(), vec.end(), fn); }
- 由于要用到占位符,所以using namespace std::placeholders。
1.4 迭代器适配器
1.4.1 reverse_iterator
- reverse_iterator:常用来对容器进行反向遍历,即从容器中存储的最后一个元素开始,一直遍历到第一个元素
- 值得一提的是,反向迭代器底层可以选用双向迭代器或者随机访问迭代器作为其基础迭代器。不仅如此,通过对 ++(递增)和 --(递减)运算符进行重载,使得:
- 当反向迭代器执行 ++ 运算时,底层的基础迭代器实则在执行 – 操作,意味着反向迭代器在反向遍历容器;
- 当反向迭代器执行 – 运算时,底层的基础迭代器实则在执行 ++ 操作,意味着反向迭代器在正向遍历容器。
- reverse_iterator接受一个迭代器作为参数,这个迭代器是正向迭代器,如图所示,对逆向迭代器的取值就是对应的正向迭代器退一格取值
1.4.2 inserter
- copy函数传入三个参数,被拷贝的首地址、被拷贝的末地址以及要拷贝到的首地址
copy(myints, myints+7, myvec.begin())
,但是从copy源代码里可以看出,copy不检查写入地址的合法性。 copy(bar.begin(), bar.end(), it)
,如果是这样写的话则会覆盖foo中的4,5并且继续往后面覆盖(超出了foo的范围),从这里可以看出copy不检查写入地址的合法性。copy(bar.begin(), bar.end(), inserter(foo,it))
,采用inserter可以让copy函数在it出开始插入值。- inserter是一个辅助函数,帮助用户使用insert_iterator。insert_iterator重载了操作符=,这样在copy函数中,
*result=*first
这一步操作会调用重载之后的=,即:调用insert来插入值。
1.5 X适配器
1.5.1 ostream_iterator
-
X适配器,不知道该属于哪一类适配器(不属于迭代器适配器、容器适配器以及仿函数适配器)
-
int main() { vector<int> vec; for(int i = 0; i < 10; i++) { vec.push_back(i * 10); } ostream_iterator<int> out_it(cout, ","); copy(vec.begin(), vec.end(), out_it); return 0; }
-
int main() { ofstream ofs("test.txt"); if (!ofs) { cout << "File open error!" << endl; exit(1); } vector<int> v{1,2,3,4,5}; ostream_iterator<int> it(ofs, " "); copy(v.begin(), v.end(), it); ofs.close(); }
-
上述代码中,ostream_iterator绑定一个cout,并传入","作为分隔符。
-
ostream_iterator中重载了++运算符,*运算符,=运算符,所以copy可以正常工作,其中,最重要的是=运算符,将value丢入out_stream,实现了cout << value的操作。
1.5.2 istream_iterator
-
istream_iterator如果接受一个cin(或者ifstream的对象),初始化数据成员后会立刻执行重载后的++运算符,即:立即读取输入
-
int main() { istream_iterator<int> iit(cin), eos; vector<int> c; copy(iit, eos, inserter(c, c.begin())); return 0; }
-
上述代码中,创建了两个istream_iterator,并作为参数传给copy,根据copy源码,由于
istream_iterator<int> iit(cin)
创建之后立刻读取cin,所以只要输入不为eos,copy就可以正常进入while循环,每读取一个,first执行++,即再次读取cin