【深蓝学院C++】第10章 泛型算法与 Lambda 表达式

泛型算法与 Lambda 表达式

1.泛型算法

​ 可以支持多种类型的算法

(1)重点讨论 C++ 标准库中定义的算法

(2)为什么要引入泛型算法而不采用方法的形式

​ 内建数据类型不支持方法

​ 计算逻辑存在相似性,避免重复定义

(3)如何实现支持多种类型

​ 使用迭代器作为算法与数据的桥梁

(4)一些泛型算法与方法同名,实现功能类似,此时建议调用方法而非算法

​ std::find V.S. std::map::find

(5)分类(查资料,自己理解)

​ 1读算法:给定迭代区间,读取其中的元素并进行计算

​ accumulate / find / count

​ 2写算法:向一个迭代区间中写入元素

​ 单纯写操作: fill / fill_n

​ 读 + 写操作: transform / copy

​ 注意:写算法一定要保证目标区间足够大

​ 3排序算法:改变输入序列中元素的顺序

​ sort / unique

(6)泛型算法使用迭代器实现元素访问

​ 迭代器的分类

​ 1输入迭代器:可读,可递增 典型应用为 find 算法

​ 2输出迭代器:可写,可递增 典型应用为 copy 算法

​ 3前向迭代器:可读写,可递增 典型应用为 replace 算法

​ 4双向迭代器:可读写,可递增递减 典型应用为 reverse 算法

​ 5随机访问迭代器:可读写,可增减一个整数 典型应用为 sort 算法

(7)一些特殊的迭代器

​ 1插入迭代器: back_insert_iterator / front_insert_iterator / insert_iterator

#include <iostream>
#include <algorithm>
#include <vector>

int main()
{
    std::vector<int> x;
    std::fill_n(std::back_insert_iterator<std::vector<int>>(x), 10, 3);
    //std::fill_n(std::back_inserter(x), 10, 3);
    for (auto i : x)
    {
        std::cout << i << ' ';
    }
    std::cout << std::endl;
}

​ 2流迭代器: istream_iterator / ostream_iterator

#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>

int main()
{
	std::istringstream str("1 2 3 4 5");
    std::istream_iterator<int> x(str);
    std::cout << *x << std::endl;
}
#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>

int main()
{
    std::istringstream str("1 2 3 4 5");
    std::istream_iterator<int> x(str);
    std::istream_iterator<int> y{};
    for (; x != y; ++x)
    {
        std::cout << *x << std::endl;
    }
}

​ 3反向迭代器

#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <numeric>

int main()
{
	std::vector<int> x{1, 2, 3, 4, 5};
    std::copy(x.begin(), x.end(), std::ostream_iterator<int>(std::cout, " "));
}

​ 4移动迭代器: move_iterator

(8)并发算法( C++17 / C++20 )

​ 1std::execution::seq

​ 2std::execution::par

​ 3std::execution::par_unseq

​ 4std::execution::unseq

2.bind 与 lambda 表达式

(1)可调用对象

​ 1函数指针:概念直观,但定义位置受限

​ 2类:功能强大,但书写麻烦

​ 3bind :基于已有的逻辑灵活适配,但描述复杂逻辑时语法可能会比较复杂难懂

​ 4lambda 表达式:小巧灵活,功能强大

(2)bind:通过绑定的方式修改可调用对象的调用方式

​ 1早期的 bind 雏形: std::bind1st / std::bind2nd

​ 具有了 bind 的基本思想,但功能有限

​ 2std::bind ( C++11 引入):用于修改可调用对象的调用方式

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <functional>

bool MyPredict(int val)
{
    return val > 3;
}
bool MyPredict2(int val1, int val2)
{
    return val1 > val2;
}

int main()
{
    using namespace std::placeholders;
    std::vector<int> x{1, 2, 3, 4, 5,  6, 7, 8, 9, 10};
    std::vector<int> y;
    std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind(MyPredict2, _1, 3));
    for (auto p : y)
    {
        std::cout << p << ' ';
    }
    std::cout << std::endl;
}
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>

bool MyPredict2(int val1, int val2)
{
    return val1 > val2;
}

int main()
{
    using namespace std::placeholders;
	auto x = std::bind(MyPredict2, _1, 3);	//调用时第1个参数与3比较
    x(50);
}
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>

bool MyPredict2(int val1, int val2)
{
    return val1 > val2;
}

bool MyAnd(bool val1, bool val2)
{
    return val1 && val2;
}

int main()
{
    using namespace std::placeholders;
	auto x1 = std::bind(MyPredict2, _1, 3);
    auto x2 = std::bind(MyPredict2, 10, _1);
    auto x3 = std::bind(MyAnd, x1, x2);
    std::cout << x3(5) << std::endl;
}

​ 调用 std::bind 时,传入的参数会被复制,这可能会产生一些调用风险

​ 可以使用 std::ref 或 std::cref 避免复制的行为

​ 3std::bind_front ( C++20 引入): std::bind 的简化形式

#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>

bool MyPredict2(int val1, int val2)
{
    return val1 > val2;
}

int main()
{
    using namespace std::placeholders;
	auto y = std::bind_front(MyPredict2, 3);	//3是第1个参数
    std::cout << y(2) << std::endl;
}

(3)lambda 表达式

​ 为了更灵活地实现可调用对象而引入

​ C++11 ~ C++20 持续更新

​ C++11 引入 lambda 表达式

​ C++14 支持初始化捕获、泛型 lambda

​ C++17 引入 constexpr lambda , *this 捕获

​ C++20 引入 concepts ,模板 lambda

​ lambda 表达式会被编译器翻译成类进行处理

​ 1lambda 表达式的基本组成部分

​ 1)参数与函数体

#include <iostream>

int main()
{
    //auto x = [](int val) {return val > 3;};
    auto x = [](int val) 
    {
        return val > 3 && val < 10;
    };
    std::cout << x(5) << std::endl;
}

​ 2)返回类型

​ 自动推导,但是类型要相同

#include <iostream>

int main()
{
    auto x = [](int val) 
    {
        if (val > 3)
        {
            return 3.0;
        }
        else
        {
            return 1.5;
        }
    };
    std::cout << x(5) << std::endl;
}

​ 如果不相同,需显式写出返回类型

#include <iostream>

int main()
{
    auto x = [](int val) -> float
    {
        if (val > 3)
        {
            return 3.0;
        }
        else
        {
            return 1.5f;
        }
    };
    std::cout << x(5) << std::endl;
}

​ 3)捕获:针对函数体中使用的局部自动对象进行捕获

#include <iostream>

int main()
{
    int y = 10;
    auto x = [y](int val) 	//[]目的是捕获
    {
        return val > y;
    };
    std::cout << x(5) << std::endl;
}

​ 值捕获、引用捕获与混合捕获

#include <iostream>

int main()
{
    int y = 10;
    auto x = [y] (int val) mutable	//[]目的是捕获
    {
        ++y;
        return val > y;
    };
    std::cout << x(5) << std::endl;
    std::cout << y << std::endl;	//Lambda表达式内部对y的修改不会传到外部,y是值捕获
}
#include <iostream>

int main()
{
    int y = 10;
    auto x = [&y] (int val)	//[]目的是捕获
    {
        ++y;
        return val > y;
    };
    std::cout << x(5) << std::endl;
    std::cout << y << std::endl;	//引用捕获修改了y的值
}
#include <iostream>

int main()
{
    int y = 10;
    int z = 4;
    auto x = [&y, z] (int val)	//混合捕获
    {
        ++y;
        return val > z;
    };
    std::cout << x(5) << std::endl;
    std::cout << y << std::endl;	//引用捕获修改了y的值
}
#include <iostream>

int main()
{
    int y = 10;
    int z = 4;
    auto x = [=] (int val)	// = 代表值捕获, & 代表引用捕获
    {
        return val > z;
    };
    std::cout << x(5) << std::endl;
    std::cout << y << std::endl;	//引用捕获修改了y的值
}

​ this 捕获

#include <iostream>
struct Str
{
	auto fun()
    {
        int val = 3;
        auto lam = [val, this]()
        {
        	return val > x;  
        };
        return lam();
    }
    int x;
};
int main()
{
	Str s;
    s.fun();
}

​ 初始化捕获( C++14 )

#include <iostream>

int main()
{
	int x = 3;
    auto lam = [y = x](int val)
    {
        return val > y;
    };
    std::cout << lam(100) << std::endl;
}
#include <iostream>
#include <string>
int main()
{
    std::string a = "hello";
    auto lam = [y = std::move(a)]()
    {
        std::cout << y << std::endl;
    };
    lam();
    std::cout << a << std::endl;
}
#include <iostream>
#include <string>
int main()
{
    int x = 3;
    int y = 8;
    auto lam = [z = x + y](int val)
    {
        return val > z;
    };
}

​ *this 捕获( C++17 )

#include <iostream>
struct Str
{
	auto fun()
    {
        int val = 3;
        auto lam = [val, *this]()
        {
        	return val > x;  
        };
        return lam;
    }
    int x;
};

auto wrapper()
{
    Str s;
    return s.fun();
}
int main()
{
	auto lam = wrapper();
    lam();
}

​ 4)说明符

​ mutable / constexpr (C++17) / consteval (C++20)…

#include <iostream>

int main()
{
    int y = 10;
    auto x = [y] (int val) mutable		//mutable使得y可修改
    {
        ++y;
        return val > y;
    };
    std::cout << x(5) << std::endl;
    std::cout << y << std::endl;
}
#include <iostream>

int main()
{
    auto lam = [] (int val) constexpr
    {
        return val + 1;
    };
    constexpr val = lam(100);
    std::cout << val << std::endl;
}

​ 5)模板形参( C++20 )

#include <iostream>

int main()
{
    auto lam = []<typename T> (T val)
    {
        return val + 1;
    };
    constexpr val = lam(100);
    std::cout << val << std::endl;
}

​ 2lambda 表达式的深入应用

​ 1)捕获时计算( C++14 )

#include <iostream>
#include <string>
int main()
{
    int x = 3;
    int y = 8;
    auto lam = [z = x + y]()
    {
        return z;
    };
    lam();
}

​ 2)即调用函数表达式( Immediately-Invoked Function Expression, IIFE )

#include <iostream>

int main()
{
    int x = 3;
    int y = 8;
    const auto lam = [z = x + y]()
    {
        return z;
    }();
    
    std::cout << lam << std::endl;
}

​ 3)使用 auto 避免复制( C++14 )

#include <iostream>
#include <map>
int main()
{
    std::map<int, int> m{{2, 3}};
    /*
    auto lam = [](const std::pair<int, int>& p)
    {
        return p.first + p.second;
    };
    */
    auto lam = [](const auto& p)
    {
        return p.first + p.second;
    };
    std::cout << lam(*m.begin()) << std::endl;
}

​ 4)Lifting ( C++14 )

#include <iostream>
auto fun(int val)
{
    return val + 1;
}
auto fun(double val)
{
    return val + 1;
}
int main()
{
    auto lam = [](auto x)
    {
        return fun(x);
    }
	std::cout << lam(3) << std::endl;
    std::cout << lam(3.5) << std::endl;
}

​ 5)递归调用( C++14 )有点难。。。

#include <iostream>
int factorial(int n)
{
    return n > 1 ? n * factorial(n - 1) : 1;
}
int main()
{
    std::cout << factorial(5) << std::endl;
}
#include <iostream>

int main()
{
    auto factorial = [](int n)
    {
        auto f_impl = [](int n, const auto& impl) -> int	//内层必须写返回类型
        {
            return n > 1 ? n * impl(n - 1, impl) : 1;
        };
        return f_impl(n, f_impl);
    };
    std::cout << factorial(5) << std::endl;
}

3.泛型算法的改进—— ranges

(1)可以使用容器而非迭代器作为输入

​ 通过 std::ranges::dangling 避免返回无效的迭代器

#include <iostream>
#include <algorithm>
#include <vector>
#include <ranges>
int main()
{
    std::vector<int> x{1, 2, 3, 4, 5};
    auto it = std::find(x.begin(), x.end(), 3);
    std::cout << *it << std::endl;

    auto it2 = std::ranges::find(x, 3);
    std::cout << *it2 << std::endl;
}
#include <iostream>
#include <algorithm>
#include <vector>
#include <ranges>
auto fun()
{
    return std::vector<int> {1, 2, 3, 4, 5};
}
int main()
{
    std::vector<int> x{1, 2, 3, 4, 5};
    auto it = std::ranges::find(fun(), 3);
    std::cout << *it << std::endl;
}

(2)从类型上区分迭代器与哨兵

(3)引入映射概念,简化代码编写

(4)引入 view ,灵活组织程序逻辑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值