《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.2、container 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.3、iterator 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.4、function 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
《STL源码剖析》——第七、八章:仿函数与接配器的更多相关文章
- stl源码剖析 详细学习笔记 仿函数
//---------------------------15/04/01---------------------------- //仿函数是为了算法而诞生的,可以作为算法的一个参数,来自定义各种操 ...
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
- 【转载】STL"源码"剖析-重点知识总结
原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...
- STL"源码"剖析
STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- 《STL源码剖析》读书笔记
转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...
- STL源码剖析之组件
本篇文章开始,进行STL源码剖析的一些知识点,后续系列笔记全是参照<STL源码剖析>进行学习记录的 STL在现在的大部分项目中,实用性已经没有Boost库好了,毕竟STL中仅仅提供了一些容 ...
- 面试题总结(三)、《STL源码剖析》相关面试题总结
声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...
- 通读《STL源码剖析》之后的一点读书笔记
直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adap ...
- 0《STL源码剖析》简介
STL源码剖析 ----侯捷 STL主要包括六个组件: 1.配置器:负责空间配置和管理. 2.迭代器:扮演容器和算法之前的胶合剂,所谓“泛型指针”. 3.容器:各种数据结构,如vector,list, ...
随机推荐
- vue传值(小demo)
vue+element ui实现的.解释大多在代码中(代码臭且长,有错误请指正)-- 代码如下: <template> <div class="userList" ...
- linux下查看Apache的访问日志及ip
linux下查看Apache的实时访问日志:tail -f /etc/httpd/logs/access_log 查看有哪些ip访问过:cat access_log |awk '{print $1} ...
- mybatis resultMap之collection聚集两种实现方式
最近做得项目用到了MyBatis处理一对多的映射关系,下面的两个方法中用到了集合的嵌套查询方法,下面仔细学习一下这两种方式 聚集元素用来处理"一对多"的关系.需要指定映射的Java ...
- Sql Server中的标识列(自增长字段)
一.标识列的定义以及特点 SQL Server中的标识列又称标识符列,习惯上又叫自增列.该种列具有以下三种特点: 1.列的数据类型为不带小数的数值类型2.在进行插入(Insert)操作时,该列的值是由 ...
- 03机器学习实战之决策树scikit-learn实现
sklearn.tree.DecisionTreeClassifier 基于 scikit-learn 的决策树分类模型 DecisionTreeClassifier 进行的分类运算 http://s ...
- 用python 写一个nagios插件 监控http内容(转载)
nagios自带的http-check插件主要是检测地址url是否可以访问,在web+中间件的架构中容易出现url能访问,但是后台中间件拓机的情况,因为最近在自学python,所以写了个脚本检测ur ...
- ACM-ICPC 2015 Changchun Preliminary Contest J. Unknown Treasure (卢卡斯定理+中国剩余定理)
题目链接:https://nanti.jisuanke.com/t/A1842 题目大意:给定整数n,m,k,其中1≤m≤n≤1018,k≤10, 然后给出k个素数,保证M=p[1]*p[2]……*p ...
- Linux 部署或升级openssh7.5p1
运维Linux系统,部署或升级openssh是经常面临的事,以下已redhat6和redhat7为例. 在redhat6中部署openssh会有什么坑,在编辑openssh源码包时会报一些类似的错误, ...
- 2018-09-10-weekly
Algorithm 删除链表的倒数第N个节点 What:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. How:这是一道典型的利用双指针法解题.首先让指针first指向头节点,然后 ...
- alert(1) to win 6
function escape(s) { // Slightly too lazy to make two input fields. // Pass in something like " ...