c++泛型算法相关笔记

news/2024/2/25 12:31:55/文章来源:https://blog.csdn.net/catpico/article/details/135333237

一. 泛型算法

1. 前言

泛型算法:可以支持多种类型的算法
此处主要来讨论怎么使用标准库中定义的泛型算法<algorithm>, numeric, ranges. 在引入泛型算法之前,还有一种是方法的形式,比如说std::sortstd::list::sort,前者是算法,后者是list类中定义的函数(方法)

为什么引入泛型算法,而不用方法的形式?

  • c++内建数据类型不支持方法(如int,float等,vector可以)
  • 计算逻辑存在相似性,避免重复定义

但如果有方法和泛型算法同名,功能类似时,建议使用方法,比如std::findstd::map::find,即只要类里面提供了这个方法就使用,因为一般这个类中的方法可以针对此类有更好的优化。

2. 泛型算法的分类

读:给定迭代空间,读其中的元素并进行计算。举例:std::accumulate, std::find, std::count
写:向一个迭代区间中写入元素,一定要保证写入的目标区间的大小足够(后续也会提到怎么给目标区间动态扩充)。 举例:

  • 单纯写:std::fill(直接给结果idx), std::fill_n(给的count数)
  • 读+写:std::transform(一般可以对一个vector做某种运算后存入新vector), std::copy

排序:改变输入序列中元素的顺序。举例:std::sort,std::unique(去除相邻的重复元素,使用前需要对数组进行排序,且会把重复的元素放到数组的最后面,这个用于区分的索引是unique的返回值,可以之后erase掉)

3. 迭代器的种类*(catagory)

*了解即可,和迭代器的类型不同,比如int*可以作为一种类型。一般,根据不同的迭代器种类,会有不同的优化算法

  • 输入迭代器:可读,可递增
  • 输出迭代器:可写,可递增
  • 前向迭代器:可读写,可递增
  • 双向迭代器:可读写,可递增递减
  • 随机访问迭代器:可读写,可递减一个整数

4. 特殊迭代器

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

  • 流迭代器: istream_iterator, ostream_iterator

    #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;  //可以依次打印出12345}
    }
    
    
    #include<vector>
    #include <iterator>
    #include <numeric>
    #include <sstream>int main()
    {std::vector<int> x{1,2,3,4,5};std::copy(x.rbegin(), x.rend(), std::ostream_iterator<int>(std::cout, " "));//打印结果为”5 4 3 3 1 “
    } 
    
  • 反向迭代器
    在这里插入图片描述

  • 移动迭代器:move_iterator

5.并发算法

std::execution::seq 顺序执行
std::execution::par 并发执行
std::execution::par_unseq 并发非顺序执行
std::execution::unseq

二. bind和lambda表达式

1. 可调用对象

类似于sort算法中,用来定义sort规则的那个部分

  • 函数指针:概念直观但定义位置受限
  • 类:功能强大但书写麻烦
  • bind:基于已有的逻辑灵活适配,但复杂逻辑时语法会难懂
  • lambda:小巧灵活,功能强大

2. bind

早期的bind1st和bind2nd

#include <functional>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;std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind1st(std::greater<int>(), 3)); //打印出的为1,2std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind2nd(std::greater<int>(), 3)); //打印出的为4,5,6,7,8,9,10for(auto p : y){std::cout << p << ' ';}std::cout << std::endl;
}

变为bind:
通过绑定的方式修改可调用对象的调用方式

MyPredict(int val1, int val2)
{return val1 > val2;
}bool MyAnd(bool val1, bool val2)
{return val1&& val2;
}int main()
{using namespace std::placeholders;auto x = std::bind(MyPredict2, _1, 3);  //按照上一块代码的内容打印出4-10的数字x(50);  //50是调用MyPredict2时出啊内的第1个参数,对应MyPredict函数中的val1,会判断50>3是否成立,返回trueauto y = std::bind(MyPredict2, 3, _1);  //3对应val1的数值,而_1就是调用时写的第一个参数,比如y(4),那么val2就是4,相当于判断3>4是否成立,此处返回false。即只有当数字小于3时,返回值才是trueauto z = std::bind(MyPredict2, _2, _1); std::cout << z(3,4);  //返回1,4对应val1,3对应val2auto x1 = std::bind(MyPredict2, _1, 3);auto x2 = std::bind(MyPredict2, 10, _1);auto x3  std::bind(MyAnd, x1, x2);std::cout << x3(5);  // 返回true,因为满足10>5 && 5>3

bind的使用风险

在调用std::bind(c++11引入)时,传入的参数会被赋值,这可能产生一些调用风险,可以使用std::ref或则和std::cref避免复制的行为。c++20之后,std::bind_front是std::bind的简化形式


#include <iostream>
#incldue <algorithm>
#include <vector>
#include <functional>
#include <memory>
void MyProc(int* ptr)
{}void MyProc(std::shared_ptr<int> ptr)
{}
auto fun()
{int x;return std::bind(MyProc, &x);  //风险1:返回了一个bind构造的可调用对象,但其内部包含了一个int型的指针,这个指针指向的一个局部的对象,有风险
}auto fun2()
{std::shared_ptr<int> x(new int());return std::bind(MyProc, x);  //OK, 因为堆上会分配一块内存,然后会把内存拷贝进去
}void Proc(int& x)
{++x;
}int main()
{//风险1auto ptr = fun();ptr(); //这个行为就是未定义的int x=0;auto b = std::bind(Proc, x);  //绑定b(); //实际执行std::cout << x << std::endl;//风险2int x = 0;Proc(x);  std::cout << x << std::endl;  //打印结果为1x=0;auto ptr = std::bindauto b = std::bind(Proc, x);  //绑定b(); //实际执行std::cout << x << std::endl;  //打印结果为0。 因为在调用bind的时候,x还是会复制一份再去处理,传递给Proc的是复制后的x',被修改的是x'auto b = std::bind(Proc, std::ref(x));  //std::ref会生成一个对象,这个对象也会拷贝给bind,但是拷贝出的对象内部会包含一个引用来引用x,所以还是可以修改x的数值;std::cref()表示常量引用
}

bind_front用例:默认绑定到第一个元素
在这里插入图片描述

3. lambda表达式

为了更灵活的实现可调用对象而引入,从c++11开始在持续更新中
lambda表达式等效为一个类的对象6,主要内容包括以下几个点:

  • 参数和函数体
  • 返回类型
  • 捕获:针对函数体中使用的局部自动对象进行捕获
  • 说明符:mutable, constexpr, consteval
  • 模板形参
//一般可以用auto自动推导出返回类型
auto x = [](int val)
{return (val > 3.0) && val < 15.0);
};  //不要忘记这里的分号//这里return的一个是float,一个是double,所以必须要指定返回类型
auto x = [](int val) ->float
{if (val > 3){return 3.0;}else{return 1.5f;}
};  //不要忘记这里的分号//捕获, 这样才可以把局部自动y传递到lambda表达式里面
//如果是静态变量or全局变量就不需要捕获可以直接使用
int y = 10;   
auto x = [y](int val) 
{return val >y;
};  //不要忘记这里的分号//捕获, 这样才可以把局部自动y传递到lambda表达式里面
int y = 10;   
auto x = [y](int val) mutable
{++y; //这里用了mutable,只有值捕获,y在lambda表达式内的操作不会影响到外部return val >y;
};  //不要忘记这里的分号
std::cout << y << std::endl; //输出的y的值是10.//捕获, 这样才可以把局部自动y传递到lambda表达式里面
int y = 10;   
auto x = [&y](int val) 
{++y; //这里用了引用捕获,y在lambda表达式内的操作会影响到外部return val >y;
};  //不要忘记这里的分号
std::cout << y << std::endl; //输出的y的值是11.//捕获, 这样才可以把局部自动y传递到lambda表达式里面
int y = 10;   
int z = 3;
//中括号里面是捕获列表,可以混合捕获
//如果说用到了很多局部对象,也可以不用每个都写进中括号里,可写作[=],自动进行值捕获
//[&] 自动的进行局部对象的引用捕获
//[&, z] 表示使用到的局部对象多是采用引用捕获,z采用值捕获
auto x = [&y, z](int val) 
{++y; return val >z;
};  //不要忘记这里的分号
std::cout << y << std::endl; //输出的y的值是11.

当去使用不是局部变量的值时,需要使用this进行捕获

struct Str
{auto fun(){int val = 3;//由于这里的x并不是一个局部的变量,所以要用this,指向Str的一个对象的指针,才能在lambda表达式里使用x//注意!! this是一个指针,使用过程中可能会有风险,c++17里,使用*this,//*this就会把Str内的所有内容复制到lambda内部,在调用lambda时更加安全,不会访问已经释放的内存//但是如果Str比较复杂,复制的时候就会比较耗时间auto lam = [val, this] (){return val >x;	};return lam();}int x;
};//写法一:ok
int main()
{Str s;s.fun();
}//写法二:有风险
auto wrapper()
{Str s;return s.fun();
}int main()
{如果此时lambda用this,此时wrapper返回的是一个lambda表达式//this实际是指向wrapper里Str的对象s的一个指针,wrapper调用结束后,s就会被销毁     auto lam = wrapper();                      lam(); //指向一个被悬挂的指针,这么调用的行为是未定义的
}

c++14引入了一种新捕获:初始化捕获

std::string a= "hello";
auto lam = [y = std::move(a)]()
{std::cout << y << std::endl;
};

c++17引入了一种新捕获:初始化捕获

std::string a= "hello";
auto lam = [y = std::move(a)]()
{std::cout << y << std::endl;
};

接下来来理解说明符:

直接在中括号内用y的话,等效于加了const,如果此时在lambda内改变y的数值是会导致编译报错的
在auto lam这一行后面加上mutable即可解决
在这里插入图片描述
这里使用constexpr(可在运行期or编译器调用)或者consteval(只能在编译期调用),return的值为101
不加的话默认运行期执行。加了的话编译器可以有优化
在这里插入图片描述

模板形参c++20
任何类型都可以,只要可以支持+1的操作
在这里插入图片描述

几种更深入的用法

//c++14捕获时计算,可以一定程度提高效率
int x = 3;
int y = 5;
auto lam = [z = x+y]()
{return z;
};//构造完lambda表达式后马上执行  Immediately-invoked function expression
int x = 3;
int y = 5;
auto lam = [z = x+y]()
{return z;
}();
//比如,这样就可以直接初始化val
const auto val = [z = x+y]()
{return z;
}();std::cout<< val << std::endl;//使用auto避免复制
std::map<int,int> m{{2,3}};
//希望通过这种传引用的方式避免数值,但是实际还是会用到
//改成const std::pair<const int, int>& p才可以不复制, 
//c++14时可以直接用auto,改成const auto& p,就可以避免复制
auto lam = [](const std::pair<int, int>& p)
{return p.first + p.second;
};
std::cout << lam(*m.begin()) << std::endl;//lifting 用auto实现函数模板
//如果用bind,编译器就不能知道到底要用什么类型
auto fun(int val)
{return val + 1;
}auto fun(double val)
{return val + 1;
}int main()
{auto lam = [](auto x){return fun(x);};cout << lam(3) << endl;cout << lam(3.2) << endl;
}//用lambda表达式实现递归
//报错写法 写阶乘
auto factorial = [](int n){//这一行会报错. 因为要先把lambda表达式走完,编译器才知道这是lambda表达式//此时碰到了factorial(n-1),其实不知道这个实际是什么return n>1? n*factorial(n-1) : 1;  
};cout << factorial(5) << endl;int factorial(int n)  //编译器走完这一行就知道这个是一个函数,所以此递归函数不会报错
{return n>1 ? n * factorial(n-1) : 1;
}//如果这么写不会报错。这里用了auto-->模板参数
auto factorial = [](int n)
{//这里一定要写返回的类型是int,否则内部的return都不知道要返回什么类型f_impl和impl的返回类型成了鸡生蛋问题auto f_impl = [](int n, const auto& impl) -> int {return n>1 ? n * impl(n-1, impl) : 1;  //注意这里的impl是一个函数!};// 内部的lambda表达式声明完毕return f_impl(n, f_impl);  //把f_impl当作了参数!};cout << factorial(5) <<endl;

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

可以使用容器而非迭代器

std::vector<int> x{12345}auto it = std::ranges::find(x,3);
//auto it = std::ranges::find(x.begin(), x.end(),3);  //就不用这么写了
std::cout << *it <<std::endl; //output:3//有问题的写法 dangling悬挂:指向了失效的指针
auto fun()
{return std::vector<int> x{1,2,3,4,5};  //返回的是一个局部对象,右值,之后会被销毁
}int main()
{std::vector<int> x{1,2,3,4,5};auto it = std::ranges::find(fun(), 3);  //使用ranges的时候注意不要传入右值std::cout << *it <<std::endl;  //这种解引用可能是未定义的行为
}

其他的简化代码的写法
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_925416.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序上传并显示图片

实现效果&#xff1a; 上传前显示&#xff1a; 点击后可上传&#xff0c;上传后显示&#xff1a; 源代码&#xff1a; .wxml <view class"{{company_logo_src?blank-area:}}" style"position:absolute;top:30rpx;right:30rpx;height:100rpx;width:100rp…

力扣67. 二进制求和算法

一、【写在前面】 这道题需要&#xff0c;给你两个字符串比如 a "1010", b "1011"答案是&#xff1a;"10101" 然后需要你给出计算结果&#xff0c;那么我们很容易想到两种做法 1. 调库做法&#xff1a;直接转化为整数&#xff0c;然后用内…

在CentOS上设置和管理静态HTTP网站的版本控制

在CentOS上设置和管理静态HTTP网站的版本控制是一项重要的任务&#xff0c;它可以帮助您跟踪和回滚对网站所做的更改&#xff0c;确保数据的一致性和完整性。以下是在CentOS上设置和管理静态HTTP网站的版本控制的步骤&#xff1a; 安装版本控制系统在CentOS上安装Git或其他版本…

K8S--Ingress的作用

原文网址&#xff1a;K8S--Ingress的作用-CSDN博客 简介 本文介绍K8S的Ingress的作用。 ----------------------------------------------------------------------------------------------- 分享Java真实高频面试题&#xff0c;吊打面试官&#xff1a; Java后端真实面试题…

[后端] 微服务的前世今生

微服务的前世今生 整体脉络: 单体 -> 垂直划分 -> SOA -> micro service 微服务 -> services mesh服务网格 -> future 文章目录 微服务的前世今生单一应用架构特征优点&#xff1a;缺点&#xff1a; 垂直应用架构特征优点缺点 SOA 面向服务架构特征优点缺点 微服…

STM32快速复制MX25L1606E系列Flash

去年做了一个使用RS485对PIC18F45K80系列单片机进行在线升级的程序&#xff0c;如果是小批量的出厂烧录程序和升级验证&#xff08;出厂前肯定要测试单片机是否能正常读写Flash&#xff09;是可以的&#xff0c;但是后来产品订单量很大&#xff0c;生产线的烧录及升级验证就很缓…

Android Retrofit使用详情

一、 Retrofit是什么 Retrofit是Android用来接口请求的网络框架&#xff0c;内部是基于OkHttp实现的&#xff0c;retrofit负责接口请求的封装&#xff0c;retrofit可以直接将接口数据解析为Bean类、List集合等&#xff0c;直接简化了中间繁琐的数据解析过程 二、 Retrofit的简单…

使用 Apache POI 更新/覆盖 特定的单元格

使用 Apache POI 更新特定的单元格 一. 需求二. 实现三. 效果 一. 需求 将以下表中第4行&#xff0c;第4列的单元格由“张宇”更新为“汤家凤”&#xff0c;并将更行后的结果写入新的Excel文件中&#xff1b; 二. 实现 使用Apache POI&#xff0c;可以精确定位到需要更改的单…

stm32学习笔记:USART串口通信

1、串口通信协议&#xff08;简介软硬件规则&#xff09; 全双工&#xff1a;打电话。半双工&#xff1a;对讲机。单工&#xff1a;广播 时钟&#xff1a;I2C和SPI有单独的时钟线&#xff0c;所以它们是同步的&#xff0c;接收方可以在时钟信号的指引下进行采样。串口、CAN和…

Picturesocial | 开发实践:如何在15分钟内将应用容器化

在常见的软件架构体系中&#xff0c;容器无疑是一个技术热点。有些开发者在工作中熟练使用容器技术&#xff0c;有些可能刚刚开始容器之旅。 面对容器使用经验不同的各类开发者&#xff0c;我们希望通过这个系列文章&#xff0c;由浅入深地介绍如何使用容器技术来构建&#xf…

C语言天花板——指针(经典题目)

指针我们已经学习的差不多了&#xff0c;今天我来给大家分享几个经典的题目&#xff0c;来让我们相互学习&#x1f3ce;️&#x1f3ce;️&#x1f3ce;️ int main() {int a[4] { 1, 2, 3, 4 };int* ptr1 (int*)(&a 1);int* ptr2 (int*)((int)a 1);printf("%x,%…

C++ OJ基础

C OJ基础 在学校学习C程序设计基础课程的OJ题目 缺少第二十题 这里写目录标题 C OJ基础习题练习(一)打印图形习题练习(二)数据的输入输出习题练习(三)函数重载习题练习(四)设计矩形类习题练习(五)定义Tree类习题练习(六)完善职工工资类Salary的设计习题练习(七)设计矩形类recta…

运维工具之iptables命令

运维工具之iptables命令 1.iptables防火墙介绍 ​ iptables其实并不是真正的防火墙&#xff0c;我们可以理解成一个客户端代理&#xff0c;用户通过 IPTables这个代理&#xff0c;将用户的安全设定执行到对应的"安全框架"中&#xff0c;这个"安全框架"才…

Laravel 框架中队列的使用

概述 Laravel 框架内置了强大的队列系统&#xff0c;用于处理异步任务、提高系统性能等。队列可以让任务异步执行&#xff0c;而不会阻塞当前进程&#xff0c;可以提高系统的处理能力。 Laravel 的队列系统支持多种驱动&#xff0c;如 Redis、Beanstalkd、SQS 等&#xff0c;…

Android WiFi Service启动-Android13

Android WiFi Service启动 - Android13 1、SystemServer中入口2、WifiService启动2.1 关键类概要2.2 启动时序图 Android WiFi基础概览 AOSP > 文档 > 心主题 > WiFi概览 1、SystemServer中入口 编译生成对应的jar包&#xff1a;"/apex/com.android.wifi/javalib…

桌面显示器type-c接口方案6020

TYPE-C接口桌面显示器&#xff0c;与传统的显示器不同的是 新一类的显示器不仅仅支持视频传输&#xff0c;还可以利用显示器的DC电源转成PD协议充电给设备端&#xff08;笔记本&#xff0c;任天堂等HOST设备&#xff09;充电。 这种新型的TYPE-C接口桌面显示器&#xff0c;不仅…

基于机器学习的高考志愿高校及专业分析系统

本项目在“基于 Python 的高考志愿高校及专业分析系统”基础上补充添加了机器学习算法对高考总问进行预测&#xff1b; 项目采用了网络爬虫技术&#xff0c;从指定的高考信息网站上抓取了各大高校的历年录取分数线数据。 通过精细的数据清洗过程&#xff0c;这些数据被存储于…

物联网智能控制器—福建蜂窝物联网科技有限公司

什么是物联网智能控制器&#xff1f; 物联网智能控制器是蜂窝物联自主研发的一种远程测控设备(RTU)&#xff0c;负责对现场信号、工业设备的监测和控制。本质上是一个模块化封装的微型计算机设备&#xff0c;将相应的一些功能进行了封装&#xff0c;无需进行电路设计和硬件程序…

为什么使用双token实现无感刷新用户认证?

单token机制 认证机制&#xff1a;对与单token的认证机制在我们项目中仅使用一个Access Token的访问令牌进行用户身份认证和授权的方案处理。 不足之处&#xff1a; 安全性较低(因为只有一个token在客户端和服务器端之间进行传递&#xff0c;一目Acess Token被截获或者被泄露…

在程序中链接静态库 和 动态库

9. 链接库 在编写程序的过程中&#xff0c;可能会用到一些系统提供的动态库或者自己制作出的动态库 或者静态库文件&#xff0c;cmake中也为我们提供了相关的加载动态库的命令hehedalinux:~/Linux/loveDBTeacher-v3$ tree . ├── CMakeLists.txt ├── include │ └── …