文档章节

C++ 仿函数和适配器

o
 osc_z1hvg4cu
发布于 2018/04/24 18:05
字数 1729
阅读 7
收藏 0

精选30+云产品,助力企业轻松上云!>>>

本文从不断复杂的应用场景入手,来说明C++设计仿函数和适配器的原因,并深入源码来介绍仿函数和适配器的使用方法。

#仿函数

现有一个vector<int>,需要统计大于8的元素个数。 使用std::count_if,代码可能长这个样子。

bool greaterThan8(int n){
	return n > 8;
}
int count = count_if(vec.begin(), vec.end(), greaterThan8);

count_if第三个参数接收的是一个函数指针,并且这个函数指针只接收一个参数,返回值是bool。 count_if是将参数1和参数2之间的每一项都会作为参数去调用第三个参数,然后返回第三个参数每一次true的次数。

这里是将8直接写在这里,并不符合编程规则,也不容易维护。

或者新加个参数

bool greaterThanN(int n, int x){
	return n > x;
}

看起来可以解决问题。 但是上文说了,count_if的第三个参数函数指针,只能接收一个参数。

如何取消这个参数呢? 1、全局变量

int minValue = 8;
bool greaterThanN(int n){
	return n > minValue ;
}

能解决问题,但是并不优雅。 1、容易出错   为什么这么说呢,我们必须先初始化maxLength的值,才能继续接下来的工作,如果我们忘了,则结果未定义。 此外,变量maxLength和函数LengthIsLessThan之间是没有必然联系的,编译器无法确定在调用该函数前是否将变量初始化。 需要使用者自己控制,则增加维护成本和出错可能性。

2、没有可扩展性   如果我们每遇到一个类似的问题就新建一个全局变量,尤其是多人合作写代码时,很容易引起命名空间污染(namespace polution)的问题。

3、全局变量的问题   全局变量花销很大,一般是编程过程中极力避免的

\#define VALUE 8
bool greaterThanN(int n){
	return n > VALUE;
}

换成宏是节省了全局变量的代价。但是依然不够优雅,容易出错且可扩展性差。

3.成员变量 我们既想要它既能想普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。 如果存成成员变量,那成员函数调用不就是很正常的事了吗?

对,为了实现上面的内容,C++语言引入了仿函数的概念。 仿函数(functor),其实是像函数一样使用的对象,也被称为函数对象(function object)。仿函数里面重载了"()"操作符,是我们可以像调用函数一样调用它。

仿函数的基本使用

class functor {
public:
        functor(int x) { value =x; }
	void operator()(const int& x) { cout << x+value << endl;}
private:
        int value;
};

functor fun(10);
fun(5); //15

为了满足文初的应用场景,使用仿函数的代码,大概长这样

template<typename T>
class greater{
public:
	greater(const T&x) { info = x; }
	bool operator()(const T& x) { return info > x; }
private:
	T info;
};

调用代码

greater<int> ge(20);
int count = count_if(vec.begin(), vec.end(), ge);

或者直接使用临时变量

int count = count_if(vec.begin(), vec.end(), greater<int>(20));

当然,这里可以不用模板,但是为了扩展和方便,建议还是养成用模板的习惯。

另外,不用普通函数指针是因为函数指针的局限:

  1. 不能满足STL的抽象要求 参数,返回值如何设置
  2. 无法和STL其他组件交互
template<typename _Kty, typename _Pr = less<_Kty>,...>
class set{...};

class Person{...};

std::set<Person, std::less<Person> > set1, set2; //operator<做排序行为
std::set<Person, std::greater<Person> >set3,set4;//operator>做排序行为

set1 = set2;  //正确,相同的型别
set1 = sete3; //错误,不同的型别!

STL中也预先实现了一些仿函数,需要包头文件#include<functional>

另外在c++11里面可以通过lambda表达式解决上述问题:

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
int main()
{
    std::vector<int> c{ 1, 5, 3, 4, 5, 6, 7 };
    int x = 5;
    int k=std::count_if(c.begin(), c.end(), [x](int n){return x == n; });
    cout << k << endl;
    return 0;
}

#适配器 ###bind1st/bind2nd

vector<int> vec{0,0,0,10};

怎么找到第一个非0元素? 通过std::not_equal_to,代码应该是下面这样

std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), 
  std::not_equal_to<int>(0));

无法通过编译。为什么?

  • not_equal_to结构没有一个参数的构造函数
  • not_equal_tooperator()需要接受两个参数
template<class _Ty = void>
	struct not_equal_to
	{	// functor for operator!=
	typedef _Ty first_argument_type;
	typedef _Ty second_argument_type;
	typedef bool result_type;

	_CONST_FUN bool operator()(const _Ty& _Left, const _Ty& _Right) const
		{	// apply operator!= to operands
		return (_Left != _Right);
		}
	};

现在,这里需要一个插座,也就是适配器模式里的适配器。

typename std::not_equal_to<int>::first_argument_type nonZeroItem(0);
std::not_equal_to<int> f;
std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
	std::binder1st<std::not_equal_to<int> >(f, nonZeroItem));

具体调用如下

使用f, nonZeroItem来构造std::not_equal_to<int>类型的bind1stoperator()实际上调用的是std::not_equal_to<int> operator()

为了方便使用,std使用bind1st进行了封装

template<class _Fn2,
	class _Ty> inline
	binder1st<_Fn2> bind1st(const _Fn2& _Func, const _Ty& _Left)
	{	// return a binder1st functor adapter
	typename _Fn2::first_argument_type _Val(_Left);
	return (binder1st<_Fn2>(_Func, _Val));
	}

那我们的代码应该会变成这个样子

	std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
		std::bind1st(std::not_equal_to<int>(), 0));

binder2ndbinder1st类似。区别只在于_Func操作的是左值还是右值。 也就是说以下两句代码的同义的

std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
		std::bind1st(std::less<int>(), 0));  //std::bind2nd(std::greater<int>(), 0)

std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
		std::bind2nd(std::greater<int>(), 0));

bind1st操作的是左值,找到第一个X,使满足0 < X bind2nd操作的是右值,找到第一个X,使满足X > 0

###mem_fun/mem_fun_ref 设计和上面类似,是用于适配对象的函数

需求:打印vector中所有Person类的成员

	std::vector<Person*> vec;
	vec.push_back(new Person("I",1));
	vec.push_back(new Person("love", 2));
	vec.push_back(new Person("u", 3));
	vec.push_back(new Person("so much", 4));

	std::for_each(vec.begin(), vec.end(), &Person::Print); //编译不通过

如何能在函数中调用对象的成员函数呢? 首先,有对象obj,和函数fun,调用方法有如下几种:

1. fun(obj)  //fun是全局函数,非成员函数
2. obj.fun()  //obj非指针,fun是成员函数
3. obj->fun() //obj指针对象,fun是成员函数

for_each会调用_For_each 只接收fun(obj)的调用

所以代码应该是下面这个样子

std::for_each(vec.begin(), vec.end(), std::mem_fun(&Person::Print));

具体调用

mem_fun_ref类似,但是用于容器中存放的不是指针的情况

释放内存

方法一
	for (vector<Person*>::iterator it = vec.begin(); it != vec.end(); ++it)
		delete (*it);
	vec.clear();

方法二
struct DeleteItem {
	template<typename _Ty>
	void operator()(const _Ty p) const
	{
		delete (p);
	}
};
std::for_each(vec.begin(), vec.end(), DeleteItem());
vec.clear();

#####注意 1、有继承的情况下,容器存放指针而不是对象

  • 对象拷贝比指针拷贝对象大得多
  • 父对象的容器存放子对象时,数据会Slice

2、使用指针记得释放内存 3、尽量用算法代替手写循环 代码更优雅,据说速度更快,但是还没测试过

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
C++ STL概论

STL六大组件介绍 STL(Standard Template Library) 是一个组件集合,共包含以下六大组件,彼此可以组合套用: 1、容器(containers):包含各种数据结构,如vector、list、deque、set、map,用...

Spring_24
04/08
0
0
函数对象

本文乃作者学习《C++标准程序库》的学习笔记,首先介绍了仿函数(函数对象)和函数适配器(配接器)的概念,然后列出STL中所有的仿函数,以及函数适配器,并摘录了几个例子演示仿函数和函数适...

romalin99
2015/04/02
3
0
C++模板技术与STL实战开发-夏曹俊-专题视频课程

C++模板技术与STL实战开发—1006人已学习 课程介绍 泛型编程(Generic Programming)是一种全新的程序设计思想,它和STL是现代C++的精髓。可以说,不会STL很难称得上掌握了现代C++编程。STL...

夏曹俊
06/30
0
0
C++ STL介绍——简介

@[TOC]目录 1、什么是STL STL,即标准模板库,是一个具有工业强度的,高效的C++ 程序库。它被容纳于C++ 标准程序库中,是 标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领...

osc_arhujamz
2019/07/26
1
0
STL仿函数与函数对象浅析

例如,一个很简单问题,求两个数中的较大数,用C++代码很好写,如下: 仿函数 对于这种问题,如果你想把代码写的让人捉摸不透,怎么搞呢?那么就用STL仿函数functor来实现,也可以称之为函数...

令狐掌门
05/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

R中“ =”和“ <-”赋值运算符有什么区别?

问题: What are the differences between the assignment operators = and <- in R? R中赋值运算符=和<-之间有什么区别? I know that operators are slightly different, as this example ......

fyin1314
25分钟前
12
0
JavaScript中的静态变量 - Static variables in JavaScript

问题: 如何在Javascript中创建静态变量? 解决方案: 参考一: https://stackoom.com/question/6RUF/JavaScript中的静态变量 参考二: https://oldbug.net/q/6RUF/Static-variables-in-Java...

法国红酒甜
今天
14
0
之间的区别 和

问题: I'm learning Spring 3 and I don't seem to grasp the functionality behind <context:annotation-config> and <context:component-scan> . 我正在学习Spring 3,并且似乎不太了解<......

javail
今天
15
0
业内首款,百度工业视觉智能平台全新亮相

本文作者:y****n 业内首款全国产化工业视觉智能平台——百度工业视觉智能平台亮相中国机器视觉展(Vision China),该平台所具有的核心AI能力完全自主可控,在质检、巡检等场景中具有高效、...

百度开发者中心
昨天
7
0
我们如何制作xkcd样式图? - How can we make xkcd style graphs?

问题: Apparently, folk have figured out how to make xkcd style graphs in Mathematica and in LaTeX . 显然,民间已经想出了如何在Mathematica和LaTeX中制作xkcd风格的图形。 Can we d......

富含淀粉
今天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部