C++函数对象详解附带实例

如果一个类将()运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象。函数对象是一个对象,但是使用的形式看起来像函数调用,实际上也执行了函数调用,因而得名。

下面是一个函数对象的例子。

#include <iostream>
using namespace std;
class CAverage
{
public:
  double operator()(int a1, int a2, int a3)
  { //重载()运算符
    return (double)(a1 + a2 + a3) / 3;
  }
};
int main()
{
  CAverage average; //能够求三个整数平均数的函数对象
  cout << average(3, 2, 3); //等价于 cout << average.operator(3, 2, 3);
  return 0;
}

程序的输出结果是:
2. 66667

()是目数不限的运算符,因此重载为成员函数时,有多少个参数都可以。

average 是一个对象,average(3, 2, 3) 实际上就是 average.operator(3, 2, 3),这使得 average 看上去像函数的名字,故称其为函数对象。

函数对象应用实例1:在 accumulate 算法中的应用

STL 中有以下实现“累加”功能的算法(函数模板):

template <class InIt, class T, class Pred>
T accumulate(InIt first, InIt last, T val, Pred op);

该模板的功能是对 [first, last) 中的每个迭代器 I 执行 val = op(val, *I),返回最终的 val。在 Dev C++ 中,numeric 头文件中 accumulate 的源代码如下:

template <class InIt, class T, class Pred>
T accumulate(InIt first, Init last, T init, Pred op)
{
  for (; first != last; ++first)
    init = op(init, *first);
  return init;
};

此模板被实例化后,op(init, *first)必须要有定义,则 op 只能是函数指针或者函数对象。因此调用该 accmulate 模板时,形参 op 对应的实参只能是函数名、函数指针或者函数对象。

下面的程序通过 accumulate 模板求一个 vector 中元素的平方和,其中用到了函数对象。

#include <iostream>
#include <vector>
#include <numeric> //accumulate 在此头文件定义
using namespace std;
template <class T>
void PrintInterval(T first, T last)
{ //输出区间[first,last)中的元素
  for (; first != last; ++first)
    cout << *first << " ";
  cout << endl;
}
int SumSquares(int total, int value)
{
  return total + value * value;
}
template<class T>
class SumPowers
{
private:
  int power;
public:
  SumPowers(int p) :power(p) { }
  const T operator() (const T & total, const T & value)
  { //计算 value的power次方,加到total上
    T v = value;
    for (int i = 0; i < power - 1; ++i)
      v = v * value;
    return total + v;
  }
};
int main()
{
  const int SIZE = 10;
  int a1[] = { 1,2,3,4,5,6,7,8,9,10 };
  vector<int> v(a1, a1 + SIZE);
  cout << "1) "; PrintInterval(v.begin(), v.end());
  int result = accumulate(v.begin(), v.end(), 0, SumSquares);
  cout << "2) 平方和:" << result << endl;
  result = accumulate(v.begin(), v.end(), 0, SumPowers<int>(3));
  cout << "3) 立方和:" << result << endl;
  result = accumulate(v.begin(), v.end(), 0, SumPowers<int>(4));
  cout << "4) 4次方和:" << result;
  return 0;
}

程序的输出结果如下:
1)1 2 3 4 5 6 7 8 9 10
2)平方和:385
3)立方和3025
4)4次方和:25333

第 37 行,第四个参数是 SumSquares 函数的名字。函数名字的类型是函数指针,因此本行将 accumulate 模板实例化后得到的模板函数定义如下:

int accumulate(vector <int>::iterator first, vector <int>::iterator last, int init, int(*op)(int, int))
{
  for (; first != last; ++first)
    init = op(init, *first);
  return init;
}

形参 op 是一个函数指针,而op(init, *first)就调用了指针 op 指向的函数,在第 37 行的情况下就是函数 SumSquares。

第 39 行,第四个参数是 SumPowers<int>(3)。SumPowers 是类模板的名字,SumPowers<int> 就是类的名字。类的名字后面跟着构造函数的参数列表,就代表一个临时对象。因此 SumPowers<int>(3) 就是一个 SumPowers<int> 类的临时对象。

编译器在编译此行时,会将 accumulate 模板实例化成以下函数:

int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, SumPowers<int> op)
{
  for (; first != last; ++first)
    init = op(init, *first);
  return init;
}

形参 op 是一个函数对象,而op(init, *first)等价于:

op.operator()(init, *first);

即调用了 SumPowers<int> 类的 operator() 成员函数。

对比 SumPowers 和 SumSquares 可以发现,函数对象的 operator() 成员函数可以根据对象内部的不同状态执行不同操作,而普通函数就无法做到这一点。因此函数对象的功能比普通函数更强大。

函数对象应用实例2:在sort算法中的应用

STL 中的排序模板 sort 能将区间从小到大排序。sort 算法有两个版本。第一个版本的原型如下:

template <class_Randlt>
void sort(_Randlt first, _RandIt last);

该模板可以用来将区间 [first, last) 中的元素从小到大排序,要求 first、last 是随机访问迭代器。元素比较大小是用<进行的。如果表达式a<b的值为 true,则 a 排在 b 前面;如果a<b的值为 false,则 b 未必排在 a 前面,还要看b<a是否成立,成立的话 b 才排在 a 前面。要使用这个版本的 sort 算法,待排序的对象必须能用<运算符进行比较。

sort 算法第二个版本的原型如下:

template <class_Randlt, class Pred>
void sort(_Randlt first, _RandIt last, Pred op);

这个版本和第一个版本的差别在于,元素 a、b 比较大小是通过表达式op(a, b)进行的。如果该表达式的值为 true,则 a 比 b 小;如果该表达式的值为 false,也不能认为 b 比 a 小,还要看op(b, a)的值。总之,op 定义了元素比较大小的规则。下面是一个使用 sort 算法的例子。

#include <iostream>
#include <algorithm> //sort算法在此头文件中定义
using namespace std;
template <class T>
void Printlnterva1(T first, T last)
{ //用以输出 [first, last) 区间中的元素
  for (; first != last; ++first)
    cout << *first << " ";
  cout << endl;
}
class A
{
public:
  int v;
  A(int n) : v(n) {}
};
bool operator < (const A & a1, const A & a2)
{ //重载为 A 的 const 成员函数也可以,重载为非 const 成员函数在某些编译器上会出错
  return a1.v < a2.v;
}
bool GreaterA(const A & a1, const A & a2)
{ //v值大的元素作为较小的数
  return a1.v > a2.v;
}
struct LessA
{
  bool operator() (const A & a1, const A & a2)
  { //v的个位数小的元素就作为较小的数
    return (a1.v % 10) < (a2.v % 10);
  }
};
ostream & operator << (ostream & o, const A & a)
{
  o << a.v;
  return o;
}
int main()
{
  int a1[4] = { 5, 2, 4, 1 };
  A a2[5] = { 13, 12, 9, 8, 16 };
  sort(a1, a1 + 4);
  cout << "1)"; Printlnterva1(a1, a1 + 4); //输出 1)1 2 4 5
  sort(a2, a2 + 5); //按v的值从小到大排序
  cout << "2)"; Printlnterva1(a2, a2 + 5); //输出 2)8 9 12 13 16
  sort(a2, a2 + 5, GreaterA); //按v的值从大到小排序
  cout << "3)"; Printlnterva1(a2, a2 + 5); //输出 3)16 13 12 9 8
  sort(a2, a2 + 5, LessA()); //按v的个位数从小到大排序
  cout << "4)"; Printlnterva1(a2, a2 + 5); //输出 4)12 13 16 8 9
  return 0;
}

编译至第 45 行时,编译器将 sort 实例化得到的函数原型如下:

void sort(A* first, A* last, bool (*op)(const A &, const A &) );

该函数在执行过程中,当要比较两个元素 a、b 的大小时,就是看 op(a, b) 和 op(b, a) 的返回值。本程序中 op 指向 GreaterA,因此就用 GreaterA 定义的规则来比较大小。

编译至第 47 行时,编译器将 sort 实例化得到的函数原型如下:

void sort( A* first, A* last, LessA op);

该函数在执行过程中,当要比较两个元素 a、b 的大小时,就是看 op(a, b) 和 op(b, a) 的返回值。本程序中,op(a, b) 等价于 op.opeartor(a, b),因此就用 LessA 定义的规则来比较大小。

STL 中定义了一些函数对象类模板,都位于头文件 functional 中。例如,greater 模板的源代码如下:

template <class T>
struct greater
{
  bool operator()(const T& x, const T& y) const{
    return x > y;
  }
};

假设有以下数组:

int a[4] = {3, 5, 34, 8};

要将该数组从大到小排序,则只需写:

sort( a, a+4, greater<int>() );

要使用 greater 模板,须确保>运算符本来就有定义,或经过了适当的重载。

list 容器的 sort 成员能将元素从小到大排序。它也有两个版本:一个是没有参数的函数,比较大小用<运算符;另一个是函数模板,原型如下:

template <class Pred>
void sort(Pred op);

sort 函数允许自定义比较大小的规则,即 op(x, y) 为真就认为 x 比 y 小。例如,假设有:

list<int> lst;

如果希望将 lst 中的元素按其整数数值从大到小排序,只需写:

lst.sort( greater<int>() );

在使用关联容器和许多算法时,都可以用函数对象来定义比较大小的规则,以及其他一些规则和操作。

STL 中的函数对象类模板

STL 中有一些函数对象类模板,如表 1 所示。

表1:STL 中的函数对象类模板

函数对象类模板 成员函数 T operator ( const T & x, const T & y) 的功能
plus <T> return x + y;
minus < > return x - y;
multiplies <T> return x * y;
divides <T> return x / y;
modulus <T> return x % y;
  成员函数 bool operator( const T & x, const T & y) 的功能
equal_to <T> return x == y;
not_equal_to <T> return x! = y;
greater <T> return x > y;
less <T> return x < y;
greater_equal <T> return x > = y;
less_equal <T> return x <= y;
logical_and <T> return x && y;
logical_or <T> return x || y;
  成员函数 T operator( const T & x) 的功能
negate <T> return - x;
  成员函数 bool operator( const T & x) 的功能
logical_not <T> return ! x;

例如,如果要求两个 double 型变量 x、y 的乘积,可以写:

multiplies<double> () (x, y)

less 是 STL 中最常用的函数对象类模板,其定义如下:

template <class_Tp>
struct less
{
  bool operator() (const_Tp & __x, const_Tp & __y) const
  { return __x < __y; }
};

要判断两个 int 变量 x、y 中 x 是否比 y 小,可以写:

if( less<int>()(x, y) ) { ... }

引入函数对象后 STL 中的“大”、“小”和“相等”概念

前面提到过,默认情况下,STL 中的容器和算法比较元素的大小是通过<运算符进行的。通过 10.3.4 节可知,sort 和 list::sort 都可以通过一个函数对象或函数自定义比较元素大小的规则。例如以下的 sort 版本:

template <class_RandIt, class Pred>
void sort(_RandIt first, _RandIt last, Pred op);

实际调用 sort 时,和 op 对应的实参可以是一个函数对象或者函数的名字。sort 在执行过程中用 op(x, y) 比较 x 和 y 的大小,因此可以将 op 称为自定义的“比较器”。

关联容器中的元素是从小到大排序的。使用关联容器时,也可以用自定义的比较器取代<运算符,以规定元素之间的大小关系。STL 中还有许多算法都可以自定义比较器。在自定义比较器 op 的情况下,以下三种说法是等价的:

  • x 小于 y。
  • op(x, y) 的返回值为 true。
  • y 大于 x。

同样地,对关联容器的 find 和 count 成员函数以及其他一些在有序区间上的 STL 算法而言,在自定义比较器 op 的情况下,x和y相等与op(x, y)和op(y, x)都为假是等价的。

到此这篇关于C++函数对象详解附带实例的文章就介绍到这了,更多相关C++ 函数对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中的函数指针与函数对象的总结

    篇一.函数指针函数指针:是指向函数的指针变量,在C编译时,每一个函数都有一个入口地址,那么这个指向这个函数的函数指针便指向这个地址. 函数指针的用途是很大的,主要有两个作用:用作调用函数和做函数的参数. 函数指针的声明方法:数据类型标志符 (指针变量名) (形参列表):一般函数的声明为: int func ( int x );而一个函数指针的声明方法为:int (*func) (int x);前面的那个(*func)中括号是必要的,这会告诉编译器我们声明的是函数指针而不是声明一个具有返回型为指针

  • 如何应用C++的函数对象

    前言 C++函数对象是通过一张虚函数表来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.重载的问题,保证其容真实反应实际的函数. 应用 假如我们实现了这样的一个单向链表: class LinkedListNode { int data_; LinkedListNode *next_; }; class LinkedList { public: void insert(LinkedListNode* &p); void del(LinkedListNode

  • C++函数对象详解附带实例

    如果一个类将()运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象.函数对象是一个对象,但是使用的形式看起来像函数调用,实际上也执行了函数调用,因而得名. 下面是一个函数对象的例子. #include <iostream> using namespace std; class CAverage { public: double operator()(int a1, int a2, int a3) { //重载()运算符 return (double)(a1 + a2 + a

  • python strip() 函数和 split() 函数的详解及实例

     python strip() 函数和 split() 函数的详解及实例 一直以来都分不清楚strip和split的功能,实际上strip是删除的意思:而split则是分割的意思.因此也表示了这两个功能是完全不一样的,strip可以删除字符串的某些字符,而split则是根据规定的字符将字符串进行分割.下面就详细说一下这两个功能, 1 Python strip()函数 介绍 函数原型 声明:s为字符串,rm为要删除的字符序列 s.strip(rm)       删除s字符串中开头.结尾处,位于 r

  • C++获取类的成员函数的函数指针详解及实例代码

    C++获取类的成员函数的函数指针详解 用一个实际代码来说明. class A { public: staticvoid staticmember(){cout<<"static"<<endl;} //static member void nonstatic(){cout<<"nonstatic"<<endl;} //nonstatic member virtualvoid virtualmember(){cout<

  • JSON对象 详解及实例代码

    前面的话 json(javascript object notation)全称是javascript对象表示法,它是一种数据交换的文本格式,而不是一种编程语言,用于读取结构化数据.2001年由Douglas Crockford提出,目的是取代繁琐笨重的XML格式.本文将详细介绍关于json的内容 语法规则 JSON的语法可以表示以下三种类型的值 [1]简单值 简单值使用与JavaScript相同的语法,可以在JSON中表示字符串.数值.布尔值和null 字符串必须使用双引号表示,不能使用单引号.

  • JavaScript 深层克隆对象详解及实例

     JavaScript 深层克隆对象 今天做项目,有个需求需要用到深层克隆对象,并且要求在原型链上编程 于是心血来潮索性来复习一下这个知识点,在网上找了相应的知识, 克隆对象,这名词看着高大上,其实也没什么,便是拷贝一个长的一模一样的对象 也许有初学的小伙伴在想,那还不简单么,so easy var obj1 = {name: 'payen'}; var obj2 = obj1; 这可并不是克隆对象,obj1和obj2根本就是同一个对象, 他俩指向同一个内存地址空间,拿到了同样的一个小房子 这是

  • JavaScript函数表达式详解及实例

    JavaScript函数表达式 一.序 定义函数的方式有两种:一种是函数声明,另一种就是函数表达式: 1.1 函数声明 function functionName(arg){ //函数体 } 关于函数声明,它有一个重要特征就是函数声明提升,意思就是在执行代码之前会先读取函数声明.这就意味着可以把函数放在调用它的语句后面.如下所示: helloworld(); //在代码执行之前会先读取函数声明 function helloworld(){ console.log("hello world&quo

  • php 数组处理函数extract详解及实例代码

    php 数组处理函数extract extract函数用于从数组中将变量导入到当前的符号表 基本语法 int extract ( array &$var_array [, int $extract_type = EXTR_OVERWRITE [, string $prefix = NULL ]] ) 本函数用来将变量从数组中导入到当前的符号表中.检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突. 参数介绍: 参数 描述 var_array 必需.规定要使用的数组

  • C++函数重载详解及实例代码

    C++函数的重载 定义 在同一个作用域中,函数名相同,函数的参数列表不同的函数之间构成重载关系,在不同作用域中的同名函数遵循标识符隐藏的原则 ATTENTION:重载与函数的返回值类型无关,因为声明一个函数不需要返回类型,所以无法用来区分哪个函数 常函数和普通成员函数之间构成重载,重载时常对象调用常成员函数,一般对象调用一般成员函数 class A{ - public: void getVal()const{-} void getVal(){-} }; int main(){ const A a

  • 微信小程序 跳转传参数与传对象详解及实例代码

    微信小程序 跳转传参数 传对象 微信小程序跳转传参 一般都是传字符串到下一页,如果要想传对象怎么办呢? 我的解决办法是先将对象转换为json字符串然后到下个页面将json字符串,再转化为对象.如下: let str=JSON.stringify(e.currentTarget.dataset.item); wx.navigateTo({ url: '../toMybaby/babyDetail/babyDetail?jsonStr='+str, success: function (res) {

  • COM组件中调用JavaScript函数详解及实例

    COM组件中调用JavaScript函数详解及实例 要求是很简单的,即有COM组件A在IE中运行,使用JavaScript(JS)调用A的方法longCalc(),该方法是一个耗时的操作,要求通知IE当前的进度.这就要求使用回调函数,设其名称为scriptCallbackFunc.实现这个技术很简单: 1 .组件方(C++) 组件A 的方法在IDL中定义: [id(2)] HRESULT longCalc([in] DOUBLE v1, [in] DOUBLE v2, [in, optional

随机推荐