《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;
}
相关文章
|
24天前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
55 21
|
1月前
|
存储 算法 安全
基于哈希表的文件共享平台 C++ 算法实现与分析
在数字化时代,文件共享平台不可或缺。本文探讨哈希表在文件共享中的应用,包括原理、优势及C++实现。哈希表通过键值对快速访问文件元数据(如文件名、大小、位置等),查找时间复杂度为O(1),显著提升查找速度和用户体验。代码示例展示了文件上传和搜索功能,实际应用中需解决哈希冲突、动态扩容和线程安全等问题,以优化性能。
|
2月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
42 1
|
2月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
69 7
|
3月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
135 4
|
3月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
140 5
|
3月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
89 2
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
67 0
|
2天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19