1 什么是函数式编程
通常人们将函数式编程理解为一种编程风格,整个程序完全由纯函数组成,程序由一系列函数以及函数和函数链的求值组成。函数式编程是一种编程范式,是思考软件构建的一种方式。
函数式编程中的函数是真正的数学函数。函数是一组输入参数和一组输出参数的关系。在计算机编程中,也称为“引用透明”。
用函数式语言编写的函数,只用于计算表达式并返回结果,不执行任何其他操作。
double square(const double value) noexcept
{
return value * value; //纯函数
};
2 函数指针
我们可以把函数当作普通的变量,可以通过指针访问和操作函数,可以作为参数传递给其他函数或数据结构。函数指针需要根据函数参数和返回值确定类型。
创建函数指针一般有如下两种方式:
使用类型别名创建函数指针,类型别名相当于函数的一个别名
typedef bool (*Matcher)(int, int);
typedef void (*MatchHandler)(size_t, int, int);
//推荐使用using,不用typedef
using Matcher = bool(*)(int, int);
using MatchHandler = void(*)(size_t, int, int);
使用
std::function
封装using Matcher = function<bool(int, int)>;
using MatchHandler = function<void(size_t, int, int)>;
函数指针作为参数的例子:
//定义函数指针
using Matcher = bool (*)(int, int);
using MatchHandler = void (*)(size_t, int, int);
//实际的函数实现
bool intEqual(int item1, int item2) { return item1 == item2; }
void printMatch(size_t position, int value1, int value2)
{
printf("Match found at position %d (%d, %d)\r\n", position, value1, value2);
}
//使用函数指针作为参数
void findMatches(span<const int> values1, span<const int> values2,
Matcher matcher, MatchHandler handler)
{
if (values1.size() != values2.size())
{
return;
}
for (size_t i{0}; i < values1.size(); ++i)
{
if (matcher(values1[i], values2[i]))
{
handler(i, values1[i], values2[i]);
}
}
}
int main()
{
vector values1{2, 5, 6, 9, 10, 1, 1};
vector values2{4, 4, 2, 9, 0, 3, 1};
//参数取函数地址
findMatches(values1, values2, &intEqual, &printMatch);
}
3 C++中的函数式编程
3.1 模板元编程
想不到吧,模板元编程竟然是函数式编程?例如下面计算最大公约数的模板元编程的例子:
//模板函数,也是函数式编程
#include <iostream>
template< unsigned int x, unsigned int y >
struct GreatestCommonDivisor {
static const unsigned int result = GreatestCommonDivisor< y, x% y >::result;
};
template< unsigned int x >
struct GreatestCommonDivisor< x, 0 > {
static const unsigned int result = x;
};
int main() {
std::cout << "The GCD of 40 and 10 is: " << GreatestCommonDivisor<40u, 10u>::result << std::endl;
std::cout << "The GCD of 366 and 60 is: " << GreatestCommonDivisor<366u, 60u>::result << std::endl;
return 0;
}
模板元编程有一些缺点:
- 大量的模板,导致代码可读性和可理解性受到严重影响
- 模板元编程的语法和习惯用法难以理解
- 出错时错误信息含糊不清
- 模板元编程应该仅用在哪些现代化且精心设计的代码中
3.2 仿函数
C++中可以定义和使用像函数一样的对象,称为仿函数。它是定义了一个()运算符的类(即operator()),实例化类之后,可以像函数一样使用。
根据参数的个数,仿函数分为:生成器、一元仿函数、二元仿函数。生成器
```cpp //无参数仿函数,也叫生成器include
include
include
//生成器 class IncreasingNumberGenator { public: int operator()() noexcept { return num++;//每次调用()返回num的值并自增1 } private: int num{ 0 }; };
int main()
{
std::vector
<a name="aWDl9"></a>
### 一元仿函数
```cpp
//一元仿函数
class ToSquare
{
public:
//返回平方
constexpr int operator()(const int value) const noexcept { return value * value; }
};
//接上面的例子
//使用一元仿函数给vector中每个数计算乘积
std::transform(std::begin(numbers), std::end(numbers), std::begin(numbers), ToSquare());
class IsAnOddNumber
{
public:
//返回输入数值是否为奇数
constexpr bool operator()(const int value) const noexcept { return (value % 2) != 0; }
};
//接上面例子
//使用一元仿函数判断是否为奇数
numbers.erase(std::remove_if(std::begin(numbers), std::end(numbers), IsAnOddNumber()), std::end(numbers));
二元仿函数
class IsGreaterOrEqual {
public:
bool operator()(const auto& value1, const auto& value2) const noexcept {
return value1 >= value2;
}
};
3.3 std::bind和std::function
头文件:
函数模板std::bind是一个绑定包装器,可以将具体的参数值绑定到函数的一个或多个参数上,这意味着可以用现有函数来创建新的函数(仿函数等)。未绑定的函数参数用占位符_1, _2, _3代替(需要先引用namespace std::placeholders)。
举例如下:
#include <functional>
#include <iostream>
constexpr double multiply(const double multiplicand, const double multiplier) noexcept
{
return multiplicand * multiplier;
}
int main()
{
//引用placeholders为占位符做准备
using namespace std::placeholders;
//使用bind模板绑定multiply函数的第二个参数,返回一个新的函数对象
auto multiplyWith10 = std::bind(multiply, _1, 10.0);
//使用函数对象
std::cout << "result = " << multiplyWith10(5.0) << std::endl;
return 0;
}
std::function是一个通用的函数包装器,可以保证任何可调用对象,并管理用于存储该对象的内存:
- 普通函数
- 仿函数
- 函数指针
- lambda表达式
举例如下:
//function的格式为:<函数返回值类型(参数类型列表)>
std::function<double(double, double)> multiplyFunc = multiply; //新的函数对象,可以直接调用
auto result = multiplyFunc(10.0, 5.0);
Note:由于lambda表达式的出现,bind和function已经很少使用了。
3.4 lambda表达式
4 C++高阶函数
高阶函数就是将一个或多个函数作为参数的函数,或者他们可以返回函数作为结果。即它们的操作对象是函数。
头文件<algorithm>
和<numeric>
提供了很多功能强大的高阶函数。
4.1 自定义高阶函数
#include <functional>
#include <iostream>
#include <vector>
//自定义高阶函数
template<typename CONTAINERTYPE, typename FUNCTIONTYPE>
void myForeach(const CONTAINERTYPE& container, FUNCTIONTYPE function)
{
for (const auto& element : container)
function(element);//可以理解为函数指针
}
template<typename T>
void myPrint(const T& t)
{
std::cout << t << ",";
}
int main()
{
std::vector<int> numbers = { 1,2,3,4,5,6,7,8,9,10 };
//通过function包装出一个支持int型的myPrint函数对象
std::function<void(int)> funcPtr = myPrint<int>;
myForeach(numbers, funcPtr);
return 0;
}
4.2 最有用的三个高阶函数
Map
使用这个高阶函数,可以将一个运算符函数应用于列表的每个元素。在C++中,std::transform
提供Map的功能。
头文件为<algorithm>
。
int main()
{
std::vector<int> numbers = { 1,2,3,4,5,6,7,8,9,10 };
//为每个元素加1
std::transform(std::begin(numbers), std::end(numbers), std::begin(numbers), [](int i) {return i + 1; });
for (auto n : numbers)
std::cout << n << ", ";
return 0;
}
Filter
该高阶函数用于过滤,从列表中删除任何不满足指定条件的元素。在C++中,std::remove_if
提供Filter的功能。
头文件为<algorithm>
。
#include <algorithm>
#include <string>
#include <iostream>
int main()
{
std::string str2 = "Text\n with\tsome \t whitespaces\n\n";
str2.erase(std::remove_if(str2.begin(),
str2.end(),
[](unsigned char x){return std::isspace(x);}), //去除空格
str2.end());
std::cout << str2 << '\n';
}
reduce/flod
reduce通过在一列值上应用二元运算符来获取一个单一的结果值。在C++中,std::accumulate
提供reduce功能。
头文件为<numeric>
。
#include <iostream>
#include <vector>
#include <numeric>
int main()
{
std::vector<int> list = { 12,45,-102,33,78,-8,100,2017,-110 };
const int result = std::accumulate(std::begin(list), std::end(list), 0,
[](const int a, const int b) { return a > b ? a : b; });//获取最大值的lambda表达式
std::cout << result << std::endl;
}