简介
本文主要介绍函数的定义与调用方法,函数配匹及实参与形参不在介绍之内。
一个函数需要有三个基本要素:函数名、参数和返回值。
但有一种特殊函数没有函数名,我们把它称为匿名函数。
函数基础
函数需要在定义或声明之后才能调用,否则会报出“未定义”错误。
auto add(const int x, const int y) -> int {
return x + y;
}
int main(int, char **) {
add(1, 2);
return 0;
}
同样地,我们也可以先声明存在该函数,在以后实现它。
auto add(const int x, const int y) -> int;
int main(int, char **) {
add(1, 2);
return 0;
}
auto add(const int x, const int y) -> int {
return x + y;
}
函数指针
定义
指针除了可以指向变量,它还可以指向一个函数。和其它指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
以上面已定义好的add
函数为例,声明指向add
的指针,只需要用指针替换函数名即可:
int (*f) (const int x, const y);
f = add;
// 或者
int (*f) (const int x, const y) = add;
我们去观察它的声明,(*f)
显然是个指针,右侧是它的形参例表,说明它指向一个函数,左侧说明是它的返回类型。
(*f)
左右两侧的括号必不可少。
调用
我们拥有了函数指针之后,我们就可以像普通函数那样去调用它。
cout << f(1, 2) << endl;
// output: 3
由于f
是个指针,所以它同样可以指向符合它类型的其它函数,只要形参列表和返回值一致。
auto add(const int x, const int y) -> int {
return x + y;
}
int (*f) (const int x, const int y);
f = add;
cout << f(1, 2) << endl;
// output: 3
f = minus;
cout << f(1, 2) << endl;
// output: -1
那么我们可以把函数指针像普通指针一样,可以通过形参传递到另一个函数中呢?
当然是可以的。我们可以扩展add
,实现CPS风格的add_cps
。
auto add_cps(const int x, const int y, int k (int)) -> int {
auto a = add(x, y);
return k(a);
}
int (*f) (int) = inc;
cout << add_cps(1, 2, f) << endl;
k
即后续计算的函数指针,它跟int (*k) (int)
的写法等价。
函数对象
定义
C++11之后,STL中提供<funtional>
头部,这里可以处理很多函数对象。
我们可以利用std::function
达到函数指针同样的效果。
std::function<int(int, int)> f = add;
cout << f(1, 2) << endl;
我们把指针f
转变成了函数对象(funtion objects[1]),我们观察一下f
的类型,std::function
说明它是一个函数对象,<int(...)>
中的int
是函数的返回值,括号内是函数的形参列表,与函数指针对比,它们表达的信息相差无几。
偏函数
还记得刚才使用到的inc
函数吗?它可以算是add(1)
的偏函数了,我们可以利用std::bind
将add
函数特例化。
std::function<int(int)> inc = std::bind(add, 1, std::placeholders::_1);
cout << inc(1) << endl;
// output: 2
std::placeholders::_1
是占位符,表明第一个参数会在这个位置被填充,同样还有_2
及其它占位符。
传递函数对象
同理,函数对象也是能当作形参的,我们把原先的add_cps
参形列表中的函数指针改成一个函数对象。
auto add_cps(const int x, const int y, std::function<int(int)> k) -> int {
auto a = add(x, y);
return k(a);
}
cout << add_cps(1, 2, inc) << endl;
// output: 4
改造之后,我们达到了与函数指针同样的效果。
高阶函数
我们可以传递函数了,那我们也可以实现更加高阶的函数,例如Haskell中的map
、filter
等。
auto map(std::function<int(int)> f, const vector<int> &in) -> vector<int> {
vector<int> v;
for (const auto &i : in) {
v.push_back(f(i));
}
return v;
}
auto filter(std::function<bool(int)> f, const vector<int> &in) -> vector<int> {
vector<int> v;
for (const auto &i : in) {
if (f(i)) {
v.push_back(i);
}
}
return v;
}
注意:
map
在C++中表示一种数据结构,这里只作演示。
lambda函数
lambda函数(或称匿名函数)提供了四种写法[2]。
在一些简法的计算中,除了重新定义一个具名函数,例如add
和inc
,我们也可以在某个地方临时使用一下新函数,这时就可以交给lambda来做了。
上面我们实现了map
函数,我们想让每个数都(+ 1)
,map
接受的是一个函数,这里我们就可以临时使用lambda了。
std::vector<int> v{1, 2, 3};
map([](auto i) { return i + 1; }, v);
// 2 3 4
[](auto i) { return i + 1; }
就是一条lambda函数,我们也可以把匿名函数赋值到std::function
,让它重新变成一个函数对象。
std::function<int(int)> inc = [](auto i) {
return i + 1;
};
showVec(map(inc, v));
有了lambda和函数对象,不必再像往常那样在外部定义只使用了一次的函数;在可读上面,我觉得lambda与function
结合在一起可能会比较直观些。