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

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

前言

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

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

Rust 的字符串

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

String 可变字符串

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

示例:

rust 复制代码
let mut s: String = String::from("hello");
s.push_str(", world"); // "hello, world",是的这里就是可变的

&str 字符串切片

那么,什么是切片呢?

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

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

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

rust 复制代码
// 字面量是一种切片
let s1 = "Hello, World"; 
// 完整的写法其实是 let s1: &str = "Hello, World"; 

// 也可以从可变字符串中切割
let string:String = String::from("wow,amazing");  
let s2 = string[0..3]; // 左开右闭,正好就是 "wow"

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

rust 复制代码
let string1 = String::from("hello");
let string2 = "hello".to_string();

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

那再来看看本质。

所有权、引用与借用

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

所有权

这里是一段官话:

所有权是 Rust 中的核心概念之一,它决定了数据的生命周期和内存管理方式。每个值在 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 类型,它的定义差不多是这样:

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

那别名是什么意思呢?

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

rust 复制代码
// a 和 b 是两个人,只是名字相同。
let a = 1;
let b = 1;

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

当然,这里的写法中, r 是不可以去修改值的。

如果需要修改,那么要这样写:

rust 复制代码
let r = &mut s; // r 是 s 的引用

那什么是借用呢?

这里的官话就是:

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

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

来看下述代码(魔改自《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

题目
rust 复制代码
// 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 DONE

fn main() {
    let answer = current_favorite_color();
    println!("My current favorite color is {}", answer);
}

fn current_favorite_color() -> String {
    "blue"
}
题解

有了上面的解释后,这里做起来就简单一些了。

这里的考点也就是区分可变字符串 String 和字符串切片 &str

rust 复制代码
// 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

题目
rust 复制代码
// 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"
}
题解

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

rust 复制代码
// 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

题目
rust 复制代码
// strings3.rs
//
// Execute `rustlings hint strings3` or use the `hint` watch subcommand for a
// hint.

// I AM NOT DONE

fn 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 复制代码
// 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"
        );
    }
}
相关推荐
爱编程的鱼9 分钟前
C# 结构(Struct)
开发语言·人工智能·算法·c#
红尘散仙14 分钟前
六、WebGPU 基础入门——Vertex 缓冲区和 Index 缓冲区
前端·rust·gpu
苏近之16 分钟前
深入浅出 Rust 异步运行时原理
rust·源码
只可远观21 分钟前
Flutter Dart 循环语句 for while do..while break、continue
开发语言·javascript·ecmascript
红尘散仙1 小时前
四、WebGPU 基础入门——Uniform 缓冲区与内存对齐
前端·rust·gpu
望获linux1 小时前
实时操作系统在服务型机器人中的关键作用
linux·机器人·操作系统·开源软件·rtos·具身智能
吴_知遇1 小时前
【华为OD机试真题】428、连续字母长度 | 机试真题+思路参考+代码解析(E卷)(C++)
开发语言·c++·华为od
basketball6162 小时前
Python torchvision.transforms 下常用图像处理方法
开发语言·图像处理·python
宁酱醇2 小时前
各种各样的bug合集
开发语言·笔记·python·gitlab·bug
啊吧怪不啊吧2 小时前
Linux常见指令介绍下(入门级)
linux·开发语言·centos