首页 > 编程语言 >C++06_函数式编程

C++06_函数式编程

时间:2022-09-20 23:56:07浏览次数:112  
标签:std 06 int auto 编程 C++ twice return lambda

函数也是对象

函数可以作为另一个函数的参数:

#include <cstdio>

template <class Func>
void call_wait(Func func) {
    func(0);
    func(1);
}

int main() {
    auto myFunc = [](int n) {
        printf("Number %d\n", n);
    };
    call_wait(myFunc);
    return 0;
}

并且,这个作为参数的函数也可以有参数:

#include <cstdio>

void print_number(int n) {
    printf("Number %d\n", n);
}

void call_twice(void func(int)) {
    func(0);
    func(1);
}

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

函数作为模板类型

甚至可以直接将 func 的类型作为一个模板参数,从而不需要写 void(int)。
这样还会允许函数的参数类型为其他类型,比如 void(float)。
这样 call_twice 会自动对每个不同的 func 类型编译一遍,从而允许编译器更好地进行自动适配与优化。

#include <cstdio>

void print_float(float f) {
    printf("Float %f\n", f);
}

void print_int(int n) {
    printf("Int %d\n", n);
}

template <class Func>
void call_twice(Func func) {
    func(0);
    func(1);
}

int main() {
    call_twice(print_float);
    call_twice(print_int);
    return 0;
}

lambda表达式

C++11 引入的 lambda 表达式允许我们在函数体内创建一个函数,大大地方便了函数式编程。
语法就是先一个空的 [],然后是参数列表,然后是 {} 包裹的函数体。
再也不用被迫添加一个全局函数了!

int main() {
    auto myFunc = [](int n) {
        printf("Number %d\n", n);
    };
    call_wait(myFunc);
    return 0;
}

lambda表达式:返回类型、自动推导返回类型

lambda 表达式的返回类型写在参数列表后面,用一个箭头 -> 表示。

int main() {
    auto twice = [](int n) -> int {
        return n * 2;
    };
    call_twice(twice);
    return 0;
}

如果 lambda 表达式不通过 -> 指定类型,则和 -> auto 等价,自动根据函数体内的 return 语句决定返回类型,如果没有 return 语句则相当于 -> void。

lambda表达式:捕获main中的变量、修改main中的变量

lambda 函数体中,还可以使用定义它的 main 函数中的变量,只需要把方括号 [] 改成 [&] 即可。
函数可以引用定义位置所有的变量,这个特性在函数式编程中称为闭包(closure)。

[&] 不仅可以读取 main 中的变量,还可以写入 main 中的变量,比如可以通过 counter++ 记录该函数被调用了多少次:

int main() {
    int fac = 2;
    int count = 0;
    auto twice = [&](int n) -> int {
        count++;
        return n * fac;
    };
    call_twice(twice);
    return 0;
}

lambda表达式:传常引用避免拷贝开销

此外,最好把模板参数的 Func 声明为 Func const & 以避免不必要的拷贝:

#include <iostream>

template <class Func>
void call_twice(Func const& func) {
    std::cout << func(0) << std::endl;
    std::cout << func(1) << std::endl;
    std::cout << "Func 的大小为:" << sizeof(func) << std::endl;
}

int main() {
    int fac = 2;
    int count = 0;
    auto twice = [&](int n) {
        count++;
        return n * fac;
    };
    call_twice(twice);
    std::cout << "调用了 " << count << " 次" << std::endl;
    return 0;
}

lambda表达式:作为返回值

既然函数可以作为参数,当然也可以作为返回值!
由于 lambda 表达式永远是个匿名类型,我们需要将 make_twice 的返回类型声明为 auto 让他自动推导。

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

int main() {
    auto twice = make_twice();
    call_twice(twice);
    return 0;
}

作为返回值:[&]出问题了

然而当我们试图用 [&] 捕获参数 fac 时,却出了问题:
fac 似乎变成 32764 了?
这是因为 [&] 捕获的是引用,是 fac 的地址,而 make_twice 已经返回了,导致 fac 的引用变成了内存中一块已经失效的地址。
总之,如果用 [&],请保证 lambda 对象的生命周期不超过他捕获的所有引用的寿命。

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

int main() {
    auto twice = make_twice(2);
    call_twice(twice);
    return 0;
}

作为返回值:[=]解决问题

这时,我们可以用 [=] 来捕获,他会捕获 fac 的值而不是引用。
[=] 会给每一个引用了的变量做一份拷贝,放在 Func 类型中。
不过他会造成对引用变量的拷贝,性能可能会不如 [&]。

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

lambda表达式:如何避免用模板参数

虽然 这样可以让编译器对每个不同的 lambda 生成一次,有助于优化。
但是有时候我们希望通过头文件的方式分离声明和实现,或者想加快编译,这时如果再用 template class 作为参数就不行了。
为了灵活性,可以用 std::function 容器。
只需在后面尖括号里写函数的返回类型和参数列表即可,比如:std::function<int(float, char *)>;

void call_twice(std::function<int(int)> const& func) {
    std::cout << func(0) << std::endl;
    std::cout << func(1) << std::endl;
    std::cout << "Func 的大小为:" << sizeof(func) << std::endl;
}

std::function<int(int)> make_twice(int fac) {
    return [=](int n) {
        return n * fac;
    };
}

int main() {
    auto twice = make_twice(2);
    call_twice(twice);
    return 0;
}

如何避免用模板参数2:无捕获的 lambda 可以传为函数指针

另外,如果你的 lambda 没有捕获任何局部变量,也就是 [],那么不需要用 std::function<int(int)>,直接用函数指针的类型 int(int) 或者 int(*)(int) 即可。
函数指针效率更高一些,但是 [] 就没办法捕获局部变量了(全局变量还是可以的)。
最大的好处是可以伺候一些只接受函数指针的 C 语言的 API 比如 pthread 和 atexit。

void call_twice(int func(int)) {
    std::cout << func(0) << std::endl;
    std::cout << func(1) << std::endl;
    std::cout << "Func 的大小为:" << sizeof(func) << std::endl;
}

int main() {
    call_twice([](int n) {
        return n * 2;
    });
    return 0;
}

lambda + 模板:双倍快乐

可以将 lambda 表达式的参数声明为 auto,声明为 auto 的参数会自动根据调用者给的参数推导类型,基本上和 template 等价。
auto const & 也是同理,等价于模板函数的 T const &。
带 auto 参数的 lambda 表达式,和模板函数一样,同样会有惰性、多次编译的特性。

template <class Func>
void call_twice(Func const& func) {
    std::cout << func(1) << std::endl;
    std::cout << func(3.14f) << std::endl;
}

int main() {
    call_twice([](auto n) {
        return n * 2;
    });
    return 0;
}

/** 等价于:
template <class T>
 auto twice(T n) {
    return n*2;
 }
*/

C++20前瞻:函数也可以 auto,lambda 也可以

void call_twice(auto const& func) {
    std::cout << func(1) << std::endl;
    std::cout << func(3.14f) << std::endl;
}

int main() {
    call_twice([] <class T> (auto n) {
        return n * 2;
    });
    return 0;
}

lambda 用途举例:yield模式

这里用了 type_traits 来获取 x 的类型。

  • decay_t<int const &> = int
  • is_same_v<int, int> = true
  • is_same_v<float, int> = false

更多这类模板请搜索 c++ type traits。

template <class 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 0;
}

lambda 用途举例:立即求值

再也不需要烦人的 flag 变量

int main() {
    std::vector<int> arr = {1, 2, 3, 4, 5};
    int tofind = 3;
    int index = [&]() {
        for (int i = 0; i < arr.size(); ++i) {
            if (tofind == arr[i])
                return i;
        }
    }();
    std::cout << index << std::endl;
    return 0;
}

lambda 用途举例:局部实现递归(匿名递归)

int main() {
    std::vector<int> arr = {1, 4, 2, 8, 5, 7, 1, 4};
    std::set<int> visited;
    auto dfs = [&] (auto const& dfs, int index) -> void {
        if (visited.find(index) == visited.end()) {
            visited.insert(index);
            std::cout << index << std::endl;
            int next = arr[index];
            dfs(dfs, next);
        }
    };
    dfs(dfs, 0);
    return 0;
}

Reference:

标签:std,06,int,auto,编程,C++,twice,return,lambda
From: https://www.cnblogs.com/cloudflow/p/16714122.html

相关文章

  • Java无难事:详解Java编程核心思想与技术 pdf
    高清扫描版下载链接:https://pan.baidu.com/s/1Ht352zrCXy9ArE-Th9fgNg点击这里获取提取码 ......
  • 根据键盘输入的三角形三边长度,利用海伦公式编程三角形面积
    提示:三角形三边(a,b,c)用海伦公式求面积公式如下:L=(a+b+c)/2面积area=(L(L-a)(L-b)(L-c))**0.5 样例输入345 样例输出6.0 样例输入6810 样例......
  • 编程计算圆面积的计算(结果保留2位小数
    提示:计算圆面积公式为area=π*radius*radius,其中π取3.14。 样例输入2 样例输出12.56 解题代码r=float(input())pi=3.14k=pi*r*rprint('%.2f'%(k......
  • T1031:反向输出一个三位数(信息学一本通C++)
     目录[题目描述]将一个三位数反向输出,例如输入358,反向输出853。[输入]一个三位数n。[输出]反向输出n。[输入样例]100[输出样例]001 #include<iostream>u......
  • T1033:计算线段长度(信息学一本通C++)
     目录[题目描述]已知线段的两个端点的坐标A(Xa,Ya),B(Xb,Yb),求线段AB的长度,保留到小数点后3位。[输入]第一行是两个实数Xa,Ya,即A的坐标。第二行是两个实数Xb,Yb,即B的......
  • T1035:等差数列末项计算(信息学一本通C++)
     目录[题目描述]给出一个等差数列的前两项a1,a2,求第n项是多少。。[输入]一行,包含三个整数a1,a2,na1,a2,n。−100≤a1,a2≤100,0<n≤1000。[输出]一个整数,即第n项的值。。......
  • T1034:计算三角形面积(信息学一本通C++)
     目录[题目描述]平面上有一个三角形,它的三个顶点坐标分别为(x1,y1),(x2,y2),(x3,y3),那么请问这个三角形的面积是多少,精确到小数点后两位。[输入]输入仅一行,包括......
  • T1037:计算2的幂(信息学一本通C++)
     目录[题目描述]非负整数n,求2^n,即2的n次方。。[输入]一个整数n。0≤n<31。[输出]一个实数,即线段AB的长度,保留到小数点后3位。[输入样例]2[输出样例]8 ......
  • C++ shared_ptr
    shared_ptrshared_ptr是C++11提供的一种智能指针类,它足够智能,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。shared_ptr使用引......
  • T1036:A*B问题(信息学一本通C++)
     目录[题目描述]输入两个正整数A和B,求A*B的值。注意乘积的范围和数据类型的选择。[输入]一行,包含两个正整数A和B,中间用单个空格隔开。1≤A,B≤50000。[输出]两......