【《STL源码剖析》提炼总结】 第8节:适配器

一.适配器概览

1.什么是适配器

adapter:将一个class的接口转换为另一个class的接口,使原本因接口不兼容而不能合作的classes,可以一起运作

2.适配器的本质

​ 我的理解是,适配器实际上就是对元素的封装,其对外的接口是通过对元素的调用来实现的,这个封装的过程是用于拓展功能、或者作为中间层提供额外接口(比如ptr_fun)

二.应用于容器的适配器 container adapters

1.stack

特点

​ stack即采用先进后出,是数据结构中一个非常重要的部分。

​ stack实际上就是对内部封装的其他容器的操作,因此被称为容器适配器(container adapter)

​ 只要拥有stack对其内部使用的方法,这个容器便可以称为stack的底层容器,因此对应容器有: deque(默认类型)、list、vector

成员

​ 内部封装的容器

迭代器

​ 因为操作都是内部容器实现,且要实现严格的先进后出,所以stack没有遍历功能也没有迭代器。

常用方法

  • push
  • pop
  • top

2.queue

特点

​ queue即采用先进先出,同样是数据结构中一个非常重要的部分。

​ 和stack一样,queue内部封装了一个容器,因此也是一个容器适配器。

​ 可以完全适配的有两个容器:deque(默认)和list。

成员

​ 只有一个,即内部封装的容器

迭代器

​ queue同样不提供遍历功能和迭代器。

常用方法

  • push
  • pop
  • front

三.迭代器适配器 iterator adaptor

作用

​ 通过对内部元素的封装,提供和迭代器一样的接口,但是实现了其他的作用。

分类

insert iterator

作用

​ 将迭代器的赋值功能改为插入功能。

特点

​ 本质上是只写的 分类为 output_iterator_tag

​ 其使用的 ++* 等运算符重载都是返回自身

​ 不提供--->等功能

分类

  • back_insert_iterator

    • 将插入的功能改为对容器的尾部插入

    • 内部修饰的是一个指向容器的指针

    • 实际上调用的是指针指向的容器的 push_back 方法,因此需要容器有对应的方法,否则会出错。

  • front_insert_iterator

    • 将插入的功能改为对容器的头部插入
    • 内部修饰的是一个指向容器的指针
    • 实际上调用的是指针指向的容器的 push_front 方法,因此需要容器有对应的方法,否则会出错。
  • insert_iterator

    • 将插入的功能改为对当前位置插入
    • 内部修饰的是一个指向容器的指针和一个指向对应位置的迭代器
    • 实际上调用的是容器的insert操作,参数为内部保存的迭代器。
      • 在插入之后还需要对迭代器进行复位操作,使其指向原来的位置(insert通常是将元素插入指向位置的前方,同时返回插入元素的位置)
      • 为什么要将insert返回值赋值给迭代器,然后迭代器再++——因为可能发生扩容事件导致迭代器失效,因此迭代器需要更新
  • 辅助函数

    • 这三个insert iterator都搭配了辅助函数,实际上就是一层封装,可以通过实参推导来隐式地推导类型,省去了手写对应类型的麻烦。
    • 比如:
      template<typename _Container>
        _GLIBCXX20_CONSTEXPR
        inline back_insert_iterator<_Container> back_inserter(_Container& __x)
        { return back_insert_iterator<_Container>(__x); }
    

erverse iterator

作用

​ 在遍历容器的时候,很多时候需要进行逆序遍历,这时候就可以使用容器提供的rendrbegin两个函数,其返回最后一个元素以及第一个元素的前端,因此可以通过[rbeign,rend) 的方式进行遍历。

​ 上述两个函数就是通过reverse_iterator对常规的begin和end进行封装得到的,其目的就是将迭代器的移动行为倒转,使其从头到尾处理序列中的元素。

封装操作

移动

​ 因为是逆序移动,因此对于++,--,+,-,+=,-=全部为逆操作(调用反方向的函数即可,比如说++内部使用的是封装的迭代器的--)。

取值

​ 因为要符合左闭右开的原则,因此返回的值(operator*)必然不是原有迭代器的*操作,而是要返回其指向元素前面元素的值。

​ 原来的end指向的是最后一个元素后一个元素,而使用reverse iterator修饰后的rend指向的确实最后一个元素 ,begin和rbegin同理。

​ 示意图如下

reverse iterator

stream iterator

作用

​ stream iterator是通过对字符流进行操作,内部封装的是istream和ostream。

​ 通过对istream和ostream的封装,暴露出的接口符合迭代器的标准,使用带copy属性的算法时就可以将输出的位置修改为对字符流进行操作(比如直接使用cout输出)。

分类

  • 封装输入流是istream_iterator

    • 内部封装了一个istream,将赋值操作“重定向”为<<操作
    • 类型为input_iterator_tag
    • 内部的成员:
      • value(存储读取的值)
      • istream类型的stream,保存读入的位置(比如cin,从stdin中读入)
      • bool end_marker ,当读完成时会被修改为false
    • 被重载的部分:
      • 创建一个istream_iterator的时候会进行一次read
      • 使用++操作的时候也会进行一次read
      • *操作即取出value的值输出,->同理

    可以将其在copy中进行对应

    copy(first,end)
    {
        for( ; first!=end ; ++result,++first)
            *result = *first;
        return result;
    }
    

    ​ end其实就是end_marker为false的istream_iterator,因此若读完成则会因为first==end而结束for循环。

    ​ 在创建的时候已经进行一次read操作,在for循环中的++操作又有一次read操作,因此与原有的for的逻辑的对应的,定义和移动操作被“重定向”为read操作。

    ​ 通过*取出value,然后通过赋值操作进行赋值

  • 封装输出流的ostream_iterator

    • 内部封装了一个ostream,将赋值操作“重定向”为>>操作
    • 类型为output_iterator_tag
    • 内部的成员
      • ostream*stream ,输出的对象
      • const char* string ,分隔符
    • 被重载的部分
      • =:对其赋值就相当于进行输出,调用stream的<<操作
      • ++*:均返回其本身

    在copy中对应的情况

    copy(first,end)
    {
        for( ; first!=end ; ++result,++first)
            *result = *first;
        return result;
    }
    

    ​ ostream_iterator其实就是result,因此赋值操作就相当于输出,++操作对其无操作

    *result = *first;=>result = *first;=>调用<<操作

四.仿函数适配器 function adapters

1.作用

​ 仿函数适配器是最庞大的一个适配器群体,其主要是两个方面的作用

  1. 通过封装仿函数,拓展其功能,比如实现绑定参数、对仿函数返回值进行操作
  2. 通过封装一下不符合仿函数体系的元素(比如函数指针和成员函数指针),使其融入仿函数体系

​ 值得注意的是,仿函数适配器本身也有对binary_negateunary_negate这两个提供类型接口的类进行继承,因此其本身也可以被当做仿函数来使用,故而仿函数适配器可以嵌套使用,而不是只能嵌套一层

2.详述

辅助函数

​ 作为标题的实际上是其辅助函数,与insert_iterator同理,其用一个function template将其封装,通过实参推导获取对应类型,省去自己的写的麻烦。

对返回值进行否定 not1 not2

  • not1:传入的仿函数有一个参数
  • not2:传入的仿函数有两个参数

实现细节:

  • 内部有一个成员,就是对应的仿函数的实例
  • operator()接收对应的参数,然后调用保存的仿函数,然后将其返回值取反后输出
// not2
bool operator()(const typename Predicate::first_argument_type& x,
          const typename Predicate::second_argument_type& y)
{
    return !pred(x,y);	// 对调用仿函数实例的结果取反后输出
}

对参数进行绑定 bind1st bind2nd

​ 接受一个二元的仿函数,然后保存一个参数作为其第一/二个参数,operator()接收另一个函数。简单来说就是将一个二元仿函数封装成一个一元仿函数。

  • bind1st:绑定第一个参数
  • bind2nd:绑定第二个参数

​ 两个成员:对应的仿函数,输入的第一/二个参数

// binder1st
bool operator()(typename Predicate::second_argument_type& x)
{
    return !pred(value,y);	
    			// 自己保存的value作为第一参数,获取一个值作为第二参数
}

函数合成 compose1 compose2

​ 用于多个函数的嵌套

  • compose1 接受2个一元函数 op1 op2 ,返回 op1(op2(x))
  • compose1 接受1个2元函数 op1,2个一元函数 op2 op3 ,返回 ,op1(op2(x),op3(x)),op2和op3使用的参数相同

​ 成员:对应的若干个仿函数实例

// compose2
bool operator()(const typename Operation2::argument_type& x) const
{
    return op1(op2(X),op3(x));
}

封装函数指针 ptr_fun

​ 使得可以将一般函数当做仿函数使用。

​ 对于算法来说,一般函数也可以使用,但是其无适配能力,因此不能搭配仿函数使用,需要 ptr_fun作为中间层。

​ 其辅助函数只有一个ptr_fun,但是通过参数数量有两个重载,一个内部调用pointer_to_unary_function,处理1元函数指针,另一个内部调用pointer_to_binary_function,处理2元函数指针。

tips: 虽然使用辅助函数可以区分,但是假如传入的函数有两个重载,其中一个为一元,一个为二元,那辅助函数就无法得知对应的是哪种类型的函数指针,因此也不知道是使用pointer_to_unary_function的重载还是pointer_to_binary_function的重载。

这种时候还是需要自己手动定义对应的类型(明确指定是函数指针的哪个重载版本)

​ 其operator()就是通过传入参数调用函数指针,唯一的区别就是多了对应的参数接口。

封装成员函数指针 mem_fun

​ 使得可以将一般函数当做仿函数使用。

​ 同样,成员函数指针没有适配能力,需要一个中间层。

​ 这个族群有3个角度的区别,因此有2^3=8 个对应的function objection。

12
参数无任何参数有一个参数
调用通过pointer调用通过reference调用
是否constconst成员函数non-const成员函数

​ 8个对应的functor adapter如下

​ 作为调用者有两个上层的辅助函数(每个有4个重载)

辅助函数functor adapter参数调用是否const
mem_funmem_fun_t无任何参数通过pointer调用const成员函数
const_mem_fun_t无任何参数通过pointer调用non-const成员函数
mem_fun_ref_t无任何参数通过reference调用const成员函数
const_mem_fun_ref_t无任何参数通过reference调用non-const成员函数
mem_fun1mem_fun1_t有一个参数通过pointer调用const成员函数
const_mem_fun1_t有一个参数通过pointer调用non-const成员函数
mem_fun1_ref_t有一个参数通过reference调用const成员函数
const_mem_fun1_ref_t有一个参数通过reference调用non-const成员函数
什么是成员函数指针

C++ 函数指针 & 类成员函数指针 | 菜鸟教程

类成员函数指针(member function pointer),是 C++ 语言的一类指针数据类型,用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息。

  1. 类成员函数指针指向类中的非静态成员函数

    1. 对于 non static member function (非静态成员函数)取地址,获得该函数在内存中的实际地址
    2. 对于 virtual function(虚函数), 其地址在编译时期是未知的,所以对于 virtual member function(虚成员函数)取其地址,所能获得的只是一个索引值

    对于非虚函数,返回其在内存的真实地址
    对于虚函数, 返回其在虚函数表的偏移位置

    //对于指向类成员函数的函数指针,引用时必须传入一个类对象的this指针,所以必须由类实体调用
        (pa->*ptr)(1000);
        ^   ^        ^
    类指针 成员函数指针 参数
    
        (a.*ptr)(10000);
    //两种形式,指针或者实例本身
    
  2. 类成员函数指针指向类中的静态成员函数
    赋值方式相同,但是静态成员函数指针只能赋值给static成员函数的函数指针(比如void (*p2)(void);)

mem_fun adapter的实现

​ 如上所述,调用mem_fun需要对应的class实例和成员函数指针

​ 因此adapter内部成员为要绑定的成员函数指针,在operator()中通过参数传入class实例的指针

// mem_fun_t
template <class S, class T>
class mem_fun_t : public unary_function<T *, S>
{
public:
    explicit mem_fun_T(S (T::*pf)()) : f(pf) {}    // 记录成员函数指针
    S operator()(T *p) cosnt { return (p->*f)(); } // 转调用
private:
    S (T::*f)
    (); // 一个参数列表为void,是T的成员的函数
};
使用样例

如下是使用辅助函数以及直接使用辅助函数调用的adapter的样例

class test
{
public:
    void dis(vector<int> num)
    {
        for (auto x : num)
            cout << x << " ";
        cout << endl;
    }
    void dis_const(vector<int> num) const
    {
        for (auto x : num)
            cout << x << " ";
        cout << endl;
    }
    void dis2()
    {
        vector<int> num{1, 2, 3, 4, 5};
        for (auto x : num)
            cout << x << " ";
        cout << endl;
    }
    void dis2_const() const
    {
        vector<int> num{1, 2, 3, 4, 5};
        for (auto x : num)
            cout << x << " ";
        cout << endl;
    }
    void dis3()
    {
        vector<int> num{1, 2, 3, 4, 5};
        dis(num);
    }
};

如下是main函数的调用部分

    cout << "\n==" << 1 << "=================\n";
    {
        // _Ret (_Tp::*__f)()
        mem_fun (&test::dis2)(tpoi);
        // _Ret (_Tp::*__f)() const
        mem_fun (&test::dis2_const)(tpoi);
        // _Ret (_Tp::*__f)(_Arg)
        mem_fun (&test::dis)(tpoi, num);
        // _Ret (_Tp::*__f)(_Arg) const
        mem_fun (&test::dis_const)(tpoi, num);
    }
    cout << "\n==" << 2 << "=================\n";
    {
        // _Ret (_Tp::*__f)()
        mem_fun_ref (&test::dis2)(tref);
        // _Ret (_Tp::*__f)() const
        mem_fun_ref (&test::dis2_const)(tref);
        // _Ret (_Tp::*__f)(_Arg)
        mem_fun_ref (&test::dis)(tref, num);
        // _Ret (_Tp::*__f)(_Arg) const
        mem_fun_ref (&test::dis_const)(tref, num);
    }
    cout << "\n==" << 3 << "=================\n";
    {
        mem_fun_t<void, test> (&test::dis2)(tpoi);
        // mem_fun_t<void,test> (&test::dis2_const)(tpoi); // 非const与cosnt不匹配
        const_mem_fun_t<void, test> (&test::dis2_const)(tpoi);
        mem_fun1_t<void, test, vector<int>> (&test::dis)(tpoi, num);
        const_mem_fun1_t<void, test, vector<int>> (&test::dis_const)(tpoi, num);
    }

3.使用实例

cout << __gnu_cxx::compose2(multiplies<int>(),bind1st(plus<int>(),1),bind1st(plus<int>(),3))(9)<<endl;
// ( 1 + x ) * ( 3 + x )  x=9
// ( 1 + 9 ) * ( 3 + 9 )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值