当前位置: 首页 > news >正文

010、切片

        除了引用,Rust还有另外一种不持有所有权的数据类型:切片(slice)。切片允许我们引用集合中某一段连续的元素序列,而不是整个集合

        考虑这样一个小问题:编写一个搜索函数,它接收字符串作为参数,并将字符串中的首个单词作为结果返回。

        如果字符串中不存在空格,那么就意味着整个字符串是一个单词,直接返回整个字符串作为结果即可。让我们来看一下这个函数的签名应该如何设计:

fn first_word(s: &String) -> ?

        由于我们不需要获得传入值的所有权,所以这个函数 first_word 采用了 &String 作为参数。但它应该返回些什么呢?

        我们还没有一个获取部分字符串的方法。当然,你可以将首个单词结尾处的索引返回给调用者,如下代码所示: 

 fn first_word(s: &String) -> usize { ❶ let bytes = s.as_bytes(); for (i, &item)❷ in bytes.iter()❸.enumerate() { ❹  if item == b' ' { return i; } } ❺ s.len() 
} 

        这段代码首先使用 as_bytes 方法❶将 String 转换为字节数组,因为我们的算法需要依次检查 String 中的字节是否为空格。

        接着,我们通过 iter 方法❸创建了一个可以遍历字节数组的迭代器。我们会在后面文章中详细讨论这里新出现的迭代器。目前,你只需要知道 iter 方法会依次返回集合中的每一个元素即可。

        随后的 enumerate 则将 iter 的每个输出作为元素逐一封装在对应的元组中返回。元组的第一个元素是索引,第二个元素是指向集合中字节的引用。

        使用 enumerate 可以较为方便地获得迭代索引。既然 enumerate 方法返回的是一个元组,那么我们就可以使用模式匹配来解构它,就像Rust中其他使用元组的地方一样。

        在 for 循环的遍历语句中,我们指定了一个解构模式,其中 i 是元组中的索引部分,而 &item ❷则是元组中指向集合元素的引用。由于我们从 .iter().enumerate() 中获取的是产生引用元素的迭代器,所以我们在模式中使用了 &。 

        现在,我们初步实现了期望的功能,它能够成功地搜索并返回字符串中第一个单词结尾处的位置索引。但这里依然存在一个设计上的缺陷。

        我们将一个 usize 值作为索引独立地返回给调用者,但这个值在脱离了传入的 &String 的上下文之后便毫无意义。换句话说,由于这个值独立于String而存在,所以在函数返回值后,我们就再也无法保证它的有效性了。

        下面的代码示例中使用 first_word 函数演示了这种返回值失效的情形:

fn main() { let mut s = String::from("hello world"); let word = first_word(&s);  // 索引5会被绑定到变量word上 s.clear();    // 这里的clear方法会清空当前字符串,使之变为"" // 虽然word依然拥有5这个值,但因为我们用于搜索的字符串发生了改变, //所以这个索引也就没有任何意义了,word到这里便失去了有效性 
} 

        上面的程序在编译器看来没有任何问题,即便我们在调用 s.clear() 之后使用 word 变量也是没有问题的。同时由于 word 变量本身与 s 没有任何关联,所以 word 的值始终都是 5

        但当我们再次使用 5 去从变量 s 中提取单词时,一个 bug 就出现了:此时 s 中的内容早已在我们将 5 存入 word 后发生了改变。

        这种 API 的设计方式使我们需要随时关注 word 的有效性,确保它与 s 中的数据是一致的,类似的工作往往相当烦琐且易于出错。这种情况对于另一个函数 second_word 而言更加明显。

        这个函数被设计来搜索字符串中的第二个单词,它的签名也许会被设计为下面这样:

fn second_word(s: &String) -> (usize, usize) {

        现在,我们需要同时维护起始和结束两个位置的索引,这两个值基于数据的某个特定状态计算而来,却没有跟数据产生任何程度上的联系。

        于是我们有了 个彼此不相关的变量需要被同步,这可不妙。幸运的是,Rust为这个问题提供了解决方案:字符串切片。 

 

1. 字符串切片

        字符串切片是指向 String 对象中某个连续部分的引用,它的使用方式如下所示:

let s = String::from("hello world");let hello = &s[0..5];❶let world = &s[6..11];

        我们可以在一对方括号中指定切片的范围区间 [starting_index.. ending_index],其中的 starting_index 是切片起始位置的索引值,ending_index 是切片终止位置的下一个索引值。

        切片数据结构在内部存储了指向起始位置的引用和一个描述切片长度的字段,这个描述切片长度的字段等价于 ending_index 减去 starting_index

        所以在上面示例的❶中,world 是一个指向变量 s 第七个字节并且长度为 5 的切片。下图中所展示的是字符串切片的图解:

        Rust的范围语法..有一个小小的语法糖:当你希望范围从第一个元素(也就是索引值为 0 的元素)开始时,则可以省略两个点号之前的值。

        换句话说,下面两个创建切片的表达式是等价的: 

let s = String::from("hello");let slice = &s[0..2];
let slice = &s[..2];

        同样地,假如你的切片想要包含 String 中的最后一个字节,你也可以省略双点号之后的值。下面的切片表达式依然是等价的: 

let s = String::from("hello");let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];

        你甚至可以同时省略首尾的两个值,来创建一个指向整个字符串所有字节的切片:

let s = String::from("hello");let len = s.len();let slice = &s[0..len];
let slice = &s[..];

注意

        字符串切片的边界必须位于有效的 UTF-8 字符边界内。尝试从一个多字节字符的中间位置创建字符串切片会导致运行时错误。为了将问题简化,我们只会在本篇文章中使用 ASCII 字符集。

        基于所学到的这些知识,让我们开始重构 first_word 函数吧!该函数可以返回一个切片作为结果。字符串切片的类型写作 &str: 

fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] 
} 

        这个新函数中搜索首个单词索引的方式类似于第一个代码示例中的方式。一旦搜索成功,就返回一个从首字符开始到这个索引位置结束的字符串切片。

        调用新的 first_word 函数会返回一个与底层数据紧密联系的切片作为结果,它由指向起始位置的引用和描述元素长度的字段组成。

        当然,我们也可以用同样的方式重构 second_word 函数: 

fn second_word(s: &String) -> &str {

        由于编译器会确保指向 String 的引用持续有效,所以我们新设计的接口变得更加健壮且直观了。还记得在上面示例中故意构造出的错误吗?

        那段代码在搜索完成并保存索引后清空了字符串的内容,这使得我们存储的索引不再有效。它在逻辑上明显是有问题的,却不会触发任何编译错误,这个问题只会在我们使用第一个单词的索引去读取空字符串时暴露出来。

        切片的引入使我们可以在开发早期快速地发现此类错误。在上面示例中,新的 first_word 函数在编译时会抛出一个错误,尝试运行以下代码: 

fn main() { let mut s = String::from("hello world"); let word = first_word(&s); s.clear(); // 错误! println!("the first word is : {}", word); 
} 

        编译错误如下所示:

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable--> src/main.rs:6:5|
4 |     let word = first_word(&s);|                            - immutable borrow occurs here
5 |
6 |     s.clear(); // 错误!|     ^ mutable borrow occurs here
7 | }| - immutable borrow ends here

        回忆一下借用规则,当我们拥有了某个变量的不可变引用时,我们就无法同时取得该变量的可变引用。

        由于 clear 需要截断当前的 String 实例,所以调用 clear 需要传入一个可变引用。这就是编译失败的原因。Rust不仅使我们的API更加易用,它还在编译过程中帮助我们避免了此类错误。 

字符串字面量就是切片

        还记得我们讲过字符串字面量被直接存储在了二进制程序中吗?在学习了切片之后,我们现在可以更恰当地理解字符串字面量了: 

let s = "Hello, world!";

        在这里,变量 s 的类型其实就是 &str:它是一个指向二进制程序特定位置的切片。正是由于 &str 是一个不可变的引用,所以字符串字面量自然才是不可变的。 

将字符串切片作为参数

        既然我们可以分别创建字符串字面量和String的切片,那么就能够进一步优化first_word函数的接口,下面是它目前的签名:

fn first_word(s: &String) -> &str {

        比较有经验的Rust开发者往往会采用下面的写法,这种改进后的签名使函数可以同时处理 String &str: 

fn first_word(s: &str) -> &str {

示例4-9:使用字符串切片作为参数s的类型来改进first_word函数

        当你持有字符串切片时,你可以直接调用这个函数。而当你持有 String 时,你可以创建一个完整 String 的切片来作为参数。

        在定义函数时使用字符串切片来代替字符串引用会使我们的 API 更加通用,且不会损失任何功能,尝试运行以下代码: 

fn main() { let my_string = String::from("hello world"); // first_word 可以接收String对象的切片作为参数 let word = first_word(&my_string[..]); let my_string_literal = "hello world"; // first_word 可以接收字符串字面量的切片作为参数 let word = first_word(&my_string_literal[..]); // 由于字符串字面量本身就是切片,所以我们可以在这里直接将它传入函数,// 而不需要使用额外的切片语法! let word = first_word(my_string_literal); 
} 

 

2. 其他类型的切片

        从名字上就可以看出来,字符串切片是专门用于字符串的。但实际上,Rust还有其他更加通用的切片类型,以下面的数组为例:

let a = [1, 2, 3, 4, 5];

        就像我们想要引用字符串的某个部分一样,你也可能会希望引用数组的某个部分。这时,我们可以这样做: 

let a = [1, 2, 3, 4, 5];let slice = &a[1..3];

        这里的切片类型是 &[i32],它在内部存储了一个指向起始元素的引用及长度,这与字符串切片的工作机制完全一样。你将在各种各样的集合中接触到此类切片,而我们会在后面文章中讨论动态数组时再来介绍那些常用的集合。 

 

相关文章:

010、切片

除了引用,Rust还有另外一种不持有所有权的数据类型:切片(slice)。切片允许我们引用集合中某一段连续的元素序列,而不是整个集合。 考虑这样一个小问题:编写一个搜索函数,它接收字符串作为参数&a…...

【华为数据之道学习笔记】8-6 质量改进

数据质量改进致力于增强满足数据质量要求的能力。数据质量改进消除系统性的问题,对现有的质量水平在控制的基础上加以提高,使质量达到一个新水平、新高度。 质量改进的步骤本身就是一个PDCA循环。质量改进包括涉及企业跨组织的变革性改进(BTM…...

python多环境管理工具——pyenv-win安装与使用教程

目录 pyenv-win简介 pyenv-win安装 配置环境变量 pyenv的基本命令 pyenv安装py环境 pyenv安装遇到问题 pycharm测试 pyenv-win简介 什么是pyenv-win: 是一个在windows系统上管理python版本的工具。它是pyenv的windows版本,旨在提供类似于unix/li…...

Excel报表框架(ExcelReport)极简化解决复杂报表导出问题

Excel Report 耗费了半个月的时间,终于在元旦这三天把报表框架开发完成了,使用该框架你可以非常方便的导出复杂的Excel报表。 项目开源地址: GiteeGithub 前言 不知道各位在使用POI开发报表导出过程中遇到过以下的情况: 频繁…...

常用设计模式全面总结版(JavaKotlin)

这篇文章主要是针对之前博客的下列文章的总结版本: 《设计模式系列学习笔记》《Kotlin核心编程》笔记:设计模式【Android知识笔记】FrameWork中的设计模式主要为了在学习了 Kotlin 之后,将 Java 的设计模式实现与 Kotin 的实现放在一起做一个对比。 一、创建型模式 单例模…...

Docker自建私人云盘系统

Docker自建私人云盘系统。 有个人云盘需求的人,主要需求有这几类: 文件同步、分享需要。 照片、视频同步需要,尤其是全家人都是用的同步。 影视观看需要(分为家庭内部、家庭外部) 搭建个人网站/博客 云端OFFICE需…...

python replace()方法 指定替换指定字段

replace()方法 使用方法 str.replace(old, new[, max]) Python replace() 方法把字符串中的 old(旧字符串) 替换成 new(新字符串),如果指定第三个参数max,则替换不超过 max 次。 示例 #!/usr/bin/pythonstr "this is s…...

【仅供测试】

https://microsoftedge.microsoft.com/addons/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4/iikmkjmpaadaobahmlepeloendndfphd 测试网站: https://www.alipan.com/s/tJ5uzFvp2aF // UserScript // name 阿里云盘助手 // namespace http://tampermonkey.net/ // …...

C#/WPF JSON序列化和反序列化

什么是json json是存储和交换文本信息的方法,类似xml。但是json比xml更小,更快,更易于解析。并且json采用完全独立于语言的文本格式(即不依赖于各种编程语言),这些特性使json成为理想的数据交换语言。json序列化是指将对象转换成j…...

Java——ArraryList线程不安全

目录 前言一、为什么ArraryList线程不安全?二、具体可以看debug源码后续敬请期待 前言 Java——ArraryList线程不安全 一、为什么ArraryList线程不安全? 因为没有synchronized,这个关键字做线程互斥,没有这个关键字,…...

基于Java SSM框架实现健康管理系统项目【项目源码】

基于java的SSM框架实现健康管理系统演示 JSP技术 JSP是一种跨平台的网页技术,最终实现网页的动态效果,与ASP技术类似,都是在HTML中混合一些程序的相关代码,运用语言引擎来执行代码,JSP能够实现与管理员的交互&#xf…...

PostgreSQL16.1(Windows版本)

1、卸载原有的PostgreSQL   点击Next即可。  点击OK即可。 卸载完成。 2、安装 (1) 前两部直接Next,第二部可以换成自己想要安装的路径。 (2) 直接点击Next。…...

使用nodejs对接arXiv文献API

GPT4.0国内站点: 海鲸AI-支持GPT(3.5/4.0),文件分析,AI绘图 要使用 Node.js 对接 arXiv 的 API,你可以使用 axios 库或者 Node.js 的内置 http 模块来发送 HTTP 请求。以下是一个简单的例子,展示了如何使用 axios 来获取 arXiv 上…...

mac 安装pyaudio

直接安装pyaudio时报错 ERROR: Could not build wheels for PyAudio, which is required to install pyproject.toml-based projects需要先安装portaudio,打开终端执行: brew install portaudio再安装pyaudio成功 pip3 install pyaudioportaudio是一个…...

k8s学习 — 各章节重要知识点

k8s学习 — 各章节重要知识点 学习资料k8s版本0 相关命令0.1 yaml配置文件中粘贴内容格式混乱的解决办法0.2 通用命令0.3 Node 相关命令0.4 Pod 相关命令0.5 Deployment 相关命令0.6 Service 相关命令0.7 Namespace 相关命令 1 k8s学习 — 第一章 核心概念1.1 Pod、Node、Servi…...

go slice源码探索(切片、copy、扩容)和go编译源码分析

文章目录 概要一、数据结构二、初始化2.1、字面量2.2、下标截取2.2.1、截取原理 2.3、make关键字2.3.1、编译时 三、复制3.1、copy源码 四、扩容4.1、append源码 五:切片的GC六:切片使用注意事项七:参考 概要 Go语言的切片(slice…...

电影“AI化”已成定局,华为、小米转战入局又将带来什么?

从华为、Pika、小米等联合打造电影工业化实验室、到Pika爆火,再到国内首部AI全流程制作《愚公移山》开机……业内频繁的新动态似乎都在预示着2023年国内电影开始加速进入新的制片阶段,国内AI电影热潮即将来袭。 此时以华为为首的底层技术科技企业加入赛…...

小程序for循环中key值的作用?

在小程序的 for 循环中,key 值有两个主要作用: 识别列表项的唯一性:当在列表渲染时使用 for 循环,每个列表项都应该具有一个唯一的 key 值。这个 key 值用于帮助小程序识别每个列表项的唯一性,以便在列表发生变化时进行…...

深入理解Dockerfile —— 筑梦之路

FROM 基础镜像 可以选择现有的镜像,比如centos、debian、apline等,特殊镜像scratch,它是一个空镜像。 如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。 不…...

Vue3 魔法:轻松删除响应式对象的属性

🧙‍♂️ 诸位好,吾乃诸葛妙计,编程界之翘楚,代码之大师。算法如流水,逻辑如棋局。 📜 吾之笔记,内含诸般技术之秘诀。吾欲以此笔记,传授编程之道,助汝解技术难题。 &…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

SpringCloudGateway 自定义局部过滤器

场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...