1 什么是函数式编程

通常人们将函数式编程理解为一种编程风格整个程序完全由纯函数组成,程序由一系列函数以及函数和函数链的求值组成。函数式编程是一种编程范式,是思考软件构建的一种方式。

函数式编程中的函数是真正的数学函数。函数是一组输入参数和一组输出参数的关系。在计算机编程中,也称为“引用透明”。
用函数式语言编写的函数,只用于计算表达式并返回结果,不执行任何其他操作。

  1. double square(const double value) noexcept
  2. {
  3. return value * value; //纯函数
  4. };

2 函数指针

我们可以把函数当作普通的变量,可以通过指针访问和操作函数,可以作为参数传递给其他函数或数据结构。函数指针需要根据函数参数和返回值确定类型。

创建函数指针一般有如下两种方式:

  • 使用类型别名创建函数指针,类型别名相当于函数的一个别名

    1. typedef bool (*Matcher)(int, int);
    2. typedef void (*MatchHandler)(size_t, int, int);
    3. //推荐使用using,不用typedef
    4. using Matcher = bool(*)(int, int);
    5. using MatchHandler = void(*)(size_t, int, int);
  • 使用std::function封装

    1. using Matcher = function<bool(int, int)>;
    2. using MatchHandler = function<void(size_t, int, int)>;

函数指针作为参数的例子:

  1. //定义函数指针
  2. using Matcher = bool (*)(int, int);
  3. using MatchHandler = void (*)(size_t, int, int);
  4. //实际的函数实现
  5. bool intEqual(int item1, int item2) { return item1 == item2; }
  6. void printMatch(size_t position, int value1, int value2)
  7. {
  8. printf("Match found at position %d (%d, %d)\r\n", position, value1, value2);
  9. }
  10. //使用函数指针作为参数
  11. void findMatches(span<const int> values1, span<const int> values2,
  12. Matcher matcher, MatchHandler handler)
  13. {
  14. if (values1.size() != values2.size())
  15. {
  16. return;
  17. }
  18. for (size_t i{0}; i < values1.size(); ++i)
  19. {
  20. if (matcher(values1[i], values2[i]))
  21. {
  22. handler(i, values1[i], values2[i]);
  23. }
  24. }
  25. }
  26. int main()
  27. {
  28. vector values1{2, 5, 6, 9, 10, 1, 1};
  29. vector values2{4, 4, 2, 9, 0, 3, 1};
  30. //参数取函数地址
  31. findMatches(values1, values2, &intEqual, &printMatch);
  32. }

3 C++中的函数式编程

3.1 模板元编程

想不到吧,模板元编程竟然是函数式编程?例如下面计算最大公约数的模板元编程的例子:

  1. //模板函数,也是函数式编程
  2. #include <iostream>
  3. template< unsigned int x, unsigned int y >
  4. struct GreatestCommonDivisor {
  5. static const unsigned int result = GreatestCommonDivisor< y, x% y >::result;
  6. };
  7. template< unsigned int x >
  8. struct GreatestCommonDivisor< x, 0 > {
  9. static const unsigned int result = x;
  10. };
  11. int main() {
  12. std::cout << "The GCD of 40 and 10 is: " << GreatestCommonDivisor<40u, 10u>::result << std::endl;
  13. std::cout << "The GCD of 366 and 60 is: " << GreatestCommonDivisor<366u, 60u>::result << std::endl;
  14. return 0;
  15. }

模板元编程有一些缺点:

  • 大量的模板,导致代码可读性和可理解性受到严重影响
  • 模板元编程的语法和习惯用法难以理解
  • 出错时错误信息含糊不清
  • 模板元编程应该仅用在哪些现代化且精心设计的代码中

    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 numbers(20); //使用生成器填充数据 std::generate(std::begin(numbers), std::end(numbers), IncreasingNumberGenator()); for (const int a : numbers) std::cout << a << “ “; //打印0-19的数字 return 0; }

  1. <a name="aWDl9"></a>
  2. ### 一元仿函数
  3. ```cpp
  4. //一元仿函数
  5. class ToSquare
  6. {
  7. public:
  8. //返回平方
  9. constexpr int operator()(const int value) const noexcept { return value * value; }
  10. };
  11. //接上面的例子
  12. //使用一元仿函数给vector中每个数计算乘积
  13. std::transform(std::begin(numbers), std::end(numbers), std::begin(numbers), ToSquare());
  1. class IsAnOddNumber
  2. {
  3. public:
  4. //返回输入数值是否为奇数
  5. constexpr bool operator()(const int value) const noexcept { return (value % 2) != 0; }
  6. };
  7. //接上面例子
  8. //使用一元仿函数判断是否为奇数
  9. numbers.erase(std::remove_if(std::begin(numbers), std::end(numbers), IsAnOddNumber()), std::end(numbers));

二元仿函数

  1. class IsGreaterOrEqual {
  2. public:
  3. bool operator()(const auto& value1, const auto& value2) const noexcept {
  4. return value1 >= value2;
  5. }
  6. };

3.3 std::bind和std::function

头文件:

函数模板std::bind是一个绑定包装器,可以将具体的参数值绑定到函数的一个或多个参数上,这意味着可以用现有函数来创建新的函数(仿函数等)。未绑定的函数参数用占位符_1, _2, _3代替(需要先引用namespace std::placeholders)。
举例如下:

  1. #include <functional>
  2. #include <iostream>
  3. constexpr double multiply(const double multiplicand, const double multiplier) noexcept
  4. {
  5. return multiplicand * multiplier;
  6. }
  7. int main()
  8. {
  9. //引用placeholders为占位符做准备
  10. using namespace std::placeholders;
  11. //使用bind模板绑定multiply函数的第二个参数,返回一个新的函数对象
  12. auto multiplyWith10 = std::bind(multiply, _1, 10.0);
  13. //使用函数对象
  14. std::cout << "result = " << multiplyWith10(5.0) << std::endl;
  15. return 0;
  16. }

std::function是一个通用的函数包装器,可以保证任何可调用对象,并管理用于存储该对象的内存:

  • 普通函数
  • 仿函数
  • 函数指针
  • lambda表达式

举例如下:

  1. //function的格式为:<函数返回值类型(参数类型列表)>
  2. std::function<double(double, double)> multiplyFunc = multiply; //新的函数对象,可以直接调用
  3. auto result = multiplyFunc(10.0, 5.0);

Note:由于lambda表达式的出现,bind和function已经很少使用了。

3.4 lambda表达式

2 新增常用特性

4 C++高阶函数

高阶函数就是将一个或多个函数作为参数的函数,或者他们可以返回函数作为结果。即它们的操作对象是函数。
头文件<algorithm><numeric>提供了很多功能强大的高阶函数。

4.1 自定义高阶函数

  1. #include <functional>
  2. #include <iostream>
  3. #include <vector>
  4. //自定义高阶函数
  5. template<typename CONTAINERTYPE, typename FUNCTIONTYPE>
  6. void myForeach(const CONTAINERTYPE& container, FUNCTIONTYPE function)
  7. {
  8. for (const auto& element : container)
  9. function(element);//可以理解为函数指针
  10. }
  11. template<typename T>
  12. void myPrint(const T& t)
  13. {
  14. std::cout << t << ",";
  15. }
  16. int main()
  17. {
  18. std::vector<int> numbers = { 1,2,3,4,5,6,7,8,9,10 };
  19. //通过function包装出一个支持int型的myPrint函数对象
  20. std::function<void(int)> funcPtr = myPrint<int>;
  21. myForeach(numbers, funcPtr);
  22. return 0;
  23. }

4.2 最有用的三个高阶函数

Map

使用这个高阶函数,可以将一个运算符函数应用于列表的每个元素。在C++中,std::transform提供Map的功能。
头文件为<algorithm>

  1. int main()
  2. {
  3. std::vector<int> numbers = { 1,2,3,4,5,6,7,8,9,10 };
  4. //为每个元素加1
  5. std::transform(std::begin(numbers), std::end(numbers), std::begin(numbers), [](int i) {return i + 1; });
  6. for (auto n : numbers)
  7. std::cout << n << ", ";
  8. return 0;
  9. }

Filter

该高阶函数用于过滤,从列表中删除任何不满足指定条件的元素。在C++中,std::remove_if提供Filter的功能。
头文件为<algorithm>

  1. #include <algorithm>
  2. #include <string>
  3. #include <iostream>
  4. int main()
  5. {
  6. std::string str2 = "Text\n with\tsome \t whitespaces\n\n";
  7. str2.erase(std::remove_if(str2.begin(),
  8. str2.end(),
  9. [](unsigned char x){return std::isspace(x);}), //去除空格
  10. str2.end());
  11. std::cout << str2 << '\n';
  12. }

reduce/flod

reduce通过在一列值上应用二元运算符来获取一个单一的结果值。在C++中,std::accumulate提供reduce功能。
头文件为<numeric>

  1. #include <iostream>
  2. #include <vector>
  3. #include <numeric>
  4. int main()
  5. {
  6. std::vector<int> list = { 12,45,-102,33,78,-8,100,2017,-110 };
  7. const int result = std::accumulate(std::begin(list), std::end(list), 0,
  8. [](const int a, const int b) { return a > b ? a : b; });//获取最大值的lambda表达式
  9. std::cout << result << std::endl;
  10. }