C++手稿:STL中的函数对象与函数指针
STL是C++的标准模板库(standard template library),自然其中定义的都是模板。 相比于类和函数声明的显式接口(explicit interface),类模板和函数模板声明的接口属于隐式接口(implicit interface)。 因为模板参数应当满足的接口是由模板中表达式的合法性决定的,这一点给了模板很大的自由。 而函数对象和函数指针具有同样的调用语法,因此STL中这两者常常可以互换。
更多关于隐式接口和显式接口的概念和区别,参见Effective C++: Item 41。
先来感受一下C++中的函数对象和函数指针:
template<typename T>
void printer(int a, int b, T func){
cout<<func(a, b)<<endl;
}
在STL中定义了很多像上面这样的模板,这里的T是一个可调用(实现了括号运算符)的东西。 这使得我们在使用模板时可以指定一个计算策略,它可以是函数对象,也可以是函数指针。
Less<int>
便是一个常见的函数对象,常用来配置容器或算法。<functional>
中定义了很多这样的函数对象。
函数指针
函数指针通常用来将函数传参或存储。例如:
int sum(int a, int b){
return a+b;
}
int main(){
printer(2, 3, sum);
return 0;
}
上述的printer
调用方式,编译器会生成对应的函数实例:
void printer(int a, int b, int (*func)(int, int)){
cout<<func(a, b)<<endl;
}
这里T
的类型是int (*)(int, int)
。
如果你是python或者javascript程序员的话,上述过程没有什么特别的。 唯一要注意的是
func
的声明方式,星号要和标识符括起来:(*func)
。
函数对象
函数对象是重载了括号运算符的类的实例,它也可以这样调用:func(a, b)
。例如:
class Sum{
public:
int operator()(int a, int b){
return a+b;
}
};
int main(){
printer(2, 3, Sum());
return 0;
}
编译器会生成这样的函数实例:
void printer(int a, int b, Sum s){
cout<<s(a, b)<<endl;
}
函数对象可以实现更加复杂的有状态的运算,因为对象可以有更多的属性和方法。
std::accumulate
#include <iostream> // std::cout
#include <functional> // std::minus
#include <numeric> // std::accumulate
int myfunction (int x, int y) {return x+2*y;}
struct myclass {
int operator()(int x, int y) {return x+3*y;}
} myobject;
int main () {
int init = 100;
int numbers[] = {10,20,30};
std::cout << std::accumulate(numbers,numbers+3,init) << std::endl;
std::cout << std::accumulate (numbers, numbers+3, init, std::minus<int>()) << std::endl;
std::cout << std::accumulate (numbers, numbers+3, init, myfunction) << std::endl;
std::cout << std::accumulate (numbers, numbers+3, init, myobject) << std::endl;
return 0;
}
在这里accumulate
的第四个参数默认值为std::plus<int>()
,是一个函数对象,
std::minus<int>()
也是一个函数对象,它们都是在functional
中定义的。
myfunction
是函数指针,而myobject
是自定义的函数对象。输出如下:
160
40
220
280
摘自cplusplus.com: http://www.cplusplus.com/reference/numeric/accumulate
比较器:std::sort
需要比较元素大小的STL算法、容器的模板、容器的成员函数,都可以给定一个比较策略。它们的默认值通常是Less<T>()
。
例如,指定greater
函数对象作为比较器,就可以实现反向排序:
sort(v.begin(), v.end(), greater<int>());
std::sort
要求随机存取迭代器,list
不可用std::sort
,可以使用list::sort(Pred pr)
。std::sort
实际上是快排,复杂度为$O(n lgn)$,它是不稳定的。stale_sort
则是稳定的归并排序。
比较器:模板参数
关联容器通常使用搜索树来实现,所以插入元素时需要进行比较操作。 我们在使用容器模板时可以指定比较器:
class A{
int n;
public:
A(int _n):n(_n){}
bool operator<(const A& lhs, const A& rhs){
return lhs.n < rhs.n;
}
};
multiset<A, less<A>> s;
s.insert(A());
默认情况下关联容器使用less
模板来进行元素比较,而less
中调用了<
,
所以默认情况下插入关联容器的元素需要实现<
运算符:
如果未实现相应的比较运算符,
insert
操作会编译错。因为运算符调用本质上是函数调用。
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2015/07/03/stl-function-objects-and-pointers.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。