Unity中【UniTask异步流程】如何进行【步骤分段】、【步骤撤销】、【步骤跳转】、【取消异步任务】

一、UniTask和Task

UniTask是Unity中的Task实现,Task是C#中实现异步操作的一个模块(类)。UniTask与Task有着同样的使用思路(使用习惯,常用API等),可以说UniTask是借鉴Task而开发出来的。

二、需求的来源

以前有一个实验,操作就是点击物体,执行动画,点击物体,执行动画…如此子子孙孙无穷循环,直到地球爆炸(实验结束)。

2.1 原来的脚本

于是很容易就用UniTask的await把所有操作连成一片,写在一个脚本里,甚至一整个实验就一个脚本。

比如下面:

1)、面板参数定义

面板用到的参数全部释放在Inspector上面,代码的话带上注释和空格将近1800行
在这里插入图片描述

2)、异步流程的组织

操作流程的话,写在一个异步方法里,浩浩荡荡写了上千行…写起来倒是比较滑溜了,但是调试和复用的话,就有点…
在这里插入图片描述
在这里插入图片描述

2.2 要改造成什么样

如果用户说他要【上一步】、【下一步】和【跳步】
思路:把一串流程按照操作逻辑分块,不同的块编一个号,放到一个执行列表里面。

1)、从前的脚本是这样:

/// <summary>
/// 主流程:所有流程连成一片,无法分步执行
/// </summary>
/// <returns></returns>
public async UniTask Flow()
{//第一步await UniTask.Delay(1000);//...//第二步await UniTask.Delay(1000);//...//第三步await UniTask.Delay(1000);//...//...//第N步await UniTask.Delay(1000);//...
}

2) 、分块思路

  • (1)定义一个列表StepInfosList,用来装载要执行的步骤。
    /// <summary>/// 步骤信息表:系列化到面板,用于调试和观察,后期执行/// </summary>[Header("步骤信息表")]public List<StepInfo> StepInfosList = new List<StepInfo>();

每一个节点StepInfo的结构如下:
在这里插入图片描述

3) 、分块后的脚本

/// <summary>
/// 步骤信息Class
/// </summary>
[Serializable]
public class StepInfo
{/// <summary>/// 当前序号/// </summary>[Header("当前步骤的序号")]public int index;/// <summary>/// 步骤的名字/// </summary>[Header("步骤的名字")]public string name;/// <summary>/// 正常流程/// </summary>public Func<UniTask> Flow;/// <summary>/// 恢复到【初始状态】的流程/// </summary>public Action Init;/// <summary>/// 跳到【结束状态】的流程/// </summary>public Action Final;
}
  • (2)拆分步骤

把脚本按照逻辑分成不同的块(步骤),装入到执行步骤列表里

/// <summary>
/// 添加步骤:新改的写法——各个步骤单独分开,逐个添加到执行列表里
/// </summary>
/// <param name="ctk"></param>
/// <returns></returns>
public async UniTask AddSteps(CancellationToken ctk)
{//第一步var step = StepInfosList.AddFlow("第一步");step.Flow = async () =>{Debug.Log("Flow() - 第一步");await UniTask.Delay(1000, cancellationToken: ctk);};   //第二步step = StepInfosList.AddFlow("第二步");step.Flow = async () =>{Debug.Log("Flow() - 第二步");await UniTask.Delay(1000, cancellationToken: ctk);}; //第三步step = StepInfosList.AddFlow("第三步");step.Flow = async () =>{Debug.Log("Flow() - 第三步");await UniTask.Delay(1000, cancellationToken: ctk);};//第N步step = StepInfosList.AddFlow("第N步");step.Flow = async () =>{Debug.Log("Flow() - 第N步");await UniTask.Delay(1000, cancellationToken: ctk);};
}

三、跳步会有哪些操作

3.1 跳步的分类

在这里插入图片描述

3.2 跳步的实现

  • 【1】获取目标步骤信息
  • 【2】处理中间步骤的状态
  • 【3】执行目标步骤的流程
/// <summary>
/// 给定步骤名字,执行指定的步骤【所谓任意跳步】
/// </summary>
/// <param name="taskName"></param>
async UniTask RunTask(string taskName)
{if (cts.IsCancellationRequested){Debug.Log("所有的任务已经被取消了!!");return;}//【1】获取目标步骤信息var targetStep = StepInfosList.First(x => x.name.Equals(taskName.Trim()));//【2】处理中间步骤的状态Debug.Log($"#################### 要执行的目标步骤为:targetStep = {targetStep.index}  {targetStep.name}  currentIndex = {currentIndex} ");if (currentIndex == targetStep.index)  //本步骤重新执行:恢复到本步骤初始状态{Debug.Log($"回到【{targetStep.name}】初始状态");if (targetStep.Init == null){Debug.LogWarning($"步骤【{targetStep.index} {targetStep.name}】的Init函数为空,请补全!");}else{targetStep.Init();}}if (currentIndex < targetStep.index)   //往后跳步:中间步骤的状态快速补足{StepInfosList.Where(x => (x.index >= currentIndex) && (x.index < targetStep.index)).ToList().ForEach(s =>{Debug.Log($"回到【{s.name}】结束状态");if (s.Final == null){Debug.LogWarning($"步骤【{s.index} {s.name}】的Final函数为空,请补全!");}else{s.Final();}});}if (currentIndex > targetStep.index)   //往前跳步:中间步骤的状态快速撤销{StepInfosList.Where(x => (x.index >= targetStep.index) && (x.index <= currentIndex)).OrderByDescending(x=>x.index).ToList()        //注意倒序排列——由后往前执行.ForEach(s =>{Debug.Log($"回到【{s.name}】初始状态");if (s.Init == null){Debug.LogWarning($"步骤【{s.index} {s.name}】的Init函数为空,请补全!");}else{s.Init();}});}//【3】执行目标步骤的流程await targetStep.Flow();currentIndex = targetStep.index;Debug.Log($"执行步骤:{targetStep.index}  {targetStep.name} 执行完毕,当前步骤currentIndex = {currentIndex}!");
}

3.3 执行所有步骤

用异步等待来逐个执行所有的步骤,如下所示:

/// <summary>
/// 执行所有的步骤
/// </summary>
/// <returns></returns>
private async UniTask RunAllTasks()
{foreach (var step in StepInfosList){Debug.Log($"当前执行的步骤:{step.index} {step.name}");currentIndex = step.index;await step.Flow();}
}

3.4 如何取消所有步骤

  • 1)定义一个CancellationTokenSource(暂且称它为-异步任务取消标记)
    异步操作必须有取消,如果你不取消,当切换场景的时候,异步流程还在运行,则会出现资源的空引用。

举个不恰当的例子:你在家里玩电脑,然后让你的5岁的娃娃去楼下接妈妈回家。结果你媳妇从地下室坐电梯上来了,结局就是你的娃娃一直没等到他妈妈,结果呢,他呆坐在门口一直等,一直等,天黑也不回家。

问题在哪里:你指派给你小孩接妈妈这个异步操作,没有附加异步取消的令牌。下次你应该对他说:天黑还没等到妈妈的话,你就直接回家吃饭。或者我打电话给你,你就回家。

/// <summary>
/// 异步任务取消的标记
/// </summary>
private CancellationTokenSource cts;
  • 2)取消异步任务
    单凡用到上面cts的await,只要发现cts.IsCancellationRequested变成true,就会停止执行
/// <summary>
/// 取消所有的任务
/// </summary>
void CancelAllTasks()
{cts.Cancel();
}
  • 3) CancellationTokenSource是啥,如何服用?
    CancellationTokenSource就是一个遥控器,遥控器一按关机,那么凡是被该遥控器遥控的电视都关机。那么如何把遥控器绑定给一个电视机呢,也就是如何把一个CancellationTokenSource绑定给一坨异步操作。
    秘诀就是凡是await 操作的地方,你都绑一个CancellationTokenSource的token给它。
var cts = new CancellationTokenSource();
cancelButton.onClick.AddListener(() =>
{cts.Cancel();
});
await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token);await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
  • 4)把所有的步骤绑定在一个CancellationTokenSource上
/// <summary>
/// 创建任务,用一个新的【CancellationTokenSource】控制这些异步任务
/// </summary>
/// <returns>cts</returns>
CancellationTokenSource CreatTasks()
{var cts = new CancellationTokenSource();StepInfosList.Clear();AddSteps(cts.Token).Forget();return cts;
}

四、测试步骤

4.1 测试脚本

//加载步骤流程到执行列表中
Debug.Log($"【1】************加载步骤流程到执行列表中:{Time.realtimeSinceStartup}");
cts = CreatTasks();//执行一遍所有的任务
Debug.Log($"【2】************执行一遍所有的任务:{Time.realtimeSinceStartup}");
await RunAllTasks();//等待3秒钟,跳步到第三步执行
Debug.Log($"【3】************等待3秒钟,跳步到第三步执行:{Time.realtimeSinceStartup}");
await UniTask.Delay(3000, cancellationToken: cts.Token);
await RunTask("第三步");//取消所有任务
Debug.Log($"【4】************取消所有任务:{Time.realtimeSinceStartup}");
CancelAllTasks();//等待3秒钟,跳步到第1步执行
Debug.Log($"【5】************等待3秒钟,跳步到第1步执行:{Time.realtimeSinceStartup}");
await UniTask.Delay(3000, cancellationToken: cts.Token);
await RunTask("第一步");

4.2 测试结果

在这里插入图片描述

五、附录:脚本源码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;/// <summary>
/// 步骤信息Class
/// </summary>
[Serializable]
public class StepInfo
{/// <summary>/// 当前序号/// </summary>[Header("当前步骤的序号")]public int index;/// <summary>/// 步骤的名字/// </summary>[Header("步骤的名字")]public string name;/// <summary>/// 正常流程/// </summary>public Func<UniTask> Flow;/// <summary>/// 恢复到【初始状态】的流程/// </summary>public Action Init;/// <summary>/// 跳到【结束状态】的流程/// </summary>public Action Final;
}/// <summary>
/// 扩展方法
/// </summary>
public static class ExtensionMethods
{/// <summary>/// 在步骤列表MySteps中生成一个只包含步骤名的空步骤/// </summary>/// <param name="MySteps">步骤列表</param>/// <param name="stepName">步骤名字</param>/// <returns></returns>public static StepInfo AddFlow(this List<StepInfo> MySteps, string stepName){int idx = MySteps.Count == 0 ?0:MySteps.Max(x => x.index) + 1;var item = new StepInfo();item.index = idx;item.name = stepName;item.Flow = null;item.Final = null;item.Init = null;MySteps.Add(item);return item;}
}/// <summary>
/// 步骤分步,任意跳转:一段操作流程,包含多个步骤,可以任意跳转到某个步骤执行。
/// 点击【上一步】时,需要对当前步骤所操作的内容进行撤销或者状态还原。
/// 点击【下一步】时,需要把跳过的步骤中的状态补足。
/// </summary>
public class FlowsDemo : MonoBehaviour
{/// <summary>/// 步骤信息表:系列化到面板,用于调试和观察,后期执行/// </summary>[Header("步骤信息表")]public List<StepInfo> StepInfosList = new List<StepInfo>();/// <summary>/// 当前执行的步骤/// </summary>private int currentIndex;/// <summary>/// 异步任务取消的标记/// </summary>private CancellationTokenSource cts;/// <summary>/// 取消所有的任务/// </summary>void CancelAllTasks(){cts.Cancel();}/// <summary>/// 创建任务,用一个新的【CancellationTokenSource】控制这些异步任务/// </summary>/// <returns>cts</returns>CancellationTokenSource CreatTasks(){var cts = new CancellationTokenSource();StepInfosList.Clear();AddSteps(cts.Token).Forget();return cts;}/// <summary>/// 执行所有的步骤/// </summary>/// <returns></returns>private async UniTask RunAllTasks(){foreach (var step in StepInfosList){Debug.Log($"当前执行的步骤:{step.index} {step.name}");currentIndex = step.index;await step.Flow();}}/// <summary>/// 主流程:所有流程连成一片,无法分步执行/// </summary>/// <returns></returns>public async UniTask Flow(){//第一步await UniTask.Delay(1000);//...//第二步await UniTask.Delay(1000);//...//第三步await UniTask.Delay(1000);//...//...//第N步await UniTask.Delay(1000);//...}/// <summary>/// 添加步骤:新改的写法——各个步骤单独分开,逐个添加到执行列表里/// </summary>/// <param name="ctk"></param>/// <returns></returns>public async UniTask AddSteps(CancellationToken ctk){//第一步var step = StepInfosList.AddFlow("第一步");step.Flow = async () =>{Debug.Log("Flow() - 第一步");await UniTask.Delay(1000, cancellationToken: ctk);};step.Init = () => { Debug.Log("Init() - 回到第一步的初始状态");};step.Final = () => { Debug.Log("Final() - 跳到第一步的结束状态"); };//第二步step = StepInfosList.AddFlow("第二步");step.Flow = async () =>{Debug.Log("Flow() - 第二步");await UniTask.Delay(1000, cancellationToken: ctk);};step.Init = () => { Debug.Log("Init() - 回到第二步的初始状态"); };step.Final = () => { Debug.Log("Final() - 跳到第二步的结束状态"); };//第三步step = StepInfosList.AddFlow("第三步");step.Flow = async () =>{Debug.Log("Flow() - 第三步");await UniTask.Delay(1000, cancellationToken: ctk);};//第N步step = StepInfosList.AddFlow("第N步");step.Flow = async () =>{Debug.Log("Flow() - 第N步");await UniTask.Delay(1000, cancellationToken: ctk);};}/// <summary>/// 给定步骤名字,执行指定的步骤【所谓任意跳步】/// </summary>/// <param name="taskName"></param>async UniTask RunTask(string taskName){if (cts.IsCancellationRequested){Debug.Log("所有的任务已经被取消了!!");return;}//【1】获取目标步骤信息var targetStep = StepInfosList.First(x => x.name.Equals(taskName.Trim()));//【2】处理中间步骤的状态Debug.Log($"#################### 要执行的目标步骤为:targetStep = {targetStep.index}  {targetStep.name}  currentIndex = {currentIndex} ");if (currentIndex == targetStep.index)  //本步骤重新执行:恢复到本步骤初始状态{Debug.Log($"回到【{targetStep.name}】初始状态");if (targetStep.Init == null){Debug.LogWarning($"步骤【{targetStep.index} {targetStep.name}】的Init函数为空,请补全!");}else{targetStep.Init();}}if (currentIndex < targetStep.index)   //往后跳步:中间步骤的状态快速补足{StepInfosList.Where(x => (x.index >= currentIndex) && (x.index < targetStep.index)).ToList().ForEach(s =>{Debug.Log($"回到【{s.name}】结束状态");if (s.Final == null){Debug.LogWarning($"步骤【{s.index} {s.name}】的Final函数为空,请补全!");}else{s.Final();}});}if (currentIndex > targetStep.index)   //往前跳步:中间步骤的状态快速撤销{StepInfosList.Where(x => (x.index >= targetStep.index) && (x.index <= currentIndex)).OrderByDescending(x=>x.index).ToList()        //注意倒序排列——由后往前执行.ForEach(s =>{Debug.Log($"回到【{s.name}】初始状态");if (s.Init == null){Debug.LogWarning($"步骤【{s.index} {s.name}】的Init函数为空,请补全!");}else{s.Init();}});}//【3】执行目标步骤的流程await targetStep.Flow();currentIndex = targetStep.index;Debug.Log($"执行步骤:{targetStep.index}  {targetStep.name} 执行完毕,当前步骤currentIndex = {currentIndex}!");}/// <summary>/// 要测试的【步骤名字】/// </summary>[Header("要执行的【步骤名字】")]public string flowName;#if UNITY_EDITOR[ContextMenu("跳到指定的步骤")]
#endifvoid TestFlow(){RunTask(flowName).Forget();}#if UNITY_EDITOR[ContextMenu("取消所有任务")]
#endifvoid TestFlow1(){CancelAllTasks();}#if UNITY_EDITOR[ContextMenu("Demo测试")]
#endifvoid TestFlow3(){Func<UniTask> TestDemo = async () =>{//加载步骤流程到执行列表中Debug.Log($"【1】************加载步骤流程到执行列表中:{Time.realtimeSinceStartup}");cts = CreatTasks();//执行一遍所有的任务Debug.Log($"【2】************执行一遍所有的任务:{Time.realtimeSinceStartup}");await RunAllTasks();//等待3秒钟,跳步到第三步执行Debug.Log($"【3】************等待3秒钟,跳步到第三步执行:{Time.realtimeSinceStartup}");await UniTask.Delay(3000, cancellationToken: cts.Token);await RunTask("第三步");//取消所有任务Debug.Log($"【4】************取消所有任务:{Time.realtimeSinceStartup}");CancelAllTasks();//等待3秒钟,跳步到第1步执行Debug.Log($"【5】************等待3秒钟,跳步到第1步执行:{Time.realtimeSinceStartup}");await UniTask.Delay(3000, cancellationToken: cts.Token);await RunTask("第一步");};Debug.Log($"开始测试:{Time.realtimeSinceStartup}");TestDemo();}
}

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

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

相关文章

Nacos 身份认证绕过漏洞(已修复)

Nacos存在权限绕过漏洞&#xff0c;攻击者利用该漏洞可以未授权访问用户列表&#xff08;我的Nacos版本为1.2.1&#xff09; 漏洞复现&#xff1a;http://127.0.0.1:8849/nacos/v1/auth/users?pageNo1&pageSize9 利用漏洞复现问题http://127.0.0.1:8849/nacos/v1/auth/us…

No205.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

JAVA基础9:Debug

1.Debug概述 Debug:是供程序员使用的程序调试工具&#xff0c;它可以用于查看程序的执行流程&#xff0c;也可以用于追踪程序执行过程来调试程序。 2.Debug操作流程 Debug调试&#xff0c;又被称为断点调试&#xff0c;断点其实是一个标记&#xff0c;告诉我们从哪里开始查看…

【电源专题】低功耗设备如何解决POE协议要求的PD最小功耗?

要让PD正常工作起来除了需要与PSE握手协商外,还要求PD有一个最小功耗输出。 其原因是如果PD没有在一定时间内给出一个最小功耗,那么PSE将会认为PD设备断开而自动关闭,将功率分配给其他网口。对于不同的类别PD,其要求也不一样。如下所示为Type 1/2/2/4最小电流的要求:如类…

Android—幸运抽奖火箭发射倒计时(第六次作业)

Android—幸运抽奖&&点火发射&#xff08;第六次作业&#xff09; 创建项目 准备工作 修改按钮的颜色&#xff0c;如果不修改这行代码&#xff0c;那么后期给按钮添加background属性的时候&#xff0c;按钮并不会发生变化。 设置按钮的样式文件btn_press_blue.xml&am…

计算机网络课后作业2023秋

计算机网络第三版吴功宜版 课后作业 第一章作业三、计算与问答3.73.8 第二章作业三、计算与问答3.23.53.6 第三章作业三、计算与问答3.13.53.73.8 第四章作业三、计算与问答3.13.2 第五章作业三、计算与问答3.13.33.4VLAN交换机模式模式切换重置交换机VLA&#xff2e;配置命令其…

基础大模型的结构特性与发展

摘要&#xff1a; 基础大模型的结构特性是什么给予的&#xff1f;在建模部分&#xff0c;我们将探索基础模型背后的底层架构&#xff0c;并确定5个关键属性。 首先&#xff0c;我们从讨论计算模型的表现力开始-捕获和吸收真实世界的信息&#xff0c;以及可扩展性-熟练地处理大量…

NFS文件系统共享服务器实战

架设一台NFS服务器&#xff0c;并按照以下要求配置 准备 两台Linux虚拟机一台作为服务端server&#xff0c;一台作为客户端client server IPV4&#xff1a;192.168.110.136/24 client IPV4&#xff1a;192.168.110.134/24 两台服务器都需要关闭防火墙和seLinux 服…

Clickhouse学习笔记(13)—— Materialize MySQL引擎

该引擎用于监听 binlog 事件&#xff0c;类似于canal、Maxwell等组件 ClickHouse 20.8.2.3 版本新增加了 MaterializeMySQL 的 database 引擎&#xff0c;该 database 能映射到 MySQL中的某个database &#xff0c;并自动在ClickHouse中创建对应ReplacingMergeTree。 ClickHous…

k8s 1.28.3 使用containerd

文章目录 环境说明最终结果环境配置时钟同步 主机名称配置主机名解析关闭swap安装ipvs 安装containerd安装containerd生成配置修改配置开启containerd服务 安装runc安装k8s安装kubelet kubeadm kubectl获取kubernetes 1.28组件容器镜像 拉取镜像初始化集群方法一&#xff08;不…

postman上传照片,视频,音频等上传文件操作测试方法

Postman上传照片&#xff0c;视频&#xff0c;音频等上传文件操作测试方法 新建一个request&#xff0c;更改请求方式&#xff0c;点击Body 勾选form-data ,key后面下拉框选择File 上一步勾选后Value即出现选择本地文件按钮&#xff0c;填写Key&#xff0c;选择文件即可 此时…

如何显示标注的纯黑mask图

文章目录 前言一、二分类mask显示二、多分类mask显示 前言 通常情况下&#xff0c;使用标注软件标注的标签图看起来都是纯黑的&#xff0c;因为mask图为单通道的灰度图&#xff0c;而灰度图一般要像素值大于128后&#xff0c;才会逐渐显白&#xff0c;255为白色。而标注的时候…

Docker的安装配置与使用

1、docker安装与启动 首先你要保证虚拟机所在的盘要有至少20G的空间&#xff0c;因为docker开容器很吃空间的&#xff0c;其次是已经安装了yum依赖 yum install -y epel-release yum install docker-io # 安装docker配置文件 /etc/sysconfig/docker chkconfig docker on # 加…

【unity插件】UGUI的粒子效果(UI粒子)—— Particle Effect For UGUI (UI Particle)

文章目录 前言插件地址描述特征Demo 演示如何玩演示对于 Unity 2019.1 或更高版本对于 Unity 2018.4 或更早版本 用法基本上是用法使用您现有的 ParticleSystem 预制件带 Mask 或 RectMask2D 组件脚本用法UIParticleAttractor 组件开发说明常见问题解答&#xff1a;为什么我的粒…

c语言:解决谁是凶手的问题。

题目&#xff1a; 思路&#xff0b;代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS //假设全部人说的话都为真&#xff0c;那结果就为4&#xff0c;现在已知三真一假&#xff0c;且说假话的人为凶手 // 现在将全部情况相加&#xff0c;满足三真一假的情况即为凶手。 #incl…

【JVM】Java内存溢出分析(堆溢出、栈溢出、方法区溢出、直接内存溢出)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

十年软件测试老程序告诉你性能测试的左移右移到底能干嘛

常规的性能测试一般都是在测试阶段集成测试时候才开始介入&#xff0c;很容易测试时间不够&#xff0c;可不可以借鉴测试左移右移的思路&#xff0c;更早的介入和发现性能风险&#xff0c;然后在测试阶段更专注于分析优化&#xff1f; 借着这个问题&#xff0c;结合自己的实践…

C语言从文件 D://test.txt 读取字符串,将字符串中所有的大写字符改为小写字母并写回到源文件中

完整代码&#xff1a; /*从文件 D://test.txt 读取字符串&#xff0c;将字符串中所有的大写字母改为小写字母并写回 到源文件中*/ #include<stdio.h>//将字符串中所有的大写字母改为小写字母 void func(char *buff){while (*buff!\0){if (*buff>A&&*buff<…

信息检索与数据挖掘 | 【实验】检索评价指标MAP、MRR、NDCG

文章目录 &#x1f4da;实验内容&#x1f4da;知识梳理&#x1f4da;实验步骤&#x1f407;前情提要&#x1f407;MAP评价指标函数&#x1f407;MRR 评价指标函数&#x1f407;NDCG评价指标函数&#x1f407;调试结果 &#x1f4da;实验内容 实现以下指标评价&#xff0c;并对…

打印字符(C++)

系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…