ch10.泛型算法与Lambda表达式目录

  1. 泛型算法
  2. bind 与 lambda 表达式
  3. 泛型算法的改进—— ranges

一.泛型算法

  • 泛型算法:可以支持多种类型的算法
    • 这里重点讨论 C++ 标准库中定义的算法
      • <algorithm> <numeric> <ranges>
    • 为什么要引入泛型算法而不采用方法的形式
      • 内建数据类型不支持方法
      • 计算逻辑存在相似性,避免重复定义
    • 如何实现支持多种类型:使用迭代器作为算法与数据的桥梁
  • 泛型算法通常来说都不复杂,但优化足够好
  • 一些泛型算法与方法同名,实现功能类似,此时建议调用方法而非算法
    • std::find V.S. std::map::find ```c #include #include//包含了C++一些基本的泛型函数 #include//包含了一些数值的算法 #include//C++20的一个库,包含了一些算法

int main(){ std::vector x(100); // int a[100]; //sort就是泛型,支持vector\内建数组 std::sort(std::begin(x),std::end(x)); //内建数据类型不支持方法 int a[100]; // a.sort();//没有方法

std::vector a(100); std::sort(a.begin(),a.end()); //一些泛型算法与方法同名,实现功能类似,此时建议调用方法而非算法 }

## 1.1.泛型算法的分类
- 读算法:给定迭代区间,读取其中的元素并进行计算
  - [accumulate](https://zh.cppreference.com/w/cpp/algorithm/accumulate) / [find](https://zh.cppreference.com/w/cpp/algorithm/find) / [count](https://zh.cppreference.com/w/cpp/algorithm/count)
  - #include<algorithm>
  - 只读
  ```c
  /*----accumulate:累积----*/
  //版本一:init累加的初始值,first-last,累加的区间
  template<class InputIt, class T>
  constexpr // C++20 起
  T accumulate(InputIt first, InputIt last, T init)
  {
      for (; first != last; ++first) {
          init = std::move(init) + *first; // C++20 起有 std::move
      }
      return init;
  }
  //版本二
  /* op	-	被使用的二元函数对象。接收当前积累值 a (初始化为 init )
  和当前元素 b 的二元运算符。该函数的签名应当等价于:
  Ret fun(const Type1 &a, const Type2 &b);
  自定义op,可以实现其他广义的累积:减法,乘法等等*/
  template<class InputIt, class T, class BinaryOperation>
  constexpr // C++20 起
  T accumulate(InputIt first, InputIt last, T init, 
                BinaryOperation op)
  {
      for (; first != last; ++first) {
          init = op(std::move(init), *first); // C++20 起有 std::move
      }
      return init;
  }
  /*----find----*/
  //指向首个满足条件的迭代器,或若找不到这种元素则为 last 。
  //版本一
  template<class InputIt, class T>
  InputIt find(InputIt first, InputIt last, const T& value)
  {
      for (; first != last; ++first) {
          if (*first == value) {
              return first;
          }
      }
      return last;
  }
  //版本二
  template<class InputIt, class UnaryPredicate>
  InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
  {
      for (; first != last; ++first) {
          if (p(*first)) {
              return first;
          }
      }
      return last;
  }
  /*----count----*/
  https://zh.cppreference.com/w/cpp/algorithm/count
  • 写算法:向一个迭代区间中写入元素
    • 单纯写操作: fill / fill_n
    • 读 + 写操作: transform / copy
    • 注意:写算法一定要保证目标区间足够大
      /*----fill----*/
      //first, last	-	要修改的元素范围
      //value	-	要赋的值
      //policy	-	所用的执行策略。细节见执行策略。
      template< class ForwardIt, class T >
      void fill(ForwardIt first, ForwardIt last, const T& value)
      {
        for (; first != last; ++first) {
            *first = value;
        }
      }
      /*----fill_n----*/
      template<class OutputIt, class Size, class T>
      OutputIt fill_n(OutputIt first, Size count, const T& value)
      {
        for (Size i = 0; i < count; ++i) {
            *first++ = value;
        }
        return first;
      }
      
      /*----transform----*/
      https://zh.cppreference.com/w/cpp/algorithm/transform
      //返回值:指向最后一个变换的元素的输出迭代器。
      //InputIt first1, InputIt last1:用来读的参数,输入
      //OutputIt d_first:写入区间的开头位置,输出 
      //UnaryOperation unary_op:一元操作符
      //版本一
      template<class InputIt, class OutputIt, class UnaryOperation>
      OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, 
                      UnaryOperation unary_op)
      {
        while (first1 != last1) {
          //对first1解引用,然后进行unary_op一元操作,
          //最后写到d_first中
            *d_first++ = unary_op(*first1++);
        }
        return d_first;
      }
      //版本二
      template<class InputIt1, class InputIt2, 
            class OutputIt, class BinaryOperation>
      OutputIt transform(InputIt1 first1, InputIt1 last1, InputIt2 first2, 
                      OutputIt d_first, BinaryOperation binary_op)
      {
        while (first1 != last1) {
            *d_first++ = binary_op(*first1++, *first2++);
        }
        return d_first;
      }
      
      /*----copy----*/
      //https://zh.cppreference.com/w/cpp/algorithm/copy
      //返回值:指向目标范围中最后复制元素的下个元素的输出迭代器。
      //InputIt first1, InputIt last1:用来读的参数,输入
      //OutputIt d_first:写入区间的开头位置,输出 
      //版本一
      template<class InputIt, class OutputIt>
      OutputIt copy(InputIt first, InputIt last, 
                  OutputIt d_first)
      {
        while (first != last) {
            *d_first++ = *first++;
        }
        return d_first;
      }
      
  • 排序算法:改变输入序列中元素的顺序
    /*----sort----*/
    https://zh.cppreference.com/w/cpp/algorithm/sort
    //RandomIt first, RandomIt last:排序的范围
    //缺省情况使用<操作符排序,既是从小到大排序
    
    /*----unique----*/
    //https://zh.cppreference.com/w/cpp/algorithm/unique
    //要求:传入的是已经排序好的元素,然后将重复的元素删除,只保留一个
    //版本一
    template<class ForwardIt>
    ForwardIt unique(ForwardIt first, ForwardIt last)
    {
        if (first == last)
            return last;
      
        ForwardIt result = first;
        while (++first != last) {
            if (!(*result == *first) && ++result != first) {
                *result = std::move(*first);
            }
        }
        return ++result;
    }
    
    //exp
    #include<iostream>
    #include<algorithm>
    
    int main(){
      //1 2 2 3 4
      //unique操作后
      //1 2 3 4
      //实际上内存中保存的是
      //1 2 3 4 (返回迭代器的位置last)4  最后一个4是之前的,没有修改
      v.erase(last, v.end()); //从返回的位置开始到结束,进行删除操作
    }
    

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

迭代器的分类:

  • 输入迭代器:可读,可递增 —— 典型应用为 find 算法
  • 输出迭代器:可写,可递增 —— 典型应用为 copy 算法
  • 前向迭代器:可读写,可递增 —— 典型应用为 replace 算法
  • 双向迭代器:可读写,可递增递减 —— 典型应用为 reverse 算法
  • 随机访问迭代器:可读写,可增减一个整数 —— 典型应用为 sort 算法

一些算法会根据迭代器类型的不同引入相应的优化:如 distance 算法

1.2.一些特殊的迭代器

  • 插入迭代器: back_insert_iterator / front_insert_iterator / insert_iterator

  • back_insert_iterator底层:push_back
  • front_insert_iterator底层:push_front
      #include <iostream>
      #include <iterator>
      #include <deque>
      #include <vector>
      #include <list>
      int main()
      {
      std::deque<int> q;
      std::back_insert_iterator< std::deque<int> > it(q);
      
      for (int i=0; i<10; ++i)
          it = i; // 调用 q.push_back(i)
      // 0 1 2 3 4 5 6 7 8 9
      for (auto& elem : q) std::cout << elem << ' ';
      --------
    
      std::vector<int> x;
      std::fill_n(std::back_insert_iterator<std::vector<int>>(x), 10, 3);
      //简化写法
      //std::fill_n(std::back_insert<std::vector<int>>(x), 10, 3);
      //3 3 3 3 3 3 3 3 3 3
      for(auto i:x)
        std::cout<< i <<' ';
      ----
      std::list<int> x;//list才支持push_front
      std::fill_n(std::front_insert<std::vector<int>>(x), 10, 3);
      //3 3 3 3 3 3 3 3 3 3
      for(auto i:x)
        std::cout<< i <<' ';
      }
    
  • 流迭代器: istream_iterator / ostream_iterator
      /*----istream_iterator----*/
      #include<iostream>
      #include<algorithm>
      #include<vector>
      #include<list>
      #include<iterator>
      #include<sstream>
      #include<numeric>
      int main(){
     std::istringstream Str{"1 2 3 4 5"};
     std::istream_iterator<int> x{Str};
     //1  ==>int tmp; Str>>tmp;//输入流迭代器
     std::cout<< *x <<std::endl;
     ++x;//istream_iterator维护一个对象,Str>>tmp;//输入流迭代器
     std::cout<< *x <<std::endl;//2
        
     //缺省初始化,可以表示流迭代器的结束位置
     std::istream_iterator<int> y{};
     for (x!=y){
       std::cout<< *x <<std::endl;//1 2 3 4 5
     }
    
     //x为迭代器的开始.y为迭代器的结束,累加结果为15
     int res = std::accumulate(x,y,0);
     std::cout<<res<<std::endl;//15
      }
    

    ```c /—-ostream_iterator—-/ #include #include #include #include

int main() { std::istringstream str(“0.1 0.2 0.3 0.4”); std::partial_sum(std::istream_iterator(str), std::istream_iterator(), std::ostream_iterator(std::cout, " ")); } //输出: 0.1 0.3 0.6 1


 - 反向迭代器:从后向前遍历
![](/CPP-Note/assets/images/ch10/00.png)

  ```c
  /*----ostream_iterator----*/
  #include <iostream>
  #include <sstream>
  #include <iterator>
  #include <numeric>
  #include <vector>

  int main()
  {
      std::vector<int> x{1,2,3,4,5};
      std::copy(x.rbegin(),x.rend(),
        std::ostream_iterator<int>(std::cout, " "));//54321
  }
  //输出:  0.1 0.3 0.6 1
  • 移动迭代器: move_iterator ```c /—-ostream_iterator—-/ #include #include #include #include #include #include #include

int main() { std::vector<std::string> v{“this”, “”, “is”, “”, “an”, “_”, “example”}; //lambda表达式,打印出v的元素 auto print_v = & { std::cout « rem; for (const auto& s : v) std::cout « std::quoted(s) « ’ ‘; std::cout « ‘\n’; };

  print_v("Old contents of the vector: ");
  //按照字符串的相加方式进行累加(字符串拼接)
  std::string concat = std::accumulate(std::make_move_iterator(v.begin()),
                                      std::make_move_iterator(v.end()),
                                      std::string());
  
  /* 使用 std::move_iterator 的替代方式可能是:
  using moviter_t = std::move_iterator<std::vector<std::string>::iterator>;
  //moviter_t 性能同move会移动变量
  std::string concat = std::accumulate(moviter_t(v.begin()),
                                      moviter_t(v.end()),
                                      std::string()); */
  
  print_v("New contents of the vector: ");
  
  std::cout << "Concatenated as string: " << quoted(concat) << '\n';   }   可能的输出:   Old contents of the vector: "this" "_" "is" "_" "an" "_" "example"    New contents of the vector: "" "" "" "" "" "" ""   Concatenated as string: "this_is_an_example"   ```   ```c  #include <iostream>   #include <iomanip>   #include <algorithm>   #include <vector>   #include <iterator>   #include <numeric>   #include <string>

int main(){ std::string x = “abc”; auto y = x; std::cout« x « std::endl;//abc

autu a = std::move(x);
//空,因为x被移动了,将亡值小结的知识点
std::cout<< x << std::endl;   }   ```

1.3.迭代器与哨兵( Sentinel )

哨兵:通常用来标记迭代器区间结尾的地方,迭代不断的递增,终有一刻和结尾的迭代器相等,哨兵就是判断是否与迭代器相等,以find函数为例,其输入的迭代器区间是同一种类型的InputIt first, InputIt last;但是ranges算法输入的迭代器类型可以不相同,只要可以进行比较判断即可,此时last就称为一个哨兵类型.

1.4.并发算法( C++17 / C++20 )

std::execution::seq/par/par_unseq/unseq四种执行的方式.

  • std::execution::seq:顺序执行
  • std::execution::par:并发执行,多线程
  • std::execution::par_unseq:并发非顺序执行,一个线程处理多条命令
  • std::execution::unseq(C++20):
#include<iostream>
#include<algorithm>
#include<chrono>
#include<random>
#include<ratio>
#include<vector>
#include<execution>

int main(){
  std::random_device rd;

  std::vector<double>  vals(10000000);
  for(auto& d:vals){
    d = static_cast<double>(rd());
  }

  for(int i = 0; i<5; ++i){
    using namespace std::chrono;
    std::vector<double> sorted(vals);
    const auto startTime = high_resolution_clock::now();
    //原始
    // std::sort(sorted,begin(), sorted.end());
    // std::execution::unseq(C++20)
    std::sort(std::execution::unseq,\ 
              sorted,begin(), sorted.end());
    //std::execution::par:并发执行,多线程,有性能提升
    std::sort(std::execution::par,\ 
              sorted,begin(), sorted.end());    

    const auto endTime = high_resolution_clock::now();
    std::cout<<"Latency: "
             <<duration_cast<duration<double,std::milli>>(endTime - startTime).count()\
             <<std::endl;
  }
}

二.bind 与 lambda 表达式

  • 很多算法允许通过可调用对象自定义计算逻辑的细节
    • transform / copy_if / sort… sort自定义比较的方式等
      #include<functional>
      #include<iostream>
      
      bool MyPredict(int val){
        return val > 3;
      }
      int main(){
        std::vector<int> x{1,2,3,4,5,6,7,8,9,10};
        std::vector<int> y;
        // back_inserter对y进行push_back操作
        std::copy_if(x.begin().x.end(),std::back_inserter(y),MyPredict);
        // 输出:4,5,6,7,8,9,10
        for(auto p:y){
          std::cout<< p <<' ';
        }
        std::cout<<std::endl;
      }
      
  • 如何定义可调用对象
    • 函数指针:概念直观,但定义位置受限:不能在函数内部定义函数
       #include<functional>
       #include<iostream>
       #include<vector>
      
       int main(){
         std::vector<int> x{1,2,3,4,5,6,7,8,9,10};
         std::vector<int> y;
         //在main函数内部定义函数,非法
         bool MyPredict(int val){
           return val > 3;
         }
         // back_inserter对y进行push_back操作
         // 函数指针调用的一种缺陷
         std::copy_if(x.begin().x.end(),std::back_inserter(y),MyPredict);
         // 输出:4,5,6,7,8,9,10
         for(auto p:y){
           std::cout<< p <<' ';
         }
         std::cout<<std::endl;
       }
      
    • 类:功能强大,但书写麻烦:对函数重载,操作符重载,对类引入一些方法
    • bind :基于已有的逻辑灵活适配,但描述复杂逻辑时语法可能会比较复杂难懂
    • lambda 表达式:小巧灵活,功能强大

2.1.bind

  • 通过绑定的方式修改可调用对象的调用方式
  • 早期的 bind 雏形: std::bind1st / std::bind2nd
    • 具有了 bind 的基本思想,但功能有限(基本已经移出标准了) std::greater
        template< class T >
        struct greater;
        // 检查第一参数是否大于第二个 
        constexpr bool operator()(const T &lhs, const T &rhs) const 
        {
            return lhs > rhs; // 假定实现使用平坦地址空间
        }
      
        #include<functional>//bind
        #include<iostream>
        #include<algorithm>
        #include<vector>
        int main(){
          std::vector<int> x{1,2,3,4,5,6,7,8,9,10};
          // back_inserter对y进行push_back操作
          // std::bind2nd(std::greater<int>(),3)
          // std::bind2nd,表示把第二个参数固定为3,
          // 所以此时,只要第一个参数比3大,就返回true
          std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind2nd(std::greater<int>(),3));
          // 输出:4,5,6,7,8,9,10
          for(auto p:y){
            std::cout<< p <<' ';
          }
          std::cout<<std::endl;
          ----
          // std::bind1st,对greater的第一个参数进行绑定,第一个参数为3
          // 表示,3比输入的数字大就为true,所以此时为1,2
          std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind1st(std::greater<int>(),3));
          // 输出:1,2
          for(auto p:y){
            std::cout<< p <<' ';
          }
          std::cout<<std::endl;
        }
      

      功能还是比较受限

        #include<functional>//bind
        #include<iostream>
        #include<algorithm>
        #include<vector>
        bool MyPredict(int val1, int val2){
          return val1 > val2;
        }
        int main(){
          std::vector<int> x{1,2,3,4,5,6,7,8,9,10};
          //非法,只有部分标准库的函数才能使用bind1st,bind2nd
          std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind1st(MyPredict,3));
          for(auto p:y){
            std::cout<< p <<' ';
          }
          std::cout<<std::endl;
        }
      
  • std::bind ( C++11 引入):用于修改可调用对象的调用方式
    #include<functional>//bind
    #include<iostream>
    #include<algorithm>
    #include<vector>
    using namespace std::placeholders;//bind的命名空间
    bool MyPredict(int val1, int val2){
      return val1 > val2;
    }
    int main(){
      std::vector<int> x{1,2,3,4,5,6,7,8,9,10};
    
      std::copy_if(x.begin().x.end(),std::back_inserter(y),std::bind(MyPredict,_1,3));
      //输出4 5 6 7 8 9 10
      for(auto p:y){
        std::cout<< p <<' ';
      }
      std::cout<<std::endl;
    
      ---单独讨论---
      auto k = std::bind(MyPredict,_1,3);
      // 输出 1 
      std::cout<< k(50);//50会被作为MyPredict的第一个参数
      // 注意:这里的_1正确理解是调用时输入的第一个参数
      // 不是固定的指MyPredict的第一个参数,如下:
      auto k = std::bind(MyPredict,_1,3);//MyPredict(50 ,3)
      auto k = std::bind(MyPredict,3,-1);//MyPredict(3, 50)
    
      //函数指针后面的参数与函数参数一一对应
      auto k = std::bind(MyPredict, _2, 3);
      std::cout<< k("dad", 50, 1);//1:50>3
      // 所以这里的_1, _2, _3,是指调用时的第几个参数
    
      auto k = std::bind(MyPredict, _2, _1);
      std::cout<< k(3, 4);//MyPredict(4,3): 输出1
    
    }
    
    #include<functional>//bind
    #include<iostream>
    #include<algorithm>
    #include<vector>
    using namespace std::placeholders;//bind的命名空间
    bool MyPredict(int val1, int val2){
      return val1 > val2;
    }
    bool MyAnd(int val1, int val2){
      return val1 && val2;
    }
    int main(){
      auto x1 = std::bind(MyPredict,_1,3);//MyPredict(_1 ,3)
      auto x2 = std::bind(MyAnd, 10, _1);
      auto x3 = std::bind(MyAnd, x1, x2);
      std::cout<< x3(5);//输出1
      //5是bind的参数,5作为x1(5>3),x2(10>5)的_1进行计算,MyAnd为真
      auto x4 = std::bind(MyPredict, _1, _1);
      std::cout<< x4(5);//输出0(5>5)
    }
    
    • 调用 std::bind 时,传入的参数会被复制,这可能会产生一些调用风险
         #include<functional>//bind
         #include<iostream>
         #include<algorithm>
         #include<vector>
         #include<memory>
         using namespace std::placeholders;//bind的命名空间
         void MyProc(int* ptr){
      
         }
         auto fun(){
           int x;
           //&x是复制到MyProc里面的,当fun调用完,x就不存在了
           return std::bind(MyProc, &x);
         }
         int main(){
           auto ptr = fun();
           ptr();//调用指针就很危险,未定义
         }
         ======
         auto fun(){
           int x;
           //使用智能指针可以解决这个问题
           std::shared_ptr<int> x(new int());
           return std::bind(MyProc,x);
         }
         int main(){
           auto ptr = fun();
           ptr();
         }
         ======
         void Proc(int& x){
           ++x;
         }
         auto fun(){
           int x;
           //使用智能指针可以解决这个问题
           std::shared_ptr<int> x(new int());
           return std::bind(MyProc,x);
         }
         int main(){
           int x = 0;
           Proc(x);
           std::cout<< x <<std::endl;//1,正常逻辑
           -----
           int x = 0;
           auto b = std::bind(Proc, x);
           b();//将x拷贝给b,进行++,操作的是b内部的成员
           std::cout<< x <<std::endl;//0,小心有坑
           //可以使用 std::ref 或 std::cref 避免复制的行为
           -----
           int x = 0;
           auto b = std::bind(Proc, std::ref(x));//传引用
           b();//避免拷贝
           std::cout<< x <<std::endl;//1
         }
      
    • 可以使用 std::ref 或 std::cref 避免复制的行为
  • std::bind_front ( C++20 引入): std::bind 的简化形式,类似与std::bind1st的功能
    #include<functional>//bind
    #include<iostream>
    #include<algorithm>
    using namespace std::placeholders;//bind的命名空间
    bool MyPredict(int val1, int val2){
      return val1 > val2;
    }
    int main(){
      //bind_front固定第一个参数为3
      auto y = std::bind_front(MyPredict, 3);
      std::cout<< y(2) <<std::endl;//1, 3>2
    }
    

    2.2.lambda表达式

2.2.1.lambda 表达式

lambda 表达式( https://leanpub.com/cpplambda )

  • 为了更灵活地实现可调用对象而引入
  • C++11 ~ C++20 持续更新
    • C++11 引入 lambda 表达式
    • C++14 支持初始化捕获、泛型 lambda
    • C++17 引入 constexpr lambda , *this 捕获
    • C++20 引入 concepts ,模板 lambda
  • lambda 表达式会被编译器翻译成类进行处理

2.2.2.lambda 表达式的基本组成部分

  • 参数与函数体
  • 返回类型

    #include<functional>//bind
    #include<iostream>
    #include<algorithm>
    using namespace std::placeholders;//bind的命名空间
    //lambda构造的是一个对象
    int main(){
      auto x = [](int val){return val > 3;};//返回类型为bool:x为bool
      std::cout<< x(5);//1,  5>3 
      ----
      auto x = (int val)
      {//此时无法自动推到x的类型
        if(val > 3)
          return 3.0;//double
        else
          return 1.5f;//float
      };
      ----
      auto x = (int val) -> float//显式指定返回类型
      {
        if(val > 3)
          return 3.0;//double
        else
          return 1.5f;//float
      };
    }
    
  • 捕获:针对函数体中使用的局部自动对象进行捕获
    • 值捕获、引用捕获与混合捕获
      #include<functional>//bind
      #include<iostream>
      #include<algorithm>
      using namespace std::placeholders;//bind的命名空间
      //lambda构造的是一个对象
      int main(){
        int y = 10;//局部自动对象
        int z = 3;
        //这种y不能捕获,static静态和全局对象不需要捕获,可以直接使用
        //static int y = 10;
        auto x= [y](int val){//利用[]进行y值的捕获,然后复制到lambda内部
          return val>y;
        };
        std::cout<<x(5);//0; 5>10;
        ----
        //值捕获
        auto x= [y](int val) mutable//值捕获,将y的值复制到lambda内部
        {
          ++y;//这个y不是外部的y
          return val>y;
        };
        std::cout<<x(5)<<std::endl;//0
        //10,lambda内部y的计算不会传到外部
        std::cout<<y<<std::endl;//10
        -----
        //引用捕获
        auto x= [&y](int val) //引用捕获
        {
          ++y;//这个y就是外部的y
          return val>y;
        };
        std::cout<<x(5)<<std::endl;//0
        //11,lambda引用捕获,传入引用到内部y的计算会传到外部
        std::cout<<y<<std::endl;//11
        -----
        //混合捕获
        auto x= [&y, z](int val) //y为引用捕获,z为值捕获
        {
          ++y;//这个y就是外部的y
          return val>z;
        };
        std::cout<<x(5)<<std::endl;//5>3:1
        //11,lambda引用捕获,传入引用到内部y的计算会传到外部
        std::cout<<y<<std::endl;//11
        -----
        //自动分析,lambda里面用到了哪些局部对象,[=]全部默认为值捕获
        //[=]代表值捕获
        auto x= [=](int val) //y,z为值捕获
        {
          ++y;//值捕获
          return val>z;
        };
        std::cout<<x(5)<<std::endl;//5>3:1
        std::cout<<y<<std::endl;//10
        -----
        //自动分析,lambda里面用到了哪些局部对象,[&]全部默认为引用捕获
        //[&]代表引用捕获
        auto x= [&](int val) //y,z为引用捕获
        {
          ++y;//引用捕获
          return val>z;
        };
        std::cout<<x(5)<<std::endl;//5>3:1
        //11,lambda引用捕获,传入引用到内部y的计算会传到外部
        std::cout<<y<<std::endl;//11
        ----
        //除了z,其他全部默认为引用捕获
        auto x= [&, z](int val) //z为值捕获
        {
          ++y;
          return val>y;
        };
        std::cout<<x(5)<<std::endl;//0
        std::cout<<y<<std::endl;//11
        ----
        //y为引用捕获,其他全部默认为值捕获
        auto x= [=, &y](int val)
        {
          ++y;
          return val>y;
        };
        std::cout<<x(5)<<std::endl;//0
        std::cout<<y<<std::endl;//11
      }
      
      
    • this 捕获
      #include<functional>//bind
      #include<iostream>
      #include<algorithm>
      using namespace std::placeholders;//bind的命名空间
      //lambda构造的是一个对象
      struct Str{
        void fun()
        {
          int val = 3;
          //this指向当前的Str对象,这样一来就相当于对x声明过了
          auto lam = [val,this]()
          {
            return val > x;
          };
          return lam();
        }
        int x;
      };
      int main(){
        Str s;
        s.fun();//此时this对应的就是s的地址
      }
      
    • 初始化捕获( C++14 )
      #include<functional>//bind
      #include<iostream>
      #include<algorithm>
      using namespace std::placeholders;//bind的命名空间
      
      int main(){
        int x = 3;
        auto lam = [](int val){
          return val>3;
        }
        std::cout<<lam(100);//100>3
        ----
        //初始化捕获,给lambda内部定义一个y并赋值
        auto lam = [y=x](int val){
          return val>y;
        }
        std::cout<<lam(100);//100>3
        ----
        //初始化捕获
        std::string a = "hello";
        //把a移动给y,此后a就为空
        auto lam = [y=std::move(a)](){
          std::cout<<y<<std::endl;
        }
        lam();//hello
        std::cout<<a<<std::endl;//空
        ----
        //初始化捕获
        int x = 3; int y = 10;
        auto lam = [z = x+y](int val){
          return val > z;
        }
        ----
        //初始化捕获
        int x = 3; int y = 10;
        //左边的x为初始化给lambda内部使用的x
        //右边的x为外部的x作为右值对内部x进行赋值
        auto lam = [x=x](int val){
          return val > x;
        }
      }
      
    • *this 捕获( C++17 )
      #include<functional>//bind
      #include<iostream>
      #include<algorithm>
      using namespace std::placeholders;//bind的命名空间
      //this 捕获,可能存在风险
      struct Str{
        void fun()
        {
          int val = 3;
          //this指向当前的Str对象,这样一来就相当于对x声明过了
          auto lam = [val,this]()
          //auto lam = [val,*this]()//会把内容复制一份到里面,就是安全的
          {
            return val > x;
          };
          return lam;
        }
        int x;
      };
      
      auto wrapper(){
        Str s;//this指向的是这一个指针
        //此时的s也是wrapper的局部自动对象,调用结束会被销毁
        return s.fun();
      }
      int main(){
         //wrapper()返回的是lambda表达式
        auto lam = wrapper();
        lam();//未定义的使用,Str s在这一步就被销毁了
      }
      
  • 说明符
    • mutable / constexpr (C++17) / consteval (C++20)……
      #include<functional>//bind
      #include<iostream>
      #include<algorithm>
      using namespace std::placeholders;//bind的命名空间
      
      int main(){
        int y = 3;
        auto lam = [y](int val){//默认;lambda内部是const的,不能改变
          //++y;//不能通过编译
          return val > y;
        };
        -------
        int y = 3;
        auto lam = [y](int val) mutable //去掉const
        {
          //++y;//mutable后就可以更改
          return val > y;
        };
        -------
        int y = 3;
        //可以在编译期(根据实际情况也可在运行期)进行运算
        auto lam = [y](int val) constexpr
        {
          retun val + 1;
        };
        constexpr int val = lam(100);
        std::cout<< val<<std::endl;//101
        -------
        int y = 3;
        //consteval只能在编译期进行运算
        auto lam = [y](int val) consteval
        {
          retun val + 1;
        };
        constexpr int val = lam(100);
        std::cout<< val<<std::endl;//101
      }
      
  • 模板形参( C++20 )
    #include<functional>//bind
    #include<iostream>
    #include<algorithm>
    using namespace std::placeholders;//bind的命名空间
    
    int main(){
      int y = 3;
      auto lam = [y]<typename T>(T val) 
      {
        retun val + 1;
      };
      constexpr int val = lam(100);
      std::cout<< val<<std::endl;//101
    }
    

2.2.3.lambda 表达式的深入应用

  • 捕获时计算( C++14 )
  • 即调用函数表达式(Immediately-Invoked Function Expression,IIFE)
     #include<functional>//bind
     #include<iostream>
     #include<algorithm>
     using namespace std::placeholders;//bind的命名空间
    
     int main(){
       int x = 3; int y = 5;
       //捕获时计算( C++14 )
       auto lam = [z = x+y](int val)
       {
         return z > val;
       };
       //前面的都是先构造lambda表达式,后进行执行,分两步
       ----
       //IIFE:即调用函数表达式
       const auto lam = [z = x+y](int val)
       {
         return z > val;
       }();//立即执行,避免重新定义一个函数
    
     }
    
  • 使用 auto 避免复制( C++14 )

     #include<functional>//bind
     #include<iostream>
     #include<algorithm>
     #include<map>
    
     int main(){
       auto lam = [](auto x)//内部是函数模板的实现
       {
         return x+1;
       };
       ------
       std::map<int,int> m{{2,3}};
       auto lam = [](const std::pair<int,int>& p)//会复制
       //auto lam = [](const std::pair<const int,int>& p)//不会复制
       //auto lam = [](const auto& p)//不会复制
       {
         return p.first + p.second;
       }
       std::cout<<lam(*m.begin())<<std::endl;
    
     }
    
  • Lifting ( C++14 )

     #include<functional>//bind
     #include<iostream>
     #include<algorithm>
     #include<map>
    
     auto fun(int val)
     {
       return val+1;
     }
     auto fun(double val)
     {
       return val+1;
     }
     int main(){
       auto b = std::bind(fun, 3);//这行报错,对函数重载无法区分
       std::cout<<b()<<std::endl;
       ------
       //Lifting ( C++14 )
       auto lam = [](auto x)//auto:内部实现为函数模板
       {
         return fun(x);
       };
       lam(3);
       lam(3.5)
    
     }
    
  • 递归调用( C++14 )
     #include<functional>//bind
     #include<iostream>
     #include<algorithm>
     #include<map>
     //函数不一样,看到函数这一行就能解析是什么对象了
     int factorial(int n)
     {
       return n>1 ? n*factorial(n-1) : 1;
     }
     int main(){
       factorial(5);//典型应用
       ----
       //无法解析,只能将后面执行了才能解析auto
       auto factorial2 = [](int n)
       {//factorial2还没确定,所以这条语句不确定,判定为不合法
         return n>1 ? n*factorial2(n-1) : 1;//出错
       }
       -----
       //解决
       auto factorial2 = [](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);
       }
       -----
    
     }
    

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

  • 可以使用容器而非迭代器作为输入
    • 通过 std::ranges::dangling 避免返回无效的迭代器
      #include<functional>//bind
      #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};
        //返回ptr为迭代器
        auto ptr = std::find(x.begin(), x.end(), 3);
        std::cout<< *ptr<<std::endl;//3
        ------
        std::vector<int> x{1,2,3,4,5};
        auto ptr = std::ranges::find(x.begin(), x.end(), 3);
        std::cout<< *ptr<<std::endl;//3
        ------
        std::vector<int> x{1,2,3,4,5};
        //ranges自动解析为x.begin(), x.end()
        auto ptr = std::ranges::find(x, 3);
        std::cout<< *ptr<<std::endl;//3
        ------
        //通过 std::ranges:dangling 避免返回无效的迭代器
        //dangling悬挂(意会,就是已经被销毁的东西)
        //有问题:fun()调用完后,vector被销毁,ptr指向右值的一个位置
        auto ptr = std::ranges::find(fun(), 3);
        std::cout<< *ptr<<std::endl;//这里解引用就出错
      }
      
  • 引入映射概念,简化代码编写

      #include<functional>//bind
      #include<iostream>
      #include<algorithm>
      #include<vector>
      #include<ranges>
      #include<map>
      int main(){
        std::map<int,int> m{{2,3}};
        //查找第二个元素为3的迭代器
        auro it = std::ranges::find(m, 3
                  &(std::pair<const int, int>::second));
        //2 3
        st d::cout<<it->first<<' '<<it->second<<std::endl;
      }
    
  • 从类型上区分迭代器与哨兵
  • 引入 view ,灵活组织程序逻辑

CPP-Note © 2024 | C++ 学习笔记

This site uses Just the Docs, a documentation theme for Jekyll.