【Rust自学】4.5. 切片(Slice)

news2024/12/23 9:02:50

4.5.0. 写在正文之前

这是第四章的最后一篇文章了,在这里也顺便对这章做一个总结:

所有权、借用和切片的概念确保 Rust 程序在编译时的内存安全。 Rust语言让程序员能够以与其他系统编程语言相同的方式控制内存使用情况,但是当数据所有者超出范围时,让数据所有者自动清理该数据意味着您无需编写和调试额外的代码来获得这个控制权

看完这篇文章,相信你会由衷的感叹Rust所有权机制到底有多么神奇和先进。

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

4.5.1. 切片的特性

  • 1. 类型和结构

    • 切片类型的表示方式是:&[T]&mut [T],其中 T 是切片中元素的类型。
    • 不可变切片&[T],只允许读取操作。
    • 可变切片&mut [T],允许修改操作。
  • 2. 不拥有数据

    • 切片本质上是对底层数据的引用,因此它不拥有数据。
    • 切片的生命周期与底层数据一致,当底层数据被销毁时,切片也失效。

4.5.2. 字符串切片

以一道题为例:
编写一个函数,它接受字符串作为参数,它返回它在这个字符串中找到的第一个单词,如果函数没找到任何空格,那么整个字符串就被返回。

fn main() {
	let s = String::from("Hello world");
	let word_index = first_word(&s);
	println!("{}", word_index);
}
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()
}
  • 因为需要逐个元素地遍历String并检查值是否为空格,所以使用as_bytes方法将String转换为字节数组.
  • 迭代器在以后会讲到,现在只需要知道iter是一个方法,用来逐一获取集合中的每个元素。enumerate是一个工具,它在iter的基础上,为每个元素附加一个索引,并将结果作为元组返回。返回元组的第一个元素是索引,第二个元素是对该元素的引用

程序成功编译,输出是5。也就是Hello后边的空格的索引位置

我们现在有办法找出字符串中第一个单词末尾的索引,但是有一个问题。我们自己返回一个usize ,但它只是&String上下文中的一个有意义的数字。换句话说,因为它是与String不同的值,所以不能保证它在将来仍然有效

比如因为某些原因代码在调用first_word之后写了s.clean();这行来清空s,此时的word_index这个变量就没有意义了;也可以说,Rust编译器发现不了代码使用了s.clean()word_index仍然存在的错误,如果你在之后的代码中还使用了word_index去打印字符,那显然就会发生错误。

这类的API(或者叫函数设计)要求随时关注word_index的有效性,确保这个索引和这个String变量s它们之间的同步性。偏偏这类工作往往相当繁琐而且特别容易出错,所以针对这类问题Rust提供了字符串切片

字符串切片是指向字符串中一部分内容的引用。

在原字符串名前加上&代表对它的引用,在后加上[开始索引..结束索引],表示引用这个字符串的一部分。注意,[]内的区间是左闭右开,所以结束索引是切片终止位的下一个索引值。顺口溜:包左不包右。

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

在这个例子中把s从0到5的索引区间(包括0不包括5),也就是"Hello"这部分赋给了hello这个变量;把从6到11的索引区间(包括6不包括11),也就是"world"这个部分赋给了world这个变量
请添加图片描述

由图可见,world这个变量并不会独立于s而存在,这样使得编译器能够在编译过程中就发现许多潜在的问题。

当然,对于索引的写法,还有几种省略的方式:

let hello = &s[0..5];

这个变量是从索引0开始截取的,Rust允许这样的等价写法:

let hello = &s[..5];
let world = &s[6..11];

这个变量截取到了s的最后一个元素,Rust允许这样的等价写法:

let world = &s[6..];

如果想截取整个字符串,那就可以:

let whole = &s[..];

注意事项

  • 字符串切片的范围索引必须发生在有效的utf-8边界内
  • 如果尝试从一个多字节的字符中创建字符串切片,程序会报错并退出

重写代码

学了切片之后,就可以修改文章开头的代码来进一步优化了:

fn main() {
	let s = String::from("Hello world");
	let word = first_word(&s);
	println!("{}", word);
}
fn first_word(s:&String) -> &str {
	let bytes = s.as_bytes();
	for (i, &item) in bytes.iter().enumerate() {
		if item == b' ' {
			return &s[..i];
		} 
	}
	&s[..]
}
  • &str表示字符串切片

这个时候如果在word = first_word(&s);这一行之后加上s.clean();,Rust就能够发现错误并报错:

error[E0502]:cannot borrow `s` as mutable because it is also borrowed as immutable

因为在同一个作用域中出现了可变引用s.clean()和不可变引用&s,违反了借用规则
PS:s.clean()等价于clean(&mut s)

4.5.3. 字符串字面值就是切片

字符串字面值被直接存储在二进制程序之中,在程序运行时会被放入静态内存里

let s = "Hello, World!";

变量s的类型是&str,它是一个指向二进制程序特定位置的切片。&str不可用,所以字符串字面值也是不可变的。

4.5.4. 将字符串切片作为参数传递

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

这是刚刚优化过的代码中声明函数的那一行,这种写法本身完全没有任何问题。但有经验的Rust开发者会使用&str作为s的参数类型,因为这样就可以同时接收String&str类型的参数了:

  • 如果你传入的的值是字符串切片,那么直接调用即可
  • 如果值类型是String,那么可以传入&String类型的实参,当函数参数需要&str而你传递的是&String时,Rust会隐式调用Deref,将&String转换为&str

定义函数时使用字符串切片来代替字符串引用会使APU更加通用,且不会损失任何功能。

根据它,还可以再进一步地优化之前的代码:

fn main() {
	let s = String::from("Hello world");
	let word = first_word(&s);
	println!("{}", word);
}
fn first_word(s:&str) -> &str {
	let bytes = s.as_bytes();
	for (i, &item) in bytes.iter().enumerate() {
		if item == b' ' {
			return &s[..i];
		} 
	}
	&s[..]
}

这行:

let word = first_word(&s);

也可以写成:

let word = first_word(&s[..]);

对于前者,Rust会隐式调用Deref,将&String转换为&str;后者是手动转换为&str类型

4.5.5. 其他类型的切片

fn main() {  
    let number = [1, 2, 3, 4, 5];  
    let num = &number[1..3];  
    println!("{:?}", num);  
}

数组也可以使用切片。num这个切片的本质就是存储了指向number中切片截取的起始点(这个例子中是索引为1的位置)的指针与长度的信息。

其输出是:

[2, 3]

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

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

相关文章

VPN技术-GRE隧道的配置

GRE隧道的配置 1, 在AR1上配置DHCP接口地址池,AR3上配置DHCP全局地址池 2, PC1获取的IP地址为10.10.10.253,PC2获取的IP地址为10.10.30.253 3,通过ip route-static将目的地址为10.10.30.253的流量引入到Tunnel #配…

碰撞检测算法之闵可夫斯基差集法(Minkowski Difference)

在游戏开发和机器人路径规划乃至于现在比较火的自动驾驶中,我们常常需要确定两个物体是否发生碰撞,有一种通过闵可夫斯基差集法求是否相交的算法,下面将介绍一下 闵可夫斯基差集法的优势 闵可夫斯基差集法优势: 可以处理复杂的…

Python OCR 文字识别

一.引言 文字识别,也称为光学字符识别(Optical Character Recognition, OCR),是一种将不同形式的文档(如扫描的纸质文档、PDF文件或数字相机拍摄的图片)中的文字转换成可编辑和可搜索的数据的技术。随着技…

【系统】Windows11更新解决办法,一键暂停

最近的windows更新整的我是措不及防,干啥都要关注一下更新的问题,有的时候还关不掉,我的强迫症就来了,非得关了你不可! 经过了九九八十一难的研究之后,终于找到了一个算是比较靠谱的暂停更新的方法&#x…

复合翼与倾转旋翼飞行器:设计与控制算法对比

一、引言 复合翼(Compound Wing)和倾转旋翼(Tilt - Rotor)飞行器在现代航空领域均占据独特地位,二者在设计和控制算法方面展现出显著差异。这些差异在飞行模式切换、推进系统设计、控制算法复杂度以及飞行器稳定性等多…

空闲中断配合DMA

1.传统串口接收数据:来一个字节接受一个。 2.一次中断将一包数据存到缓冲区 3.DMA原理

三格电子——新品IE103转ModbusTCP网关

型号:SG-TCP-IEC103 产品概述 IE103转ModbusTCP网关型号SG-TCP-IEC103,是三格电子推出的工业级网关(以下简称网关),主要用于IEC103数据采集、DLT645-1997/2007数据采集,IEC103支持遥测和遥信,可…

HDLBits训练3

时间:2024.12.22 Hadd 代码 法一: module top_module( input a, b,output cout, sum );assign {cout,sum}ab; endmodule法二: 运行结果 Fadd 代码 法一: module top_module( input a, b, cin,output cout, sum );assign…

Qt之串口设计-线程实现(十二)

Qt开发 系列文章 - Serial-port(十二) 目录 前言 一、SerialPort 二、实现方式 1.创建类 2.相关功能函数 3.用户使用 4.效果演示 5.拓展应用-实时刷新 总结 前言 Qt作为一个跨平台的应用程序开发框架,在串口编程方面提供了方便易用…

STM32F407ZGT6-UCOSIII笔记12: 事件标志组

有时一个任务需要与多个事件同步,这就要用到事件标志组 本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 文章提供测试代码讲解、完整工程下载、测试效果图 目录 事件标志组: 定义与初始化事件标志组: #include "Public.h&quo…

聊一聊 C#前台线程 如何阻塞程序退出

一:背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题:后台线程的内部是如何运转的 ? ,犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程序无法退出的bug,最后发现是有一个 Backgrond…

JVM性能优化一:初识内存泄露-内存溢出-垃圾回收

本文主要是让你充分的认识到什么叫做内存泄露,什么叫做内存溢出,别再傻傻分不清了,别再动不动的升级服务器的内存了。 文章目录 1.基本概念1.1.内存泄露1.2.内存溢出1.3.垃圾回收1.4.内存泄露-垃圾回收-内存溢出三者的关系关系 2.代码示例2.…

为什么使用环形队列

1.看以下两种情况。第一种不会出现问题,当主流程读取次数比较慢时,数据会被覆盖。 2.扩大空间。不可取。 3.什么是队列

【WRF教程第3.6期】预处理系统 WPS 详解:以4.5版本为例

预处理系统 WPS 详解:以4.5版本为例 Geogrid/Metgrid 插值选项详解1. 插值方法的工作机制2. 插值方法的详细说明2.1 四点双线性插值(four_pt)2.2 十六点重叠抛物线插值(sixteen_pt)2.3 简单四点平均插值(av…

批量提取zotero的论文构建知识库做问答的大模型(可选)——含转存PDF-分割统计PDF等

文章目录 提取zotero的PDF上传到AI平台保留文件名代码分成20个PDF视频讲解 提取zotero的PDF 右键查看目录 发现目录为 C:\Users\89735\Zotero\storage 写代码: 扫描路径‘C:\Users\89735\Zotero\storage’下面的所有PDF文件,全部复制一份汇总到"C:\Users\89735\Downl…

Java模拟Mqtt客户端连接Mqtt Broker

Java模拟Mqtt客户端基本流程 引入Paho MQTT客户端库 <dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.mqttv5.client</artifactId><version>1.2.5</version> </dependency>设置mqtt配置数据 …

boost asio 异步服务器

boost网络框架使用方法 boost绑定 首先介绍io_context&#xff0c;可以理解为这是操作系统和应用层数据交互的桥梁。有了它不必关注内核态的缓冲区&#xff0c;只需要关注自己定义在用户态的缓冲区&#xff0c;因为它会通过桥梁运输到用户态的缓冲区。 boost::asio::io_contex…

图解HTTP-HTTP协议

HTTP HTTP是一种不保存状态&#xff0c;即无状态的协议。HTTP协议自身不对请求和响应之间的通信进行保存。为了保存状态因此后面也有一些技术产生比如Cookies技术。 HTTP是通过URI定位网上的资源&#xff0c;理论上将URI可以访问互联网上的任意资源。 如果不是访问特定的资源…

【Go】-限流器的四种实现方法

目录 关于限流和限流器 固定窗口限流器 滑动窗口限流器 漏桶限流器 令牌桶限流器 总结 关于限流和限流器 限流&#xff08;Rate Limiting&#xff09;是一种控制资源使用率的机制&#xff0c;通常用于防止系统过载和滥用。 限流器&#xff08;Rate Limiter&#xff09;是…

CTF_1

CTF_Show 萌新赛 1.签到题 <?php if(isset($_GET[url])){system("curl https://".$_GET[url].".ctf.show"); }else{show_source(__FILE__); }?> 和 AI 一起分析 1.if(isset($_GET[url]))检查GET请求中是否存在名为url的参数。 curl 2.curl…