【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"
        );
    }
}
相关推荐
欧阳枫落2 分钟前
python 2小时学会八股文-数据结构
开发语言·数据结构·python
何曾参静谧9 分钟前
「QT」文件类 之 QTextStream 文本流类
开发语言·qt
monkey_meng13 分钟前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
落落落sss21 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
2401_8532757342 分钟前
ArrayList 源码分析
java·开发语言
zyx没烦恼42 分钟前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
lb363636363642 分钟前
整数储存形式(c基础)
c语言·开发语言
feifeikon44 分钟前
Python Day5 进阶语法(列表表达式/三元/断言/with-as/异常捕获/字符串方法/lambda函数
开发语言·python
大鲤余1 小时前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
浪里个浪的10241 小时前
【C语言】从3x5矩阵计算前三行平均值并扩展到4x5矩阵
c语言·开发语言·矩阵