当前位置: 首页 > 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 魔法:轻松删除响应式对象的属性

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

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言:多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率&#xff0c…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

云原生安全实战:API网关Kong的鉴权与限流详解

🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...

CppCon 2015 学习:Time Programming Fundamentals

Civil Time 公历时间 特点: 共 6 个字段: Year(年)Month(月)Day(日)Hour(小时)Minute(分钟)Second(秒) 表示…...

深度解析:etcd 在 Milvus 向量数据库中的关键作用

目录 🚀 深度解析:etcd 在 Milvus 向量数据库中的关键作用 💡 什么是 etcd? 🧠 Milvus 架构简介 📦 etcd 在 Milvus 中的核心作用 🔧 实际工作流程示意 ⚠️ 如果 etcd 出现问题会怎样&am…...

工厂方法模式和抽象工厂方法模式的battle

1.案例直接上手 在这个案例里面,我们会实现这个普通的工厂方法,并且对比这个普通工厂方法和我们直接创建对象的差别在哪里,为什么需要一个工厂: 下面的这个是我们的这个案例里面涉及到的接口和对应的实现类: 两个发…...