什么是仿函数
所谓的仿函数(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