我们已经见过了Rust的动态字符串String类型:
let mut a = String::from("hello");
a.push_str(" world");
今天再借LeetCode的题来了解一下字符串的常用操作——切片。
对String来说,切片是一种引用形式的子串,所以它的效率很高。
我们先看题目:
58. 最后一个单词的长度
给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。
示例:
输入: "Hello World"
输出: 5
为了得到正确答案,需要3步操作。
- 去除结尾的空格
- 找到最后一个单词
- 得到单词长度
其中第一步可以用String的trim()
方法。
不考虑切片,使用已经熟悉的引用和直接返回值的方法,可以得到如下的实现:
fn find_last_word(s: &String) -> String {
let mut r = String::new();
for c in s.chars() {
match c {
' ' => r = String::new(),
_ => r.push(c),
}
}
r
}
fn length_of_last_word(s: &String) -> i32 {
let s = String::from(s.trim());
let last_word = find_last_word(s);
last_word.len() as i32
}
pub fn main() {
let s = String::from("hello world");
let l = length_of_last_word(&s);
println!("length of last word: {}", l);
}
这里需要改进的地方是find_last_word
,因为我们使用了动态分配的字符串来保存最后一个单词。其实,为了避免额外的内存,我们可以让函数返回一个s
的切片:
fn find_last_word(s: &String) -> &str {
let mut begin = 0;
for (i, c) in s.char_indices() {
if c == ' ' {
begin = i + 1;
}
}
&s[begin..]
}
这里的返回类型,&str
,是一个不可变的字符串切片。它实际上引用的是String内的一个数据片段。这也意味着切片的有效期必须不能超过原始数据。
可变的切片对应的类型是&mut str
,语法上也用mut关键字来区分(注意b
的类型——可变变量和可变切片是相互独立的概念):
let s = String::from("hello");
let mut t = String::from("world");
let a = &s[0..1];
let b = &mut t[2..3];
由于一个切片本质也是对数据的借用,所以必须遵循借用的规则:
- 如果已经创建了切片,那么String本身不能再被可写借用
- 可写的切片跟其他切片或借用也不能并存
简单来说,Rust想要保证的只有两个核心的约定:一是引用和切片背后的数据不能过早的失效,二是同一时刻的数据访问方式必须是安全的(只有最多一个写者,或者只有多个读者但是没有写者)。如果碰到了跟借用有关的编译错误,就要结合编译器给出的错误提示,检查上下文里的其他切片和借用,是不是违反了共存的规则或者生命周期的规则。
最后总结一下,LeetCode 58完整的答案如下:
fn find_last_word(s: &String) -> &str {
let mut begin = 0;
for (i, c) in s.char_indices() {
if c == ' ' {
begin = i + 1;
}
}
&s[begin..]
}
pub fn length_of_last_word(s: &String) -> i32 {
let s = String::from(s.trim());
let last_word = find_last_word(&s);
last_word.len() as i32
}
pub fn main() {
let s = String::from("a ");
let l = length_of_last_word(&s);
println!("length of last word: {}", l);
}
关注红小豆,一起学习Rust开发。欢迎点赞,转发,收藏!