Sirius' blog Sirius' blog
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Sirius0v0

怕什么真理无穷,进一寸有一寸的欢喜
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • Cpp小记
  • C++学习笔记-220705-V1
  • 进阶

    • CC++模板
    • CC++STL-常用容器
    • CC++STL-函数对象
    • CC++STL-常用算法
    • 现代C++:常量、变量与类型推导
    • 现代C++:函数式编程(lambda、函数对象包装器)
      • 函数作为另一个函数的参数
      • Lambda 表达式
        • lambda表达式作为返回值
        • lambda 用途举例
        • 关于lambda表达式的新特性说明
      • 函数对象包装器
        • std::bind 绑定
    • 现代C++:右值引用、移动语义与完美转发
    • 现代C++:智能指针
    • C++11多线程编程
  • 奇思妙用

  • 库的使用

  • 《C++》学习笔记
  • 进阶
Sirius0v0
2023-08-15
目录
函数作为另一个函数的参数
Lambda 表达式
lambda表达式作为返回值
lambda 用途举例
关于lambda表达式的新特性说明
函数对象包装器
std::bind 绑定

现代C++:函数式编程(lambda、函数对象包装器)

# 现代C++:函数式编程(lambda、函数对象包装器)

# 函数作为另一个函数的参数

函数其实也可以作为另一个函数的参数,而且,这个作为参数的函数也可以有参数。

void print_number(int n)
{
    std::cout << n << '\n';
}
void call_func(void func(int))
{
    func(0);
    func(1);
}

int main()
{
    call_func(print_number);
    return 0;
}

因为传入的 print_number 与定义的 void func(int) 类型匹配,因而可以作为参数传入。

为了简化,甚至可以将 func 的类型作为一个模板参数,从而无需写 void(int),同时允许函数的参数为其他类型,更好地进行自动适配与优化。

template<typename Func>
void call_func(Func func)
{
    func(0);
    func(1);
}

# Lambda 表达式

Lambda表达式提供了类似匿名函数的特性,其具体语法如下:

// [捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
// }

Lambda 表达式内部函数体在默认情况下是不能使用函数体外部的变量的,这时候捕获列表可以起到传递外部数据的作用。函数可以引用定义位置所有的变量,这一特性在函数式编程中称为闭包(closure)。根据传递的行为,捕获列表也分为以下四种:

值捕获、引用捕获、隐式捕获、表达式捕获

小贴士:lambda表达式,传常引用以避免拷贝开销

当一个函数需要lambda表达式作为函数参数时,最好把模板参数的Func声明为 Func const & 以避免不必要的拷贝。

这是由于捕获了变量之后,Func的大小将会变大。

# lambda表达式作为返回值

既然函数可以作为参数,当然也可以作为返回值,由于lambda表达式永远是个匿名类型,因此需要使用 auto 让其自动推导。

auto make_twice(int fac)
{
    return [=] (int n)
    {
        return n*fac;
    };
}

注意,此处捕获列表不可写成 [&](){};

因为退出函数后,fac在栈上已被销毁,所以引用失效。

总之,要是使用 [&] ,需要保证 lambda 对象的生命周期不超过他的捕获的所有引用的寿命。

# lambda 用途举例

# yield模式

可以延时求值。

template <typename Func>
void fetch_data(Func const &func) {
    for (int i = 0; i < 32; i++) {
        func(i);
        func(i + 0.5f);
    }
}

int main() {
    std::vector<int> res_i;
    std::vector<float> res_f;
    fetch_data([&](auto const &x) {
        using T = std::decay_t<decltype(x)>;
        if constexpr(std::is_same_v<T, int>) {
            res_i.push_back(x);
        } else if constexpr(std::is_same_v<T, float>){
            res_f.push_back(x);
        }
    });
    return EXIT_SUCCESS;
}

这里用 std::decay_t<> 去掉类型中的const与&,即:

decay_t<int const &> = int

is_same_v<int,int> = true

is_same_v<float,int> = false

# 用于立即求值

以下程序实现查找数字下标的功能。

std::vector<int> vec = {1,3,2,4,5,6};
int tofind = 5;
int index = [&]{
    for (int i = 0; i < vec.size(); i++)
        if (vec[i] == tofind)
            return i;
    return -1;
}();
std::cout << index << '\n';

# 实现局部递归

需要注意的是:

  • lambda需要把自己作为参数传入;
  • 返回类型需要自己定义,否则无法推断
auto fibonacci = [&](auto const &func, int index) {
    if (index == 1 || index == 2)
        return 1;
    return func(func, index-1)+func(func, index-2);
};

std::cout << fibonacci(fibonacci, 7); // 1 1 2 3 5 8 [13] 2

# 关于lambda表达式的新特性说明

另外,将lambda表达式的参数声明为 auto 时,基本上与 template<typaname T> 等价,和模板函数一样,具有惰性、多次编译的特性。缺点是前者无法进行特化。

auto twice = [](auto n){return n * 2;};
// 等价于
template <typename T>
auto twice(T n)
{
    return n * 2;
}

另另外,在C++20后,lambda 表达式中可以使用模板类型参数。例如,下面的 lambda 表达式使用了一个模板类型参数。

auto lambda = []<typename T>(std::vector<T> const &vec) -> std::vector<T>
{
    std::vector<T> result;
    return result;
};

# 函数对象包装器

在使用lambda表达式作为函数参数的时候,通常要借助模板来声明定义函数,这就会造成无法将声明与定义分离或者加快编译的问题,为了灵活性,可以使用函数对象包装器 std::function 容器,只需在尖括号里写函数的返回类型和参数列表即可:

#include <functional>
std::function<int(float, char *)>;

另外,当没有捕获任何局部变量,那么不需要使用 std::function<int(int)>,直接用函数指针类型 int (int) 即可,[](int n)->int{}; 会退化为 int (int)。

# std::bind 绑定

有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过 placeholder 函数,我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调用

int foo3(int a, int b, int c)
{
	std::cout << "a = " << a
		<< "\tb = " << b
		<< "\tc = " << c << '\n';
};
void test_bind()
{
	using std::placeholders::_1;
	auto binded_foo3 = std::bind(foo3, _1, 2, 3);
	binded_foo3(10);
}
编辑 (opens new window)
#Cpp
上次更新: 2023/08/16, 16:01:49
现代C++:常量、变量与类型推导
现代C++:右值引用、移动语义与完美转发

← 现代C++:常量、变量与类型推导 现代C++:右值引用、移动语义与完美转发→

最近更新
01
ipopt优化库配置及使用
07-21
02
ubuntu离线安装包的方法
07-21
03
其它控件的使用
03-05
更多文章>
Theme by Vdoing | Copyright © 2020-2025 Sirius0v0 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式