《STL源码剖析》——第七、八章:仿函数与接配器

第七章:仿函数

 7.1、仿函数(函数对象)概观

STL仿函数的分类,若以操作数(operand)的个数划分,可分为一元和二元仿函数,若以功能划分,可分为算术运算(Arithmetic)、关系运算(Rational)、逻辑运算(Logical)三大类。任何应用程序欲使用STL内建的仿函数,都必须含人<functiona1>头文件,SGI则将它们实际定义于<st1_function.h>文件中。以下分别描述。

重载 () 所以函数的对象 使用()像函数调用

是类 而不是普通的函数

内部记录状态:

作为类型 与模板进行配合使用

1、函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题。

2、函数对象超出普通函数的概念,函数对象可以有自己的状态

3、函数对象可内联编译,性能好。用函数指针几乎不可能

4、模版函数对象使函数对象具有通用性,这也是它的优势之一

 7.2、可配接(adaptable)的关键

  • unary_function

unary_function用来呈现一元函数的参数型别和回返值型别。其定义非常简单:

  • binary_function

binary_function 用来呈现二元函数的第一参数型别、第二参数型别,以及回返值型别。其定义非常简单:

 7.3、算术类(Arithmetic)仿函数

STL内建的“算术类仿函数”,支持加法、减法、乘法、除法、模数(余数,modulus)和否定(negation)运算。除了“否定”运算为一元运算,其它都是二元运算。

·加法:plus<T>

·减法:minus<T>

·乘法:multiplies<T>

·除法:divides<T>

·模取(modulus):modulus<T>

·否定(negation):negate<T>

使用:

 7.4、关系运算类(Relational)仿函数

STL内建的“关系运算类仿函数”支持了等于、不等于、大于、大于等于、小于、小于等于六种运算。每一个都是二元运算。

·等于(equality):equal_to<T>

·不等于(inequality):not_equal_tocT>

·大于(greater than):greater<T>

·大于或等于(greater than or equal):greater_-equal<T>

·小于(less than):1ess<T>

·小于或等于(less than or equal):1ess_equal<T>

使用:

 7.5、逻辑运算类(Logical)仿函数

STL内建的“逻辑运算类仿函数”支持了逻辑运算中的 And、or、Not三种运算,其中And和or为二元运算,Not为一元运算。

·逻辑运算And:1ogical_and<T>

·逻辑运算or:1ogical_or<T>

·逻辑运算Not:logical_not<T>

使用:

 7.6、证同(identity)、选择(select)、投射(project

  • identity
  • select
  • project

 7.7、自建函数function

  • 包装普通函数

int g_Minus(int i, int j)

{

return i - j;

}

int main()

{

function<int(int, int)> f = g_Minus;

cout << f(1, 2) << endl;                                            // -1

}

  • 包装模板函数

template <class T>

T g_Minus(T i, T j)

{

return i - j;

}

int main()

{

function<int(int, int)> f = g_Minus<int>;

cout << f(1, 2) << endl;                                            // -1

return 1;

}

  • 包装lambda表达式

auto g_Minus = [](int i, int j){ return i - j; };

int main()

{

function<int(int, int)> f = g_Minus;

cout << f(1, 2) << endl;                                            // -1

return 1;

}

  • 包装函数对象

非模板类型:

struct Minus

{

int operator() (int i, int j)

{

return i - j;

}

};

int main()

{

function<int(int, int)> f = Minus();

cout << f(1, 2) << endl;                                            // -1

return 1;

}

模板类型:

template <class T>

struct Minus

{

T operator() (T i, T j)

{

return i - j;

}

};

int main()

{

function<int(int, int)> f = Minus<int>();

cout << f(1, 2) << endl;                                            // -1

return 1;

}

  • 包装类静态成员函数

非模板类型:

class Math

{

public:

static int Minus(int i, int j)

{

return i - j;

}

};

int main()

{

function<int(int, int)> f = &Math::Minus;

cout << f(1, 2) << endl;                                            // -1

return 1;

}

模板类型:

class Math

{

public:

template <class T>

static T Minus(T i, T j)

{

return i - j;

}

};

int main()

{

function<int(int, int)> f = &Math::Minus<int>;

cout << f(1, 2) << endl;                                            // -1

return 1;

}

  • 包装类对象成员函数

非模板类型:

class Math

{

public:

int Minus(int i, int j)

{

return i - j;

}

};

int main()

{

Math m;

function<int(int, int)> f = bind(&Math::Minus, &m, placeholders::_1, placeholders::_2);

cout << f(1, 2) << endl;                                            // -1

return 1;

}

模板类型:

class Math

{

public:

template <class T>

T Minus(T i, T j)

{

return i - j;

}

};

int main()

{

Math m;

function<int(int, int)> f = bind(&Math::Minus<int>, &m, placeholders::_1, placeholders::_2);

cout << f(1, 2) << endl;                                            // -1

return 1;

}

x章:匿名函数(lambda

  • 格式: [](){};

[ ](int val){ cout << val ""; }

//匿名函数  lambda表达式  [](){};

for_each(v.begin(), v.end(), [](int val) { cout << val << " "; });

  • 捕获:需要捕获的参数放置在[ ]中!!!

[a](int b){return a+b;}

[ ]中为需要捕获的参数,一般存在与函数体中!!!

  • 传参放置在()中:

int a = 45;

int sum = [a](int b){return a+b;};

cout << sum (14)<< endl;

注意:

捕获参数a需自己定义,调用函数不需写明!

而传参需要调用函数传入进去!!!

( )中一般使用的是容器中的元素。

eg:

string ss;

float f;

map<int, string>m;

vecot<float>v;

auto pt=find_if(m.begin(), m.end(), [ss](pair<int, string>ps){return ps.second==ss; });

auto pt=find_if(v.begin(), v.end(), [=](float fa){return fa == f;);

  • lambda表达式语法定义

  lambda表达式的语法定义如下:

  [capture] (parameters) mutable ->return-type {statement};

(1) [capture]: 捕捉列表。捕捉列表总是出现在lambda函数的开始处。实质上,[]是lambda引出符(即独特的标志符)

  编译器根据该引出符判断接下来的代码是否是lambda函数

  捕捉列表能够捕捉上下文中的变量以供lambda函数使用

  捕捉列表由一个或多个捕捉项组成,并以逗号分隔,捕捉列表一般有以下几种形式:

  • []:默认不捕获任何变量;
  • [=]:默认以值捕获所有变量;
  • [&]:默认以引用捕获所有变量;
  • [x]:仅以值捕获x,其它变量不捕获;
  • [&x]:仅以引用捕获x,其它变量不捕获;
  • [=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
  • [&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
  • [this]:通过引用捕获当前对象(其实是复制指针);
  • [*this]:通过传值方式捕获当前对象;

  <1> [var] 表示值传递方式捕捉变量var

  <2> [=] 表示值传递方式捕捉所有父作用域的变量(包括this指针)

【即该作用域中说所有的变量】

  <3> [&var] 表示引用传递捕捉变量var

  <4> [&] 表示引用传递捕捉所有父作用域的变量(包括this指针)

讲解一下使用&的作用

int main()

{

int i=1234,j=5678,k=9;

std::function<int()> f=[=,&j,&k]{return i+j+k;};

i=1;

j=2;

k=3;

std::cout<<f()<<std::endl;

}

输出为:1234,记住,当生命lambda声明时,参数已经捕获完毕,即i为值传递,不可改变,j,k为引用传递,可以改变,故最终传入的参数为:

i=1234, j=2, k=3;

  <5> [this] 表示值传递方式捕捉当前的this指针

当要使用类成员时,不能使用[=]进行捕获,需使用[this]来捕获

  <6> [=,&a,&b] 表示以引用传递的方式捕捉变量 a 和 b,而以值传递方式捕捉其他所有的变量

  <7> [&,a,this] 表示以值传递的方式捕捉 a 和 this,而以引用传递方式捕捉其他所有变量

  备注:父作用域是指包含lambda函数的语句块{ }

  另外,需要注意的是,捕捉列表不允许变量重复传递。下面的例子就是典型的重复,会导致编译错误:

  [=, a] 这里 = 已经以值传递方式捕捉了所有的变量,那么再捕捉 a 属于重复

  [&,&this] 这里 & 已经以引用传递方式捕捉了所有变量,那么再捕捉 this 属于重复

(2)(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号

()一起省略

(3)mutable : mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性(后面有详解)

  在使用该修饰符时,参数列表不可省略(即使参数为空)

(4)->return-type : 返回类型。用追踪返回类型形式声明函数的返回类型。

  出于方便,不需要返回值的时候也可以连同符号->一起省略

  此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导

(5){statement} : 函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量

  在lambda函数的定义中,参数列表和返回类型都是可选的部分,而捕捉列表和函数体都可能为空

  那么,在极端情况下,C++11中最为简单的lambda函数只需要声明为:

  [] {};

  • 当使用多个捕获时

当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=。此符号指定了默认捕获方式为引用或值。

当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因此不能在其名字前使用&。类似的,如果隐式捕获采用的是值方式(使用了=),则显式捕获命名变量必须采用引用方式,即,在名字前使用&。

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表:

auto f=[v1]()mutable{return ++vl;};

  • 声明返回类型:

默认返回为void类型,当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型;

[](int i)->int{if (i<0)return-i;else return i;}

第八章:配接器(adapters)

 8.1、配接器之概观与分类

  • 应用于容器,container adapters
  • 应用于迭代器,iterator adapters
  • 应用于仿函数,functor adapters

 8.2container adapters

  • stack

stack的底层由deque构成。从以下接口可清楚看出stack与deque的关系:

template <class T,class Sequence= deque<T>>

class stack{

protected:

Sequence c;//底层容器

};

  • queue

queue的底层由deque构成。从以下接口可清楚看出queue与deque的关系:

template <class T,class Sequence=deque<T>>

class queue{

protected:

Sequence c;//底层容器

};

 8.3iterator adapters

  • insert iterators
  • reverse iterators

所谓 reverse iterator,就是将迭代器的移动行为倒转。如果STL算法接受的不是一般正常的迭代器,而是这种逆向迭代器,它就会以从尾到头的方向来处理序列中的元素。例如:

//将所有元素逆向拷贝到ite所指位置上

//rbegin()和rend()与reverse_iterator有关copy(id.rbegin(),id.rend(),ite);看似单纯,实现时却大有文章。

  • stream iterators

所谓 stream iterators,可以将迭代器绑定到一个stream(数据流)对象身上。

绑定到istream对象(例如std::cin)者,称为istream iterator,拥有输人能力;

绑定到ostream对象(例如std::cout)者,称为ostream_iterator,拥有输出能力。

 8.4function adapters

  • 对返回值进行逻辑否定:not1,not2
  • 对参数进行绑定:bindls t,bind2nd
  • 用于函数合成:compose1,compose2
  • 用于函数指针:ptr_fun

定义一个函数指针类型。

比如你有三个函数:

void hello(void) { printf("你好!"); }

void bye(void) { printf("再见!"); }

void ok(void) { printf("好的!"); }

typdef void (*funcptr)(void);

typede[函数返回类型][*函数指针名][函数参数类型]

这样就构造了一个通用的函数

你用的时候可以这样:

void speak(int id)

{

funcptr words[3] = {&hello, &bye, &ok};//将函数指针存入

funcptr fun = words[id];

(*fun)();

}

speak(0)就会显示“你好!”;

speak(1)就会显示“再见!”;

speak(2)就会显示“好的!”

void sayHello(){}

int main() {

void (*sayHelloPtr)() = sayHello;   //其中,括号是必不可少的

(*sayHelloPtr)();

}

  • 用于成员函数指针:memfun,mem fun_ref

原文地址:https://www.cnblogs.com/zzw1024/p/12094369.html

时间: 2025-01-12 21:33:04

《STL源码剖析》——第七、八章:仿函数与接配器的相关文章

stl源码剖析 详细学习笔记 仿函数

//---------------------------15/04/01---------------------------- //仿函数是为了算法而诞生的,可以作为算法的一个参数,来自定义各种操作,比如比大小,返回bool值,对元素进行操作等 //虽然这些函数也能实现,但是如果配合配接器(adapter)可以产生更灵活的变化. //为了使对象像函数一样,就必须重载operator() //unary_function template<class Arg, class Result> s

STL&quot;源码&quot;剖析-重点知识总结

STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.search.copy.erase.从实现的角度来看,STL算法

通读《STL源码剖析》之后的一点读书笔记

[QQ群: 189191838,对算法和C++感兴趣可以进来] 直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adaptors).算法(algorithms).仿函数(functors)六个部分. 迭代器和泛型编程的思想在这里几乎用到了极致.模板或者泛型编程其实就是算法实现时不指定具体类型,而由调用的时候指定类型,进行特化.在STL中,迭代器保证了ST

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

《STL源码剖析》---stl_hashtable.h阅读笔记

在前面介绍的RB-tree红黑树中,可以看出红黑树的插入.查找.删除的平均时间复杂度为O(nlogn).但这是基于一个假设:输入数据具有随机性.而哈希表/散列表hash table在插入.删除.查找上具有"平均常数时间复杂度"O(1):且不依赖输入数据的随机性. hash table的实现有线性探测.二次探测.二次散列等实现,SGI的STL是采用开链法(separate chaining)来实现的.大概原理就是在hash table的每一项都是个指针(指向一个链表),叫做bucket.

【转载】STL&quot;源码&quot;剖析-重点知识总结

原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.se

STL源码剖析——STL算法之find查找算法

前言 由于在前文的<STL算法剖析>中,源码剖析非常多,不方便学习,也不方便以后复习,这里把这些算法进行归类,对他们单独的源码剖析进行讲解.本文介绍的STL算法中的find.search查找算法.在STL源码中有关算法的函数大部分在本文介绍,包含findand find_if.adjacent_find.search.search_n.lower_bound. upper_bound. equal_range.binary_search.find_first_of.find_end相关算法,下

STL源码剖析——STL算法之remove删除算法

前言 由于在前文的<STL算法剖析>中,源码剖析非常多,不方便学习,也不方便以后复习,这里把这些算法进行归类,对他们单独的源码剖析进行讲解.本文介绍的STL算法中的remove删除算法,源码中介绍了函数remove.remove_copy.remove_if.remove_copy_if.unique.unique_copy.并对这些函数的源码进行详细的剖析,并适当给出使用例子,具体详见下面源码剖析. remove移除算法源码剖析 // remove, remove_if, remove_co

STL源码剖析——STL算法stl_algo.h

前言 在前面的博文中剖析了STL的数值算法.基本算法和set集合算法,本文剖析STL其他的算法,例如排序算法.合并算法.查找算法等等.在剖析的时候,会针对函数给出一些例子说明函数的使用.源码出自SGI STL中的<stl_algo.h>文件.注:本文的源码非常多,可能后续博文会对这些算法进行归类分析. STL算法剖析 #ifndef __SGI_STL_INTERNAL_ALGO_H #define __SGI_STL_INTERNAL_ALGO_H #include <stl_heap

《STL源码剖析》---stl_numeric.h阅读笔记

stl_numeric.h里面的都是数值算法,与数值计算有关. G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_numeric.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any pu