14.8.0 引入

函数对象

我们可以重载类类型的函数调用运算符,进而像使用函数一样使用这个类

那么这个类既可以存储状态,又可以当函数使用,十分灵活

//定义一个类,让它起到一个求绝对值函数的作用
struct absInt{
int operator()(int val) const {
return val<0? -val : val;
}
}; //使用
int main(){
int i = -42;
absInt absObj;
int ui = absObj(i);
cout<<ui<<endl;
return 0;
}

函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符[这样就像函数的重载],相互之间应该在参数数量或类型上有所区别。

如果类定义了调用运算符,则该类的对象称作函数对象( function object)。因为可以调用这种对象,所以我们说这些对象的“行为像函数一样”。

含有状态的函数对象

举个例子,我们将定义一个打印 string 实参内容的类。默认情况下,我们的类会将内容写入到cout中,每个string之间以空格隔开。同时也允许类的用户提供其他可写入其他分隔符。我们将该类定义如下

class PrintString{
public:
PrintString(ostream &o = cout, char c = ' ') :
os(o), sep(c){};
void operator()(const string &s) const{
os<<s<<sep;
}
private:
ostream &os;
char sep;
}; //上述的类等价于下面的函数
void PrintString_Function(const string &s,
ostream &os = cout, char sep = ' '){
os<<s<<sep;
} int main(){
string s("Hello World!");
PrintString ps;
ps(s);
return 0;
}

函数对象常做泛型算法实参

和lambda一样,函数对象常做泛型算法的实参

如for_each

for_each(vs.begin(), vs.end(), PrintString(cout,'\n'));

//等价于
for_each(vs.begin(), vs.end(),
[](const string &s){cout<<s<<endl;});

14.8.1 lambda是函数对象

表示没有捕获值的lambda的类

在第十章泛型算法的“10-3定制操作lambda”中我们介绍了lambda的使用,但其实lambda本质上就是重载了()的未命名类的未命名对象,如10-3的例子

Stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{return a.size() < b.size();}); //lambda的行为类似于下面的类的一个未命名对象
class ShorterString{
bool operator()(const string &a, const string &b)
{ return a.size()<b.size();}
}; //Stable_sort还可以写为
Stable_sort(words.begin(), words.end(), ShorterString());

表示lambda及其捕获行为的类

众所周知,lambda有两种捕获行为:引用捕获和值捕获

  1. 引用捕获时:程序负责确保执行时引用的对象确实存在,所以可以直接使用该对象的引用,而不必在lambda产生的类中存储为数据成员
  2. 值捕获时:lambda必须将每个值捕获的对象保存为数据成员
//获得第一个指向满足条件元素的迭代器,该元素满足size()>=sz
auto wc = find_if(words.begin(), words.end(),
[sz](const string &s)
{return s.size() >= sz;}); //lambda等价于下面的类的未命名对象
class SizeComp{
public :
SizeComp(int n) : sz(n){}
//该调用类型的形参,函数体和返回类型都和lambda一致
bool operator()(const string s){
return s.size() >= sz;
}
private:
int sz;
}; //等价调用
auto wc = find_if(words.begin(), words.end(),
SizeComp(sz));

14.8.2 标准库定义的函数对象

标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类

例如:

  1. plus类定义了一个函数调用运算符用于对一对运算对象执行+的操作;
  2. modulus类定义了一个调用运算符执行二元的%操作;
  3. equal_to类执行==

这些类都被定义成模板的形式

例如:

  1. plus<string>令string加法运算符作用于string对象;
  2. plus<int>的运算对象是int;
  3. plus<sales_data>对 sales_data对象执行加法运算

在算法中使用高标准库函数

标准库规定其函数对象对于指针同样适用。

我们之前曾经介绍过比较两个无关指针将产生未定义的行为(参见3.5.3节,第107页),然而我们可能会希望通过比较指针的内存地址来sort指针的vector。

直接这么做将产生未定义的行为,因此我们可以使用一个标准库函数对象来实现该目的:

14.8.3 可调用对象与function

调用形式

C语言中可调用对象

  1. 函数和函数指针
  2. lambda
  3. 重载了函数调用运算符的类

不同的调用对象可能有相同的调用形式,如

//普通函数
int add(int i, int j) { return i+j;}
//lambda,产生一个未命名的函数对象类
auto mod = [](int i, int j){ return i%j;};
//函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};

上述三种对象都享有相同的调用形式

int ( int, int)

用function统一调用形式

function定义在头文件functional中

function<int(int, int)> f1 = add; //函数指针
function<int(int, int)> f2 = divide(); //函数对象类的对象
function<int(int, int)> f3 = [](int i, int j)//lambda
{return i*j;}; cout<<f1(4,2)<<endl; //打印6
cout<<f2(4,2)<<endl; //打印2
cout<<f3(4,2)<<endl; //打印8

用function建立函数表

函数表(function table)中不知包含函数,而是包含各种可调用对象(函数,lambda,函数对象类的对象)

//列举了可调用对象与二元运算符对应关系的表格
//所有可调用对象都必须接受两个int、返回一个int
//其中的元素可以是函数指针、函数对象或者lambda
map<string ,function<int(int, int)> binops = {
{"+", add}, //函数指针
{"-", minus<int>()}, //标准库函数对象
{"/", divide()}, //用户定义的函数对象
{"*",[](int i, int j){return i*j;}}, //未命名lambda
{"%", mod} //命名了的lambda对象
}; //使用:
binops["+"](10,5); //调用add(10,5)
binops["-"](10,5); //使用minus<int>对象的调用运算符
binops["/"](10,5); //使用divide对象的调用运算符
binops["*"](10,5); //调用lambda函数对象
binops["%"](10,5); //调用lambda函数对象

14-8 C++函数调用运算符的更多相关文章

  1. C++ Pirmer : 第十四章 : 重载运算符与类型转换之函数调用运算符与标准库的定义的函数对象

    函数调用运算符 struct test { int operator()(int val) const { return (i > 0 ? i : -i); } }; 所谓的函数调用就是一个类重 ...

  2. 函数调用运算符"()"

    14.8函数调用运算符"()"1.函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,但是他们相互之间应该在参数数量或返回类型上有所区别.定义了调用运算符的类的对 ...

  3. C++ //函数调用运算符重载 (仿函数)

    1 //函数调用运算符重载 2 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 //函 ...

  4. C++赋值运算符、函数调用运算符、下标运算符(“=”、“()”、“[]”)重载

    #include <iostream>#include <assert.h>#include <string.h> using namespace std; cla ...

  5. c/c++ 重载运算符 函数调用运算符

    重载运算符 函数调用运算符 把一个类的对象a,当成函数来使用,比如a(),所以需要重载operator()方法.重载了函数调用运算符的类的对象,就是函数对象了. 还有什么是函数对象呢??? lambd ...

  6. [C++] 重载运算符与类型转换(2)——函数调用运算符和类型转换运算符

    1.这两个应该是C++中比较高级的用法了. 一.函数调用运算符   1.重载函数调用运算符(),必须是成员函数,一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或者类型上有所区别.   ...

  7. [C++ Primer] : 第14章: 重载运算符与类型转换

    基本概念 重载运算符是具有特殊名字的函数: 它们的名字由关键字operator和其后要定义的运算符号共同组成. 重载运算符函数的参数数量与该运算符作用的运算对象数量一样多. 对于二元运算符来说, 左侧 ...

  8. C++学习之路—运算符重载(二)运算符重载作为类的成员函数和友元函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 对运算符重载的函数有两种处理方式:(1)把运算符 ...

  9. C++ Primer 5th 第14章 重载运算与类型转换

    当运算符作用域类类型的对象时,可以通过运算符重载来重新定义该运算符的含义.重载运算符的意义在于我们和用户能够更简洁的书写和更方便的使用代码. 基本概念 重载的运算符是具有特殊名字的函数:函数名由关键词 ...

  10. [转]C++之运算符重载(1)

    在前一节中曾提到过,C++中运行时的多态性主要是通过虚函数来实现的,而编译时的多态性是由函数重载和运算符重载来实现的.这一系列我将主要讲解C++中有关运算符重载方面的内容.在每一个系列讲解之前,都会有 ...

随机推荐

  1. springboot2集成oauth2坑二(The bean 'scopedTarget.oauth2ClientContext', defined in class path resource )

    码云地址:https://gitee.com/lpxs/lp-springcloud.git 有问题可以多沟通:136358344@qq.com. 由于使用Enableoauth2sso注解一直报错, ...

  2. 使用Power Shell 拉取项目源代码

    使用 Power Shell 下载代码 工具:Power Shell(win10 自带) 新建源码的存放地址 1.使用 CD 命令进入文件夹 2.使用 git clone 命令拉取代码 git 命令: ...

  3. jpa 查询 报错 EntityNotFoundException

    背景: 使用JPA查询,串接到另外一张表时,由于没有实际在表格关系中指定外键,而是在实体类中指定外键 导致关联表中的数据被删除时,JPA查询时,报错EntityNotFoundException 方案 ...

  4. 排除log4j-api ( 排除log4j )

    写法都一样,排除log4j,或者其相关的依赖 1. gradle: 1.1 判断是否有log4j a. 将war或jar解压后,搜索log4j b. 使用IDEA gradle菜单, 项目名-> ...

  5. Charles的https抓包方法及原理/下载ssl/http证书【转】

    Charles的https抓包方法及原理/下载ssl/http证书   本文的Charles,适应windows/MAC/IOS/Android,避免抓包HTTPS失败和乱码: charles如果不配 ...

  6. 如何发布一个Vue组件到Npm上?

    前端时间做了一个基于Vue的拼图验证组件,因为公司需要,就想着做完之后放到Npm上,方便使用 发布流程如下: 1. 创建一个Npm账号并进行邮箱确认(很重要) 2. 创建一个文件夹,然后 npm in ...

  7. 基于Tauri2+Vue3搭建桌面端程序|tauri2+vite5多窗口|消息提醒|托盘闪烁

    基于tauri2+vite5+vue3封装多窗口实践|自定义消息提醒|托盘右键菜单及图标闪烁 这段时间一直在捣鼓最新版Tauri2.x整合Vite5搭建桌面端多开窗体应用实践.tauri2.0相较于1 ...

  8. DML—对表中的数据进行增删改

    一.添加数据 1.给指定列添加数据 insert into 表名(列名1,列名2,...) values(值1,值2...); 执行给指定列添加数据前: -- 给指定列添加数据 insert into ...

  9. Spring —— 依赖自动装配

    依赖自动装配   IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配 自动装配方式 按类型(常用) 按名称 按构造方法 不启用自动装配    注意: 自动装配用于引 ...

  10. Linux下挂载SD卡,以及容易犯的误区

    1.插入SD卡 如果系统能够识别SD卡,则会打印一些信息: 2.查看系统给SD卡分配的设备名 命令如下: fdisk -l 说明:通常是根据SD卡的存储容量来确定的. 比如下面的信息: 3.挂载SD卡 ...