首先我们先看看这个代码
fn main() {
let my_name = "Pascal";
greet(my_name);
}
fn greet(name: String) {
println!("Hello, {}!", name);
}
这段代码会报错,因为 greet
函数需要一个 String
类型的参数,而 my_name
是一个 &str
类型的变量,这两种类型是不同的。
切片(Slice)是一种数据类型,它引用了一段数据,但是并不拥有它,它是对一个已经存在的变量的引用。
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
hello
和 world
都是 &str
类型的变量,它们是 s
的切片。
创建切片的语法,使用方括号包括的一个序列:[开始索引..终止索引]
..
range 运算符,它会从第一个索引开始一直到最后一个索引(包括最后一个索引)。
// 索引从 0 开始
let s = String::from("hello");
let slice = &s[0..2]; // 等价于 he
let slice = &s[..2]; // 等价于 he
// 索引到结尾
let len = s.len();
let slice = &s[3..len]; // 等价于 lo
let slice = &s[3..]; // 等价于 lo
// 索引从 0 开始到结尾
let slice = &s[..]; // 等价于 hello
let slice = &s[0..len]; // 等价于 hello
字符串字面量是切片,类型是 &str
,它是一个不可变的引用。
let s = "hello";
let s: &str = "hello";
- 字符串是由字符组成的连续集合
- Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间
- 但是字符串不同,字符串是 UTF-8 编码,因此所占的字节数是不确定的(1-4 个字节)
- 语言中
str
是类型,&str
是引用,String
是类型,&String
是引用。String
是标准库中提供的,str
是语言的一部分。 str
类型是无法修改的,String
类型是可以修改的
如何转化String
和str
呢?
let s = String::from("hello");
let slice = &s[..]; // 等价于 hello
let s = String::from("hello");
let h = s[0]; // error `String` cannot be indexed by `{integer}`
字符串的底层的数据存储格式实际上是[ u8 ],一个字节数组。对于 let hello = String::from("Hola"); 这行代码来说,Hola 的长度是 4 个字节,因为 "Hola" 中的每个字母在 UTF-8 编码中仅占用 1 个字节,但是对于下面的代码呢?
也就是说一个字符串并不是依据字符长度计算而是字节,例如中文字符占用 3 个字节,英文字符占用 1 个字节。
还有一个原因导致了 Rust 不允许去索引字符串:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 String 类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。
let hello = "中国人";
let s = &hello[0..2]; // error: byte index 2 is not a char boundary; it is inside '中' (bytes 0..3) of `中国人`
因此在通过索引区间来访问字符串时,需要格外的小心,一不注意,就会导致你程序的崩溃!
由于 String 是可变字符串,下面介绍 Rust 字符串的修改,添加,删除等常用方法:
尾部追加字符
- 在原有的字符串上追加,并不会返回新的字符串
- 该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰
let mut s = String::from("hello");
s.push(' '); // 追加一个字符
s.push_str("world"); // 追加一个字符串
在指定位置插入字符
- 由于字符串插入操作要修改原来的字符串
- 字符串变量必须由 mut 关键字修饰
let mut s = String::from("hello");
s.insert(0, ' '); // 在索引 0 的位置插入一个字符
s.insert_str(0, "world"); // 在索引 0 的位置插入一个字符串
替换字符串
replace
- 第一个参数是要被替换的字符串,第二个参数是新的字符串
- 该方法是返回一个新的字符串,而不是操作原来的字符串。
replacen
- 第一个参数是要被替换的字符串,第二个参数是新的字符串,第三个参数是替换的次数
- 该方法是返回一个新的字符串,而不是操作原来的字符串。
replace_range
- 第一个参数是要被替换的字符串的索引范围,第二个参数是新的字符串
- 该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰。
let mut s = String::from("hello");
s.replace("hello", "world"); // 替换字符串
s.replacen("hello", "world", 1); // 替换字符串,只替换第一个
s.replace_range(0..5, "world"); // 替换索引 0 到 5 的字符串
与字符串删除相关的方法有 4 个,他们分别是 pop(),remove(),truncate(),clear()。这四个方法仅适用于 String 类型。
pop
- 删除字符串最后一个字符,并返回该字符
- 存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None,否则返回 Some(value)
fn main() {
let mut string_pop = String::from("rust pop 中文!");
let p1 = string_pop.pop();
let p2 = string_pop.pop();
dbg!(p1);
dbg!(p2);
dbg!(string_pop);
}
remove
- 删除指定索引的字符,并返回该字符
fn main() {
let mut string_remove = String::from("测试remove方法");
println!(
"string_remove 占 {} 个字节",
std::mem::size_of_val(string_remove.as_str())
);
// 删除第一个汉字
string_remove.remove(0);
// 下面代码会发生错误
// string_remove.remove(1);
// 直接删除第二个汉字
// string_remove.remove(3);
dbg!(string_remove);
}
truncate
- 保留指定长度的字符串,超出的部分将被删除
- 该方法是直接操作原来的字符串。无返回值。
fn main() {
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3);
dbg!(string_truncate);
}
clear
- 清空字符串
- 该方法是直接操作原来的字符串
fn main() {
let mut string_clear = String::from("string clear");
string_clear.clear();
dbg!(string_clear);
}
- 使用
+
或是+=
运算符
-
是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰。
fn main() { let string_append = String::from("hello "); let string_rust = String::from("rust"); // &string_rust会自动解引用为&str let result = string_append + &string_rust; let mut result = result + "!"; // `result + "!"` 中的 `result` 是不可变的 result += "!!!"; println!("连接字符串 + -> {}", result); }
-
使用
format!
连接字符串format! 这种方式适用于 String 和 &str 。format! 的用法与 print! 的用法类似,
fn main() { let s1 = "hello"; let s2 = String::from("rust"); let s = format!("{} {}!", s1, s2); println!("{}", s); }
//字符
let c = 'hello';
for b in c.chars() {
println!("{}", b);
}
//字节
for b in c.bytes() {
println!("{}", b);
}
- 🌟 正常情况下我们无法使用 str 类型,但是可以使用 &str 来替代
// 修复错误,不要新增代码行
fn main() {
let s: &str = "hello, world";
}
- 🌟🌟 如果要使用 str 类型,只能配合 Box。 & 可以用来将 Box 转换为 &str 类型
// 使用至少两种方法来修复错误
fn main() {
let s: Box<str> = "hello, world".into();
greetings(&s)
}
fn greetings(s: &str) {
println!("{}",s)
}
- 🌟
// 填空
fn main() {
let mut s = String::from("");
s.push_str("hello, world");
s.push('!');
assert_eq!(s, "hello, world!");
}
- 🌟🌟🌟
// 修复所有错误,并且不要新增代码行
fn main() {
let mut s = String::from("hello");
s.push(',');
s.push_str(" world");
s.push('!');
println!("{}", s)
}
- 🌟🌟🌟
// 填空
fn main() {
let s = String::from("I like dogs");
// 以下方法会重新分配一块内存空间,然后将修改后的字符串存在这里
let s1 = s.replace("dogs", "cats");
assert_eq!(s1, "I like cats")
}
- 🌟🌟🌟
// 修复所有错误,不要删除任何一行代码
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
let s3 = s1 + s2;
assert_eq!(s3,"hello,world!");
println!("{}",s1);
}
- 🌟🌟🌟
// Solution 1
fn main() {
let s = String::from("hello, world");
greetings(s)
}
fn greetings(s:String) {
println!("{}",s)
}
// Solution 2
fn main() {
let s = "hello, world".to_string();
greetings(s)
}
fn greetings(s: String) {
println!("{}",s)
}
- 🌟🌟🌟
// 使用两种方法来解决错误,不要新增代码行
fn main() {
let s = "hello, world".to_string();
let s1: &str = s;
}
// 使用两种方法来解决错误,不要新增代码行
fn main() {
let s = "hello, world";
let s1: &str = s;
}
- 🌟
fn main() {
// 你可以使用转义的方式来输出想要的字符,这里我们使用十六进制的值,例如 \x73 会被转义成小写字母 's'
// 填空以输出 "I'm writing Rust"
let byte_escape = "I'm writing Ru\x73\x74!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// 也可以使用 Unicode 形式的转义字符
let unicode_codepoint = "\u{211D}";
let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
println!("Unicode character {} (U+211D) is called {}",
unicode_codepoint, character_name );
// 还能使用 \ 来连接多行字符串
let long_string = "String literals
can span multiple lines.
The linebreak and indentation here \
can be escaped too!";
println!("{}", long_string);
}
- 🌟🌟🌟 有时候需要转义的字符很多,我们会希望使用更方便的方式来书写字符串: raw string.
/* 填空并修复所有错误 */
fn main() {
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
// 修改上面的行让代码工作
assert_eq!(raw_str, "Escapes don't work here: ? ℝ");
// 如果你希望在字符串中使用双引号,可以使用以下形式
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果希望在字符串中使用 # 号,可以如下使用:
let delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", delimiter);
// 填空
let long_delimiter = r####"Hello, \"##\"####;
assert_eq!(long_delimiter, "Hello, \"##\"")
}
- 🌟🌟 你无法通过索引的方式去访问字符串中的某个字符,但是可以使用切片的方式 &s1[start..end] ,但是 start 和 end 必须准确落在字符的边界处.
fn main() {
let s1 = String::from("hi,中国");
let h = s1[0..1]; // 修改当前行来修复错误,提示: `h` 字符在 UTF-8 格式中只需要 1 个字节来表示
assert_eq!(h, "h");
let h1 = &s1[3..6];// 修改当前行来修复错误,提示: `中` 字符在 UTF-8 格式中需要 3 个字节来表示
assert_eq!(h1, "中");
}
- 🌟🌟🌟
fn main() {
// 填空,打印出 "你好,世界" 中的每一个字符
for c in "你好,世界".chars() {
println!("{}", c)
}
}