《C++ STL开发技术引导》 第五章-C++ STL泛化技术分析笔记

简介: 《C++ STL开发技术引导》 第五章-C++ STL泛化技术分析笔记

for_each的尝试

template<class InputIter, class T1, class T2>
T2 for_each(InputIter first, InputIter last, T1 f) {
  for (; first != last; ++first)
        f(*first);
    return f;
} 

在上面的for_each算法代码中,模版类型T1是函数f的一个类型,但在C++语言中,变量有类型,函数并没有类型,因此不能找到一个具体的C++类型来具现模板类型T1,这样繁华的算法实现是行不通的。

由此,自然会提出函数对象的编程模式,用一个结构体来封装函数,内部提供operator()操作符函数,支持传统函数形式的调用。如此,既解决了函数类型的问题,又可以继续使用f()形式的函数调用。与函数指针相比较,使用函数对象调用函数的速度更快,并且可拥有多个函数对象的实例。函数对象的一般定义如下,关键在内部要定义一个"()"操作符。

struct f {
  void operator()(T x) {......}     // 定义operator()函数,使f(T x)形式有意义
};

如下是SGI C++ STL提供的for_each算法代码。模板类型T1用Function代替,表明是一个函数对象,删去模板类型T2,返回函数对象。

template <class InputIter, class Function>
Function for_each(InputIter first, InputIter last, Function f) {
    for (; first != last; ++first)
        f(*first);
    return f;
}

for_each的实例

#include <iostream>
#include <algorithm>
using namespace std;
struct print {
    void operator()(int a, int b) { printf("%d\n", a + b)};     // 定义operator()函数,使f(T x)形式有意义
};
int main()
{
  int a[] = {1,2,3,4,5,6};
  const int len = sizeof(a) / sizeof(int);
  for_each(a, a +len, print());
  return 0;
}

为了方面对容器的元素进行基本的算术逻辑运算,C++ STL定义了一些常用的函数对象,供算法调用。这些函数对象都是相应二元关系函数的一个封装,从结构体binary_function继承过来。

binary_function结构体的作用是,提供参数类型和返回值类型的信息,以便在必要时可将二元函数对象转换为一元函数对象。如下是SGI C++ STL在stl_function.h文件提供的函数对象。

template <class Arg1, class Arg2, class Result>
struct binary_function {
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};
template <class T>
struct plus : public binary_function<T, T, T> {
  T operator()(const T& x, const T& y) const { return x +y; }
};
template minus : public binary_function<T, T, T> {
  T operator()(const T& x, const T& y) const { return x - y; }
};

适配器是一种泛化的模板类型。通过传递一个具现的C++类型给适配器的模板类型参数, 就可以生成一种新的类型,从而在已有类型的基础上完成新类型的构造。

适配器本身是一个新的自定义类型 class/struct,其中会包含一个或多个辅助的所适配的类型的成员,并对内含的成员的接口进行改造,再以新的适配器类型向外部提供接口。

适配器分为迭代器适配器、函数对象适配器和容器适配器。

一般而言,容器是以 class templates 完成,算法以 function templates 完成,仿函数是一种将 operator() 重载的 class template, 迭代器则是一种将 operator++ 和 operator* 等指针习惯常行为重载的 class template。

C++ STL的反向迭代器reverse_iterator提供字符串的尾端开始查找字符。reverse_iterator用类而不是结构体来实现,是由于内部需要用protected来控制变量的访问权限。

逆向迭代器并不改变存储中的实际位置,只是改变了逻辑位置。逆向迭代器 reverse_iterator 就是对 正向迭代器 iterator 的二次包装,在保持和利用正向迭代器的原有行为的同时,将一个正向迭代器转换为逆向迭代器,实现了逻辑上的相反遍历的功能。

template <class Iterator>
class reverse_iterator
{
protected:
  Iterator current;
public:     
    // 使用关键字typename说明其后标识符是一个类型,而不是类的变量,进而用typedef重定义位一个规范的类型名字
  typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
  typedef typename iterator_traits<Iterator>::value_type value_type;
  typedef typename iterator_traits<Iterator>::diffetence_type difference_type;
  typedef typename iterator_traits<Iterator>::pointer pointer;
  typedef typename iterator_traits<Iterator>::reference reference;
  typedef Iterator iterator_type;
  typedef reverse_iterator<Iterator> Self;
public:
  reverse_iterator{}
  explicit reverse_iterator(iterator_type x) :current(x) {}
  reverse_iterator(const Self& x) : current(x.current) {}
  iterator_type base() const { return current; }
  reference operator*() const {
    Iterator tmp = current;
    return *tmp;
  }
  Self& operatpr++() {  // 实际执行"--"
    --current;
    return *this;
  }
  Self operator++(int) {
    Self tmp = *this;
    --current;
    return tmp;
  }
  Self& operatpr--() {  // 实际执行"++"
    ++current;
    return *this;
  }
  Self operator--(int) {
    Self tmp = *this;
    ++current;
    return tmp;
  }
  Self operator+(difference_type n) const {
    return Self(current - n);
  }
  Self operator+=(difference_type n) {
    current -= n;
    return *this;
  }
  Self operator-(difference_type n) const {
    return Self(current + n);
  }
  Self operator-=(difference_type n) {
    current += n;
    return *this;
  }
  reference operator[](difference_type n) const {
    return *(*this + n);
  }
};

reverse_iterator例子

#include <iterator>
#include <iostream>
#include <vector>
int main(void)
{
  using namespace std;
  vector<int> v;
  v.push_back(3);
    v.push_back(6);
  v.push_back(9);
  reverse_iterator<vector<int>::iterator, int> rfirst(v.end());
  reverse_iterator<vector<int>::iterator, int> rend(v.begin());
  while(rfirst != rend) {
      cout << *rfirst << end;
      ++rfirst;
   }
   return 0;

下面再介绍一种迭代器适配器insert_iterator。与reverse_iterator不同,这个插入迭代器并不适用iterator来做模板类型,而是使用一个容器Container* container。insert_iterator可直接利用容器的插入函数,提供一种进行"="赋值插入方式。

对于 insert iterator,可以将一般迭代器的赋值(assign)操作,转化为插入(insert)操作。

insert_iterator的实现思路为:每个 insert_iterators 内部都维护一个由用户指定的容器和容器相应的迭代器;当客户端对 insert iterators 做赋值操作时,就在 insert_iterators 的 operator= 操作符中调用底层容器的 insert() 函数。对于 insert iterators 的前进、后退、取值、成员取用等操作都是关闭的,或是不允许的。

template <class Container>
class insert_iterator {
protected:
  Container* container;
  typename Container::iterator iter;
public:
  typedef Container container_type;   // 包含一个容器
  typedef output_iterator_tag iterator_category;
  typedef void value_type;
  typedef void difference_type;
  typedef void pointer;
  typedef void reference;
  insert_iterator(Container& x, typename Container::iterator i) : container(&x), iter(i) {}
  insert_iterator<Container&> operator=(const typename Container::value_type& value) {
    iter = container->insert(iter, value);    // 调用容器的insert()函数插入数据
    ++iter;                   // 继续移动迭代器
    return *this;
  }
  insert_iterator<Container>& operator*() { return *this; }
  insert_iterator<Container>& operator++() { return *this; }    //实际并没有移动
  insert_iterator<Container>& operator++(int) { return *this; }    //实际并没有移动
};

insert_iterator例子

List<int> L;
L.push_back(3);
insert_iterator<List<int>> ii(L, L.begin());
*ii++ = 0;
*ii++ = 1;
*ii++ = 2;

一些在算法中调用的函数对象,要求是一个返回bool值的一元函数对象,以使容器的每一个元素可以被这个一元函数对象进行判断处理。从下面的查找算法find_if的代码,可以看到代码中的函数对象pred,必须是一个返回bool值的单参数函数对象。

template<class InputIter, class Predicate>   // 返回bool值的函数对象,称为谓词判断Predicate
InputIter find_if(InputIter first, InputIter last, Predicate pred, input_iterator_tag)
{
  while(first != last && !pred(*first)) // 要求pred是返回bool值的一元函数(对象)
      ++first;
  return first;
}

一个返回bool值的函数对象,称为谓词判断Predicate,如greater、less、not_equal_to等基本的函数对象。

函数对象适配器的作用在于,将双参数的函数对象转化为单参数的函数对象,以适应算法的调用要求。

传递某个值给二元函数对象,也就是所谓的绑定,C++ STL提供了两种用于绑定的函数对象适配器:binder1st/bind1st和binder2nd/bind2nd,分别将数值绑定二元函数的第一个参数和第二个参数。

binder1st/bind1st、binder2st/bind2st的实现:

//一元函数结构
template <class Arg, class Result>
struct unary_function 
{
    typedef Arg argument_type; //参数类型,可以理解为x对应的类型
    typedef Result result_type;//返回值类型,可以理解为 z 对应的类型
};
//二元函数结构
template <class Arg1, class Arg2, class Result>
struct binary_function
 {
    typedef Arg1 first_argument_type; //第一个参数类型,可以理解为x对应的类型
    typedef Arg2 second_argument_type;//第二个参数类型,可以理解为y对应的类型
    typedef Result result_type; //返回值类型,可以理解为 z 对应的类型
};
// unary_function 是一元函数结构。Operation是两个参数的函数对象,binder1st是一个单参数对象,类型是Operation的第二参数的类型。
template <class Operation>
class binder1st : public unary_function<typename Operation::second_argument_type, typename Operation::result_type>
{
protected:
  Operation op;                 // 用于绑定二元函数对象,提供op()函数处理
  typename Operation::first_argument_type value;     // first_argument_type表示第一个参数类型,可以理解为x对应的类型
public:
  // y是指需要绑定的常数值
  binder1st(const Operation& x, const typename Operation::first_argument_type& y)
    : op(x), value(y) {}
  // operator()的返回值类型,调用op()
  typename Operation::result_type operator()(const typename Operation::second_argument_type& x) const {
    return op(value, x);      // value绑定到Operation第一个参数,x用作第二个参数
  }
};
template<class Operation, class T>
inline binder1st<Operation> bind1st(const Operation& fn, const T& x)
{
  typedef typename Operation::first_argument_type Arg1_type;
  return binder1st<Operation>(fn, Arg1_type(x));
}
template <class Operation>
class binder2nd : public unary_function<typename Operation::first_argument_type, typename Operation::result_type>
{
protected:
  Operation op;                 // 用于绑定二元函数对象,提供op()函数处理
  typename Operation::second_argument_type value;
public:
  // y是指需要绑定的常数值
  binder2nd(const Operation& x, const typename Operation::second_argument_type& y)
    : op(x), value(y) {}
  // operator()的返回值类型,调用op()
  typename Operation::result_type operator()(const typename Operation::first_argument_type& x) const {
    return op(value, x);      // value绑定到Operation第一个参数,x用作第二个参数
  }
};
template<class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation& fn, const T& x)
{
  typedef typename Operation::second_argument_type Arg2_type;
  return binder2nd<Operation>(fn, Arg2_type(x));
}

参考文档:https://www.cnblogs.com/chuyongliu/p/5308616.html

实例如下

#include <iostream>
#include <algorithm>  // count_if
#include <functional> // binder
#include <list>
using namespace std;
int main()
{
  // Data
  int iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  list<int> aList(iarray, iarray + 10);
  // binder和bind区别:
  // 1. 类绑定器有binder1st和binder2nd,而函数绑定器是bind1st和bind2nd
  // 2. bind是一个全局的模板函数其返回值为一个binder模板类的实例
  // 3. binder要指定泛型
  int k = count_if(aList.begin(), aList.end(), binder1st<greater<int>>(greater<int>(), 5));
  // bind1st bind2nd功能比较
  // k = count_if(aList.begin(), aList.end(), bind1st(greater<int>(), 5)); 
  // bind1st(greater<int>(), 5); //---->5 > x 即5作为第一个固定参数。返回5大于的数字的个数
  // bind2nd(greater<int>(), 5); //---->x > 5 即5作为第二个固定参。返回大于5的数字的个数
  cout << k << endl;
  system("pause");
  return 0;
}
相关文章
|
1天前
|
消息中间件 算法 Java
C++实时通信优化技术探究
C++实时通信优化技术探究
10 3
|
2天前
|
安全 Java 程序员
【C++笔记】从零开始认识继承
在编程中,继承是C++的核心特性,它允许类复用和扩展已有功能。继承自一个基类的派生类可以拥有基类的属性和方法,同时添加自己的特性。继承的起源是为了解决代码重复,提高模块化和可维护性。继承关系中的类形成层次结构,基类定义共性,派生类则根据需求添加特有功能。在继承时,需要注意成员函数的隐藏、作用域以及默认成员函数(的处理。此外,继承不支持友元关系的继承,静态成员在整个继承体系中是唯一的。虽然多继承和菱形继承可以提供复杂的设计,但它们可能导致二义性、数据冗余和性能问题,因此在实际编程中应谨慎使用。
5 1
【C++笔记】从零开始认识继承
|
6天前
|
存储 算法 程序员
C++从入门到精通:2.2.1标准库与STL容器算法深度解析
C++从入门到精通:2.2.1标准库与STL容器算法深度解析
|
7天前
|
存储 搜索推荐 C++
【C++高阶(二)】熟悉STL中的map和set --了解KV模型和pair结构
【C++高阶(二)】熟悉STL中的map和set --了解KV模型和pair结构
|
7天前
|
设计模式 C语言 C++
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
|
7天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
7天前
|
算法 C++ 容器
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
|
7天前
|
编译器 C++
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
|
7天前
|
存储 C语言 C++
【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现
【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现
|
7天前
|
算法 Linux C语言
【C++进阶(一)】STL大法以及string的使用
【C++进阶(一)】STL大法以及string的使用