c++泛型算法相关笔记

news2024/12/24 13:10:07

一. 泛型算法

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,2
	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;
}


变为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是否成立,返回true
	auto y = std::bind(MyPredict2, 3, _1);  //3对应val1的数值,而_1就是调用时写的第一个参数,比如y(4),那么val2就是4,相当于判断3>4是否成立,此处返回false。即只有当数字小于3时,返回值才是true

	auto z = std::bind(MyPredict2, _2, _1); 
	std::cout << z(3,4);  //返回1,4对应val1,3对应val2

	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);  // 返回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()
{
	//风险1
	auto ptr = fun();
	ptr(); //这个行为就是未定义的

	int x=0;
	auto b = std::bind(Proc, x);  //绑定
	b(); //实际执行
	std::cout << x << std::endl;


	//风险2
	int x = 0;
	Proc(x);  
	std::cout << x << std::endl;  //打印结果为1
	
	x=0;
	auto ptr = std::bind
	auto 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.coloradmin.cn/o/1390415.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Overmind平台推出Sui任务,帮助开发者学习Move并构建强大的应用程序

Overmind与Sui基金会合作&#xff0c;推出了其首个任务系列&#xff0c;旨在帮助开发者学习Move并开始在Sui上构建。这些任务通过提供赢取奖励的机会来将学习体验变成游戏&#xff0c;激励开发者构建高质量的代码并向Sui社区展示他们的技能。 去年推出的Overmind平台正在扩展到…

【C++】“Hello World!“

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C ⚙️操作环境:Visual Studio 2022 ​ 2024.1.14 纪念一下自己编写的第一个C程序 #include<iostream>int main() {/*我的第一个C程序*/std::cout << "Hello world!:>" <<std::endl;ret…

基于python舆情分析可视化系统+情感分析+爬虫+机器学习(源码)✅

大数据毕业设计&#xff1a;Python招聘数据采集分析可视化系统✅ 毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&…

使用AI自动生成PPT提高制作效率

使用AI自动生成PPT提高制作效率 在制作PPT方面&#xff0c;很多制作者都会轻易跳进一个怪圈&#xff1a;“我要制作一个关于关爱老人的PPT&#xff0c;该怎么做呢&#xff0c;有模板没有?”这个会涉及很多逻辑需要经过不断的思考&#xff0c;制作PPT要通过很多素材、使用技巧、…

翼龙-2H无人机

一、概述 翼龙-2&#xff0c;是成都飞机工业集团研制的无人驾驶飞行器&#xff0c;是空中侦察、精确打击和应急通讯的平台。成都飞机工业集团于2015年9月的北京国际航空航天展览会上介绍了翼龙-2的概念。在2016年珠海航展期间&#xff0c;翼龙-2的原型机首次向公众展示。 因为…

【现代密码学】笔记4--消息认证码与抗碰撞哈希函数《introduction to modern cryphtography》

【现代密码学】笔记4--消息认证码与抗碰撞哈希函数《introduction to modern cryphtography》 写在最前面4 消息认证码与抗碰撞哈希函数MAC概念回顾&#xff08;是的&#xff0c;我忘记这些缩写是什么了。。&#xff09;MAC的定义适应性CMA&#xff08;Chosen Message Attack&a…

GPT应用开发:运行你的第一个聊天程序

本系列文章介绍基于OpenAI GPT API开发应用的方法&#xff0c;适合从零开始&#xff0c;也适合查缺补漏。 本文首先介绍基于聊天API编程的方法。 环境搭建 很多机器学习框架和类库都是使用Python编写的&#xff0c;OpenAI提供的很多例子也是Python编写的&#xff0c;所以为了…

openssl3.2 - 官方demo学习 - cms - cms_ver.c

文章目录 openssl3.2 - 官方demo学习 - cms - cms_ver.c概述运行结果笔记END openssl3.2 - 官方demo学习 - cms - cms_ver.c 概述 CMS验签, 将单独签名和联合签名出来的签名文件都试试. 验签成功后, 将签名数据明文写入了文件供查看. 也就是说, 只有验签成功后, 才能看到签名…

如何区分GPT-3.5模型与GPT-4模型?

GPT 3.5 和 GPT-4 有什么区别&#xff1f; GPT-3.5 在经过大量数据训练后&#xff0c;成功地发展到可以考虑 1750 亿个参数以响应提示。这使其具备令人印象深刻的语言技能&#xff0c;以非常人性化的方式回应各种查询。然而&#xff0c;GPT-4 在更为庞大的训练数据基础上进行了…

服务器和电脑有啥区别?

服务器可以说是“高配的电脑”&#xff0c;两者都有CPU、硬盘、电源等基础硬件组成&#xff0c;但服务器和电脑也是有一定区别的&#xff0c;让小编带大家了解一下吧&#xff01; #秋天生活图鉴# 1、稳定性需求不同&#xff1a;服务器是全年无休&#xff0c;需要高稳定性&…

docker下载时报错 /usr/local/bin/docker-compose: 1: cannot open html: No such file

docker 下载时报错 /usr/local/bin/docker-compose: 1: cannot open html: No such file /usr/local/bin/docker-compose: 2: Syntax error: redirection unexpected&#xff0c; 在网上查找了一些解决方法都不对&#xff0c;最后&#xff0c;通过删除/usr/local/bin/docker-co…

Embedding:数据的奇妙之变

在深度学习的领域,Embedding是连接符号与连续的一座桥梁。它通过将高维离散数据映射到低维连续向量空间,为大模型提供了更好的处理能力。 在这一部分,我们将深入研究Embedding的基本概念、作用以及在深度学习中的广泛应用。 一、向量Embedding与ChatGPT大模型 ChatGPT 大…

Ventoy:打造你的万能启动 U 盘 | 开源日报 No.146

ventoy/Ventoy Stars: 54.3k License: GPL-3.0 Ventoy 是一个开源工具&#xff0c;用于创建支持 ISO/WIM/IMG/VHD(x)/EFI 文件的可启动 USB 驱动器。其主要功能包括将镜像文件复制到 USB 驱动器并进行引导、一次性复制多个镜像文件并提供引导菜单选择以及在本地磁盘中浏览和引…

C++力扣题目104--二叉树的最大深度

给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&#xff0c; 返回它的最大深度 3 。 思路 看完本篇可以一起做了如下…

vue前端开发自学,使用yarn脚手架创建vue项目

vue前端开发自学,使用yarn脚手架创建vue项目&#xff01;下面展示一下&#xff0c;如何在本机操作&#xff0c;使用yarn这款脚手架&#xff0c;创建一个vue项目。 第一步&#xff0c;你需要先创建好&#xff0c;即将存档项目的文件夹。我的路径是在&#xff1a;"D:\yarn\…

【Linux驱动】设备树中指定中断 | 驱动中获得中断 | 按键中断实验

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;在设备树中指定中断&#x1f3c0;代码中获得中断&#x1f3c0;按键中断⚽驱动…

spring boot mybatis plus mapper如何自动注册到spring bean容器

##Import(AutoConfiguredMapperScannerRegistrar.class) ##注册MapperScannerConfigurer ##MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法扫描注册mapper ##找到mapper候选者 ##过滤mapper 类 候选者 ##BeanDefinitionHolder注册到spring 容器

JVM基础(4)——JVM存活判定算法

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

ArcGIS Pro中怎么加载在线地图

当我们在制图的时候&#xff0c;有的时候需要加载在线地图&#xff0c;在ArcGIS Pro中加载在线地图的方式有很多&#xff0c;这里为大家介绍一下加载的方法&#xff0c;希望能对你有所帮助。 加载底图 在菜单栏上选择地图&#xff0c;点击底图&#xff0c;可以看到所有可加载…

记录下载安装rabbitmq(Linux) 并整合springboot--详细版(全)

下载rabbitmq&#xff08;Linux&#xff09;&#xff1a; erlang压缩包&#xff1a; https://share.weiyun.com/TGhfV8eZ rabbitMq-server压缩包&#xff1a; https://share.weiyun.com/ZXbUwWHD &#xff08;因为RabbitMQ采用 Erlang 实现的工业级的消息队列(MQ)服务器&#…