首页 > 编程语言 >C++:21---仿函数

C++:21---仿函数

时间:2022-11-01 14:38:40浏览次数:58  
标签:count const 21 int C++ --- length str 函数

什么是仿函数

所谓的仿函数(functor),是通过重载()运算符模拟函数形为的类。

  因此,这里需要明确两点:

  1 仿函数不是函数,它是个类;

  2 仿函数重载了()运算符,使得它的对你可以像函数那样子调用(代码的形式好像是在调用函数)。


假设有一个vector<string>,你的任务是统计长度小于20的字符串的个数,如果使用count_if函数的话,你的代码可能长成这样:

#define STR_LEN  20
bool LengthIsLess(const string& str) {
return str.length()<STR_LEN;
}
int res=count_if(vec.begin(), vec.end(), LengthIsLess);

  其中count_if函数的第三个参数是一个函数指针,返回一个bool类型的值。 

LengthIsLess这个函数原型,将原来的宏定义通过参数传进来呢:

bool LengthIsLess(const string& str, int len)

但是他不能满足count_if函数的参数要求:count_if要求的是仅带有一个参数。怎么样找到以上两个函数的一个折中的解决方案呢?

 有三种解决方案可以考虑:

1、函数的局部变量;

      局部变量不能在函数调用中传递,而且caller无法访问。

2、函数的参数;

  这种方法我们已经讨论过了,多个参数不适用于count_if函数。

3、全局变量;

  我们可以将长度阈值设置成一个全局变量,代码可能像这样:

int maxLength;
bool LengthIsLess(const string& str) {
return str.length()<maxLength;
}
int res=count_if(vec.begiin(), vec.end(), LengthIsLess);

  这段代码看似很不错,实则不符合规范,刚重要的是,它不优雅。原因有以下几点要考虑:

1、容易出错;

  为什么这么说呢,我们必须先初始化maxLength的值,才能继续接下来的工作,如果我们忘了,则可能无法得到正确答案。此外,变量maxLength和函数LengthIsLessThan之间是没有必然联系的,编译器无法确定在调用该函数前是否将变量初始化,给码农平添负担。

2、没有可扩展性;

  如果我们每遇到一个类似的问题就新建一个全局变量,尤其是多人合作写代码时,很容易引起命名空间污染(namespace polution)的问题;当范围域内有多个变量时,我们用到的可能不是我们想要的那个。

3、全局变量的问题;

  每当新建一个全局变量,即使是为了coding的便利,我们也要知道我们应该尽可能的少使用全局变量,因为它的cost很高;而且可能暗示你这里有一些待解决的优化方案。

 

  说了这么多,还是要回到我们原始的那个问题,有什么解决方案呢?答案当然就是这篇blog的正题部分:仿函数。

  我们的初衷是想设计一个unary function,使其能做binary function的工作,这看起来并不容易,但是仿函数能解决这个问题。

   

class Test{
public:
explicit Test(int lenth) : len(lenth){}


bool operator() (const QString& str) const{
return str.length() < len;
}


private:
const int len;
};




int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);


QString str = "hello world";
int len1 = str.length()-1;
int len2 = str.length()+1;


//使用方法1
qDebug()<<Test(len1)(str)<<endl;
//使用方法2
Test t(len2);
qDebug()<<t(str)<<endl;


return a.exec();
}

  仿函数其实是上述解决方案中的第四种方案:成员变量。成员函数可以很自然的访问成员变量:

  


class StringAppend{
public:
explicit StringAppend(const string& str) : ss(str){}


void operator() (const string& str) const{
cout<<str<<' '<<ss<<endl;
}


private:
const string ss;
};


StringAppend myFunc("is world");
myFunc("hello");


  我相信这个例子能让你体会到一点点仿函数的作用了;它既能想普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。

  让我们回到count_if的问题中去,是不是觉得问题变得豁然开朗了?


class ShorterThan {
public:
explicit ShorterThan(int maxLength) : length(maxLength) {}
bool operator() (const string& str) const {
return str.length() < length;
}
private:
const int length;
};
count_if(myVector.begin(), myVector.end(), ShorterThan(length));//直接调用即可


  这里需要注意的是,不要纠结于语法问题:ShorterThan(length)似乎并没有调用operator()函数?其实它调用了,创建了一个临时对象。你也可以自己加一些输出语句看一看。

  

为什么使用仿函数(functor)
  • 迭代和计算逻辑分离

    使用仿函数可以使迭代和计算分离开来。因而你的functor可以应用于不同场合,在STL的算法中就大量使用了functor,下面是STL中for_each中使用functor的示例:

struct sum
{
sum(int * t):total(t){};
int * total;
void operator()(int element)
{
*total+=element;
}
};
int main()
{
int total = 0;
sum s(&total);
int arr[] = {0, 1, 2, 3, 4, 5};
std::for_each(arr, arr+6, s);
cout << total << endl; // prints total = 15;
}


  • 参数可设置

    可以很容易通过给仿函数(functor)设置参数,来实现原本函数指针才能实现的功能,看下面代码:

class CalculateAverageOfPowers
{
public:
CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {}
void operator() (float x) { acc += pow(x, p); n++; }
float getAverage() const { return acc / n; }
private:
float acc;
int n;
float p;
};


    这个仿函数的功能是求给定值平方或立方运算的平均值。只需要这样来声明一个对象即可:

CalculateAverageOfPowers my_cal(2);


  • 有状态

    与普通函数另一个区别是仿函数(functor)是有状态的,所以可以进行诸如下面这种操作:

CalculateAverage avg;
avg = std::for_each(dataA.begin(), dataA.end(), avg);
avg = std::for_each(dataB.begin(), dataB.end(), avg);
avg = std::for_each(dataC.begin(), dataC.end(), avg);


    对多个不同的数据集进行取平均。

  • 性能

    我们看一下2中写的代码:

std::transform(in.begin(), in.end(), out.begin(), add_x(1));


    编译器可以准确知道std::transform需要调用哪个函数(add_x::operator)。这意味着它可以内联这个函数调用。而如果使用函数指针,编译器不能直接确定指针指向的函数,而这必须在程序运行时才能得到并调用。

    一个例子就是比较std::sort 和qsort ,STL的版本一般要快5-10倍。

标签:count,const,21,int,C++,---,length,str,函数
From: https://blog.51cto.com/u_14934686/5813602

相关文章

  • C++:18---函数模板(template)
    一、模板的定义template<typenameT>以关键字template开头,后面跟一个模板参数列表,列表里面用逗号将多个模板参数隔开定义的注意事项模板的编译当编译器遇到一个模板定义时,并......
  • C:02---scanf、printf
    一、printf​控制符①精度控制:输入小数点后m位(%.mf)。右对齐5位,保留小数点后m位(%d.mf)%f、%lf默认输出6位小数②宽度:%md(打印m为,右对齐,多出m位照常打印)。%-md(打印m位,左对齐,多......
  • C++:17---sizeof运算符
    功能:以字节位单位,返回一个表达式或一个数据类型所占的字节数返回值类型:是size_t类型sizeof有无括号:sizeof不加括号,后面不可以直接跟数据类型sizeof加括号,后面既可以跟表达式......
  • Redis:10---List对象
    ​一、列表对象概述   列表类型是用来存储多个有序的字符串,一个列表最多可以存储多个元素。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有......
  • C++:44---关键字virtual、override、final
    一、虚函数概念:在函数前面加virtual,就是虚函数虚函数的一些概念:只有成员函数才可定义为虚函数,友元/全局/static/构造函数都不可以虚函数需要在函数名前加上关键字virtual成......
  • C++:45---多态
    一、多态介绍面向对象的核心思想是多态性,其含义是“多种形式”概念:在子类覆盖了父类函数的情况下,用父类的指针(或引用)调用子类对象,或者通过父类指针调用覆盖函数的时候(动......
  • C++(STL):26 ---关联式容器set用法
    set容器都会自行根据键的大小对存储的键值对进行排序,只不过set容器中各键值对的键key和值value是相等的,根据key排序,也就等价为根据value排序。另外,使用set容器......
  • 怎么查小河流名称--河流数据库
    全国最大的河流名称数据库,没有你找不到的当一条河流自上而下不同河段有不同的名称时,一般以下游的河名作为整个河流的名称。当河流在地形图上没有标注名称时,可采用下列方法......
  • C++(STL):06---数值的极值(numeric_limits类)
    一、数值的极值概述数值类型有着与平台相依的极值C++标准规定了各种类型必须保证的最小精度。这些最小值如下图所示: 类型最小长度char1byte(8bits)shortint2bytesint2bytes......
  • C++:51---继承中的构造函数、析构函数、拷贝控制一系列规则
    一、继承中的构造函数根据构造函数的执行流程我们知道:派生类定义时,先执行基类的构造函数,再执行派生类的构造函数拷贝构造函数与上面是相同的原理二、继承中的析构函数根据析......