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

【rCore OS 开源操作系统】Rust 字符串(可变字符串String与字符串切片str)

【rCore OS 开源操作系统】Rust 语法详解: Strings

前言

这次涉及到的题目相对来说比较有深度,涉及到 Rust 新手们容易困惑的点。

这一次在直接开始做题之前,先来学习下字符串相关的知识。

Rust 的字符串

Rust中“字符串”这个概念涉及多种类型,这里介绍题目中涉及到的两种的字符串类型:&str(字符串切片,slice)和String(可变字符串)。

String 可变字符串

存储在堆中,并且是可变的——是和其他大部分主流编程语言类似的概念。

示例:

let mut s: String = String::from("hello");
s.push_str(", world"); // “hello, world”,是的这里就是可变的

&str 字符串切片

那么,什么是切片呢?

既然 String 被我们称为可变字符串,这里也就暗示字符串切片是不可变的immutable)。

同时,它是存储在栈中的。

来看看如何产生字符串切片:

// 字面量是一种切片
let s1 = "Hello, World"; 
// 完整的写法其实是 let s1: &str = "Hello, World"; // 也可以从可变字符串中切割
let string:String = String::from("wow,amazing");  
let s2 = string[0..3]; // 左开右闭,正好就是 “wow”

可以从切片中得到可变字符串:

let string1 = String::from("hello");
let string2 = "hello".to_string();

这里也不很啰嗦,很直观地就认识了两种字符串在形式上的区别。

那再来看看本质。

所有权、引用与借用

要搞懂字符串的本质,还得回到所有权、引用和借用三个概念上。

所有权

这里是一段官话:

所有权是 Rust 中的核心概念之一,它决定了数据的生命周期和内存管理方式。每个值在 Rust 中都有一个拥有它的变量,称为“所有者”。

所有权有三个特性:

  • 唯一所有者:一个值在同一时间内只能有一个所有者。
  • 所有权转移:当把一个所有者的值传递给另一个变量时,所有权会被转移。
  • 自动释放内存:当一个值的所有者离开作用域时,该值所占用的内存会被自动释放

第一次看估计不太理解,但是没关系,直接来对照代码再看一次:

这里就当作是一个代码拟人小故事来看

// 现在变量 s 是 String::from("hello") 的所有者
let s = String::from("hello");
// 直接赋值,现在 String::from("hello") 到了 t 的手中,t 是新的所有者,而 s 不再是了,s 会被释放掉!
// 这里就体现了三个特性,“唯一所有者”,“所有权转移”和“自动释放内存”。
let t = s; 
//  这里会编译失败,因为 s 已经不再有效println!("{}", s); // 报错

所有权的作用是什么?

这个问题有有更广而全面的回答,此处只简单说明最核心的一点

内存安全,所有权机制的存在避免了许多常见的内存错误,如悬空引用双重释放等问题——因为一个引用永远都是有值的,并且同一时刻只有一个指针能够操作值,而且在值指针失效时会自动销毁。

引用和借用

Rust 中的引用是(直接)基于指针实现的,可以看作是别名

而 JavaScript 等语言并不是直接基于指针实现的,而是依靠对象的引用实现的。
当然了,对象的引用的本质其实也还是可以基于指针——所以这里提到了“直接”一词。

基于指针是什么意思呢?

对于 String 类型,它的定义差不多是这样:

struct MyString {ptr: *const u8, // 一个指针len: usize, // 字符串长度, 是当前字符串中字符的数量cap: usize, // 字符串的最大容量,指最多可以容纳多少长度
}

那别名是什么意思呢?

这里我们也拟人地来解释下:

// a 和 b 是两个人,只是名字相同。
let a = 1;
let b = 1;// s 和 r 是同一个人,只是有两个名字
let mut s = String::from("hello");
let r = &s; // r 是 s 的引用

当然,这里的写法中, r 是不可以去修改值的。
如果需要修改,那么要这样写:

let r = &mut s; // r 是 s 的引用

那什么是借用呢?
这里的官话就是:

借用是值,允许使用一个变量的值,而不改变所有权。

其实,借用就是 Rust 的引用的一个特性。

来看下述代码(魔改自《Rust 高级程序设计》):

fn main() {let s1 = String::from("hello");// 传入一个 s1 的引用,不会改变所有权。// 而 s1 的所有权没有转移,就意味着 s1 不会被销毁,在后面可以接着用。let len = calculate_length(&s1); // 使用 s1 不报错呢println!("The length of '{}' is {}.", s1, len);
}fn calculate_length(s: &String) -> usize {s.len()
}

练习题

Strings1

题目
// strings1.rs
//
// Make me compile without changing the function signature!
//
// Execute `rustlings hint strings1` or use the `hint` watch subcommand for a
// hint.// I AM NOT DONEfn main() {let answer = current_favorite_color();println!("My current favorite color is {}", answer);
}fn current_favorite_color() -> String {"blue"
}
题解

有了上面的解释后,这里做起来就简单一些了。
这里的考点也就是区分可变字符串 String 和字符串切片 &str

// strings1.rs
//
// Make me compile without changing the function signature!
//
// Execute `rustlings hint strings1` or use the `hint` watch subcommand for a
// hint.fn main() {let answer = current_favorite_color();println!("My current favorite color is {}", answer);
}fn current_favorite_color() -> String {// 两种写法都可以// "blue".to_string()String::from("blue")
}

Strings2

题目
// strings2.rs
//
// Make me compile without changing the function signature!
//
// Execute `rustlings hint strings2` or use the `hint` watch subcommand for a
// hint.fn main() {let word = String::from("green"); // Try not changing this line :)if is_a_color_word(&word) {println!("That is a color word I know!");} else {println!("That is not a color word I know.");}
}fn is_a_color_word(attempt: &String) -> bool {attempt == "green" || attempt == "blue" || attempt == "red"
}
题解

在上文的字符串的知识点梳理中,其实已经给出了类似的代码了。

// strings2.rs
//
// Make me compile without changing the function signature!
//
// Execute `rustlings hint strings2` or use the `hint` watch subcommand for a
// hint.fn main() {let word = String::from("green"); // Try not changing this line :)if is_a_color_word(&word) {println!("That is a color word I know!");} else {println!("That is not a color word I know.");}
}fn is_a_color_word(attempt: &String) -> bool {attempt == "green" || attempt == "blue" || attempt == "red"
}

Strings3

题目
// strings3.rs
//
// Execute `rustlings hint strings3` or use the `hint` watch subcommand for a
// hint.// I AM NOT DONEfn trim_me(input: &str) -> String {// TODO: Remove whitespace from both ends of a string!???
}fn compose_me(input: &str) -> String {// TODO: Add " world!" to the string! There's multiple ways to do this!???
}fn replace_me(input: &str) -> String {// TODO: Replace "cars" in the string with "balloons"!???
}#[cfg(test)]
mod tests {use super::*;#[test]fn trim_a_string() {assert_eq!(trim_me("Hello!     "), "Hello!");assert_eq!(trim_me("  What's up!"), "What's up!");assert_eq!(trim_me("   Hola!  "), "Hola!");}#[test]fn compose_a_string() {assert_eq!(compose_me("Hello"), "Hello world!");assert_eq!(compose_me("Goodbye"), "Goodbye world!");}#[test]fn replace_a_string() {assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool");assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons");}
}
题解

这个题目还是有点难度,考察 Rust 常用的字符串操作 API。
这里我根据题目的提示查阅了官方文档:
Rust字符串操作方法: trim
然后还要注意,有的方法挂在是字符串切片,有的则挂在可变字符串上。
所以在操作它们的时候,还得先转化。

// strings3.rs
//
// Execute `rustlings hint strings3` or use the `hint` watch subcommand for a
// hint.fn trim_me(input: &str) -> String {let string = input.to_string();string.trim().to_string()
}fn compose_me(input: &str) -> String {let mut string = input.to_string();// 这里有个坑点是,push_str 改变的是在原来的值上进行修改,而返回值是一个空的元组string.push_str(" world!");string// 还有一种比较奇技淫巧:// format!("{} world!", input)
}fn replace_me(input: &str) -> String {let str = input.replace("cars", "balloons");str.to_string()
}#[cfg(test)]
mod tests {use super::*;#[test]fn trim_a_string() {assert_eq!(trim_me("Hello!     "), "Hello!");assert_eq!(trim_me("  What's up!"), "What's up!");assert_eq!(trim_me("   Hola!  "), "Hola!");}#[test]fn compose_a_string() {assert_eq!(compose_me("Hello"), "Hello world!");assert_eq!(compose_me("Goodbye"), "Goodbye world!");}#[test]fn replace_a_string() {assert_eq!(replace_me("I think cars are cool"),"I think balloons are cool");assert_eq!(replace_me("I love to look at cars"),"I love to look at balloons");}
}
http://www.lryc.cn/news/453647.html

相关文章:

  • 远程过程调用RPC知识科普
  • Java - LeetCode面试经典150题 - 区间 (三)
  • NVIDIA网卡系列之ConnectX-6 DX规格信息(200G-PCIe 4.0x16-8PF1000VF-2019年发布)
  • 【案例】平面云
  • 测试用例的进阶二
  • zotero WebDAV同步忘记密码
  • 如何在 SQL 中创建一个新的数据库?
  • 《Linux从小白到高手》理论篇:Linux的进程管理详解
  • 【Qt】控件概述(3)—— 显示类控件
  • 数据库管理-第247期 23ai:全球分布式数据库-Schema对象(20241004)
  • Docker搭建一款开源的文档管理系统
  • 软件验证与确认实验一:静态分析
  • 基于SpringBoot+Vue的高校运动会管理系统
  • 什么东西可以当做GC Root,跨代引用如何处理?
  • Python深度学习:从神经网络到循环神经网络
  • C++输⼊输出
  • 卡码网KamaCoder 117. 软件构建
  • Acwing 线性DP
  • Docker面试-24年
  • ubuntu 安装k8s
  • No.4 笔记 | 探索网络安全:揭开Web世界的隐秘防线
  • spring揭秘24-springmvc02-5个重要组件
  • 关键字:register
  • 力扣 简单 110.平衡二叉树
  • 基于深度学习的代码优化
  • 汽车电气系统中KL30、KL15、KL50、KLR、KL31、KL87、KL75的作用
  • 随笔(四)——代码优化
  • 安装管理K8S的开源项目KubeClipper介绍
  • 北交大研究突破:塑料光纤赋能低成本无摄像头AR/VR眼动追踪技术
  • 算法题总结(七)——哈希表