C++ STL:适配器

简介: C++ STL:适配器

Part 3:适配器 Adapter

适配器就是接口,对容器、迭代器、算法进行包装,但其实质还是容器、迭代器和算法,只是不依赖于具体的标准容器、迭代器和算法类型。概念源于设计模式中的适配器模式:将一个类的接口转化为另一个类的接口,使原本不兼容而不能合作的类,可以一起运作。

容器适配器可以理解为容器的模板,迭代器适配器可理解为迭代器的模板,算法适配器可理解为算法的模板。

1、容器适配器

应用于容器,容器适配器包括:stack queue priority_queue

1.1、stack

stack 容器适配器,是 FILO(先进后出)的数据结构,底层容器deque,从容器的尾部(栈顶)推弹元素

template<class T, class Container = std::deque<T>> class stack;

1.2、queue

queue 容器适配器,是 FIFO (先进先出)数据结构,底层容器 deque,从容器尾部(队尾)推入元素,从容器头部(队头)弹出元素。

template<class T, class Container = std::deque<T>> class queue;

1.3、priority_queue

优先队列是容器适配器,提供常数时间的最值元素查找,对数时间的插入和删除。

  • 底层容器vector
  • 元素放入优先队列时,采用堆排序自动调整顺序
  • 针对自定义类类型,需要重载比较方式class compare
  • 默认情况下,std::less<T>,大顶堆。否则,std::greater<T>,小顶堆
template<class T, class Container = std::vector<T>, class Compare = std::less<typename Container::value_type>> class priority_queue;

(疑问:less,数组元素从小到大排列,访问数组尾部元素,大根堆?)

2、迭代器适配器

应用于迭代器。

  • 插入迭代器:front_insert_iteratorback_insert_iteratorinsert_iterator
  • 反向迭代器:reverse_iterator
  • 流迭代器:ostream_iteratoristream_iterator

2.1、插入迭代器

  • front_insert_iterator:在容器的尾部插入新元素,底层调用push_back()
  • back_insert_iterator:在容器的头部插入新元素,底层调用push_front()
  • insert_iterator:在容器指定位置插入新元素,底层调用insert()

容器使用插入迭代器时,底层必须实现对应的方法。

STL 为了提升使用的便利性,提供三个相应的函数,内部封装的是相应的插入迭代器

  • front_inserter
  • back_inserter
  • inserter

测试1:front_inserter | front_insert_iterator,输出结果:5 4 3 2 1 l1 l2 l3

vector<int> nums{1, 2, 3, 4, 5};
 list<int> ls{11, 12, 13};
 copy(nums.begin(), nums.end(), front_inserter(ls));
 // copy(nums.begin(), nums.end(), front_insert_iterator<list<int>>(ls));
 copy(ls.begin(), ls.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

测试2:back_insert_iterator|,输出结果:11 12 13 1 2 3 4 5

vector<int> nums{1, 2, 3, 4, 5};
 list<int> ls{11, 12, 13};
 copy(nums.begin(), nums.end(), back_inserter(ls));
 // copy(nums.begin(), nums.end(), back_insert_iterator<list<int>>(ls));
 copy(ls.begin(), ls.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

测试3:insert_iterator | inserter,输出结果:11 1 2 3 4 5 12 13

vector<int> nums{1, 2, 3, 4, 5};
 list<int> li {11, 12, 13};
 auto it = li.begin();
 ++it;
 copy(nums.begin(), nums.end(), inserter<list<int>>(li, it));
 // copy(nums.begin(), nums.end(), insert_iterator<li<int>>(li, it));
 copy(li.begin(), li.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

2.2、反向迭代器

reverse_iterator,将迭代器的方向进行逆转,实现逆序遍历,底层是双向迭代器

测试:逆序输出结果:5 4 3 2 1

vector<int> nums{1, 2, 3, 4, 5};
 for (reverse_iterator rit = nums.rbegin(); rit != nums.rend(); ++rit) {
     cout << *rit << " ";
 }

2.3、流迭代器

ostream_iterator | inputIterator,可以将迭代器绑定到某个 iostream对象上,拥有输入输出功能。以此为基础,稍加修改,就可以适用于任何输入或输出装置上。例如:绑定到磁盘的某个目录上。原理就是把输入输出流当作容器(输入输出流拥有缓冲区),容器的访问需要迭代器。

测试1:ostream_iterator

vector<int> nums{1, 2, 3, 4, 5};
 // 将标准输出当成是容器,容器的访问就需要迭代器
 // 每次写操作后,写入可选的分隔符。
 // std::ostream_iterator<int> osi(cout, " ");
 // std::copy(nums.begin(), nums.end(), osi);
 copy(nums.begin(), nums.end(), ostream_iterator<int>(cout, " "));

测试2:istream_iterator

vector<int> nums;
 // 将标准输入流当成是容器,容器的访问就需要迭代器
 istream_iterator<int> isi(std::cin);
 // 匿名的 istream_iterator 对象,表示迭代器结束的位置,详见构造函数源码
 // 快捷键: ctrl + d 表示输入结束
 // 问题:nums 还没有申请空间,所以插入新的元素时,会出现 bug
 // copy(isi, istream_iterator<int>(), nums.begin()); // error
 // 插入元素时,需要使用插入迭代器来完成
 // back_insert  ==> 调用到push_back完成元素的添加
 copy(isi, istream_iterator<int>(), std::back_inserter(nums));

3、函数适配器

应用于仿函数。灵活度很高,可以配接,配接,再配接。通过它们之间的绑定、组合、修饰能力,可以无限制创造出各种可能的表达式。

包括

  • 绑定 bind
  • 否定 negate
  • 组合 compose
  • 修饰:通过修饰普通函数、成员函数,使之成为仿函数

3.1、* bind

bind 函数绑定多个实参到函数对象,函数原型:

/*
 返回值:函数对象
 参数:
 - f: 可调用 (Callable) 对象(函数对象、指向函数指针、函数引用、指向成员函数指针或指向数据成员指针)
 - args: 要绑定的参数列表,未绑定参数为命名空间 std::placeholders 的占位符 _1, _2, _3...所替换
 */
 template< class F, class... Args > 
 /*unspecified*/ bind( F&& f, Args&&... args );
 template< class R, class F, class... Args > 
 /*unspecified*/ bind( F&& f, Args&&... args );

bind 使用方法

bind 绑定类型

  • 绑定普通函数。
  • 绑定成员函数。成员函数隐含 this 指针,必须显示绑定
  • 绑定数据成员。

若不想提前绑定参数,则使用占位符,占位符本身所在的位置是形参的位置,占位符的数字代表实参传递时候的位置,即函数调用时绑定实参列表中的对应实参位置。没有绑定的实参是无效参数。

bind 绑定参数采用值传递。

例如:

#include <functional>
 #include <iostream>
 using std::cout;
 using std::endl;
 using std::bind;
 using namespace std::placeholders;
 int add(int x, int y) {
     cout << "int add(int,int) = " << endl;
     cout << "(" << x << ", " << y << ")" << endl;
     return x + y;
 }
 struct MyTest {
     int add(int x, int y) { 
         cout << "MyTest::add(int,int)" << endl;
         return x + y + data;
     }
     int data = 1000; // C++11 特性
 };
 void func(int n1, int n2, int n3, const int & n4, int n5) {
     cout << "(" << n1 << "," << n2 << "," << n3 
          << "," << n4 << "," << n5 << ")" << endl;
 }
 void func(int n1, int n2, int n3, const int & n4, int n5) {
     cout << "(" << n1 << "," << n2 << "," << n3 
          << "," << n4 << "," << n5 << ")" << endl;
 }
 // 1、bind 绑定普通函数
 void test0() {
     // 1.1、绑定实参到函数对象,实参与形参必须一一对应
     auto f = std::bind(add, 1, 2);
     cout << f() << endl << endl;
     // 1.2、若不想提前绑定参数,使用占位符,表示调用时绑定实参列表的位置
     // 1.2.1、使用 bind 绑定 add 函数对象: arg1 = 1, 占位符 _1
     auto f1 = std::bind(add, 1, _1); 
     // 占位符 _1 表示绑定的是调用时实参列表的第一个位置, 即 (10, 11, 12) 中的 10
     // 输出结果:(1, 10), 11,12 为无效参数
     cout << f1(10, 11, 12) << endl; // 等价于 f1(10)
     // 1.2.2、使用 bind 绑定 add 函数对象: arg1 = 1,占位符 _2
     auto f2 = std::bind(add, 1, _2);
     // 占位符 _1 表示绑定的是调用时实参列表的第2个位置, 即 (10, 11, 12) 中的 11
     // 输出结果:(1, 11). 10, 12 为无效参数
     cout << f2(10, 11, 12) << endl; 
 } 
 // 2、bind 绑定成员函数和数据成员
 void test1() {
     MyTest mytest;
     // 2.1、绑定成员函数
     // 显示绑定 this 指针,需要保证其生命周期存在
     auto f = std::bind(&MyTest::add, &mytest, 1, 2);
     cout << f() << endl;        // 输出:3
     auto f1 = std::bind(&MyTest::add, &mytest, _1, 2);
     cout << f1(10) << endl;     // 输出 12
     auto f2 = std::bind(&MyTest::add, mytest, _1, _1);
     cout << f2(10, 20) << endl; // 输出 20
     // 2.2、绑定数据成员
     auto f3 = std::bind(&MyTest::data, mytest);
     cout << f3() << endl;       // 输出 1000
 }
 // 3、bind 绑定采用的是值传递
 void test2() {  
     int n = 100;
     // 值传递,传递对象时,会对对象本身进行复制
     auto f = std::bind(func, _2, 10, _1, std::cref(n), n);
     n = 111;  
     // 实参1对应形参_1,实参2对应形参_2,值传递 n 不变
     f(1, 2);  // 输出结果:(2,10,1,111,100)
 }
 int main(void) {
     test0(); 
     // test1();
     // test2(); 
     return 0;
 }

bind 简化原理

内部生成 Binder类,把要绑定的函数指针和函数参数提存储起来,并重载函数调用运算符

bind绑定普通函数

#include <functional>
 #include <iostream>
 using std::cout;
 using std::endl;
 int (* f)(int,int);              // 声明一个函数类型变量 f
 typedef int(*Function)(int,int);  // 声明一个函数类型
 int add(int x, int y) {
     int number ;
     cout << "int add(int,int)" << endl;
     cout << "(" << x << ", " << y << ")" << endl;
     return x + y;
 }
 int multiply(int x, int y) {    
     return x * y;   
 }
 // 1、C 语言的多态机制:函数指针
 // C 语言通过函数指针体现多态
 void test0() {
     // Function 是一个类型,func 是一个对象(变量)
     // 函数 add 和 multiply 是常量。编译完成后,函数入口地址就确定了
     Function func;
     // 1、注册回调函数 
     // 2、执行回调函数 
     func = add;    
     cout << func(1, 2) << endl;
     func = multiply;
     cout << func(3, 4) << endl;
 }
 // 类比:std::bind 绑定后,返回的函数对象
 struct Binder {
     Binder(int(*p)(int,int), int x, int y)
     : pFunc(p), a(x), b(y) {}
     int operator()() {
         return pFunc(a, b);
     }
     int (*pFunc)(int,int);
     int a;
     int b;
 };
 struct Binder mybind(int(*p)(int,int), int x, int y) {
     return Binder(p, x, y);
 }
 // 2、std::bind 绑定普通函数
 void test1() {
     auto f = std::bind(add, 1, 2);
     f();
     Binder binder(add, 1, 2);
     binder();
     auto f2 = mybind(add, 1, 2);
     f2();
 }

bind 绑定成员函数

struct Mytest {
     int add(int x, int y) {
         cout << "Mytest::aad(int,int)" << endl;
         return x + y;
     }
 };
 struct Mybinder {
     Mybinder(int(Mytest::*f)(int,int), Mytest t, int x, int y)
     : p(f), test(t), a(x), b(y) {}
     int operator()() {
         return (test.*p)(a, b);
     }
     int (Mytest::*p)(int,int);
     Mytest test;
     int a;
     int b;
 };
 // 2、std::bind 绑定成员函数
 void test2() {
     Mytest test;
     Mytest * ptest = &test;
      // 声明一个成员函数指针时,需要指定类作用域
      int (Mytest::*p)(int,int) = &Mytest::add;
      // 成员函数指针的调用形式
      // 2.1、通过对象调用成员函数指针, 采用 .*
      cout << (test.*p)(1, 2) << endl;
      // 2.2、通过对象的指针调用成员函数指针, 采用 ->*
      cout << (ptest->*p)(1, 2) << endl;
 }

3.2、mem_fn

成员函数绑定器

// 绑定成员函数,删除质数元素,并打印
 nums.erase(remove_if(nums.begin(), nums.end(), mem_fn(&Number::isPrime)), nums.end());
 std::for_each(nums.begin(), nums.end(), mem_fn(&Number::print));
相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
91 10
|
1月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
48 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
1月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
68 5
|
1月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
52 1
|
1月前
|
安全 测试技术 C++
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化2
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
61 6
|
1月前
|
安全 测试技术 C++
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化1
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
52 7
|
1月前
|
算法 安全 Linux
【C++STL简介】——我与C++的不解之缘(八)
【C++STL简介】——我与C++的不解之缘(八)
|
1月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
52 5
|
1月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
53 2
|
1月前
|
算法 数据处理 C++
c++ STL划分算法;partition()、partition_copy()、stable_partition()、partition_point()详解
这些算法是C++ STL中处理和组织数据的强大工具,能够高效地实现复杂的数据处理逻辑。理解它们的差异和应用场景,将有助于编写更加高效和清晰的C++代码。
22 0