rust
的字符串 。字符串不是复合类型,
String
和 &str
:
String
具有所有权,是存储在堆上的。&str
没有所有权,是对String
的引用。字符串字面量也是&str
类型,存储在栈上。
切片(slice)
对于字符串而言,切片就是对字符串一部分的引用。
let s = String::from("hello");
let s1 = &s[0..5]; // 不包含终止索引 hello
创建切片的语法:[开始索引..终止索引]
。(该语法是一个右半开区间) 。
切片数据结构:保存开始的位置(指针)和切片的长度。
..
range 序列 语法。
包含第一个字节或最后一个字节时,开始索引或最后索引可以省略。
let s = String::from("hello");
let s1 = &s[..2]; // he
let s2 = &s[2..]; // llo
let s3 = &s[..]; // hello
注意
在使用切片时,需要保证索引落在字符的边界上。
因为切片上的索引指的是字节,而不是字符,而且字符串使用的编码是UTF-8
。
所以当一个字符占用多个字节时,索引可能不在字符的边界上,导致没有取到完成的字符,出现错误。
字符串切片的类型标识为 &str
。
所以切片(slice)是一个不可变引用。
其他切片
数组也可以进行切片。
切片是对集合的部分引用,所以所有是集合的类型都可以进行切片。
字符串字面量也是切片,因为类型也是 &str
。
字符串
字符串与字符:
- 字符:
Unicode
类型,每个字符占用 4 个字节空间。 - 字符串:
UTF-8
类型,每个字符占用的字节数是变化的(1-4).
语言级别上的字符串类型只有 str
。(编程语言提供的原生字符串类型)
标准库中,还有多种不同用途的字符串类型:用的最广的是 String
类型。(额外的字符串处理工具)
str
和 String
:
str
类型是硬编码进可执行文件,无法被修改。String
是一个可增长、可改变且具有所有权的UTF-8
编码字符串。(&str
也是UTF-8
字符串)
String 与 &str 的转换
从 &str
到 String
:
String::from("hello")
"hello".to_string()
从 String
到 &str
:
- 取引用
Rust 不允许进行字符串索引,因为索引也是按照字节的,可能会落在字符上,导致无法取到完整的字符。
操作字符串
String
的常用方法:
追加(Push)
push()
追加 字符。push_str()
追加 字符串。
在用来的字符串上追加,不会返回新的字符串。
字符串必须是可变的,即必须使用 mut
修饰。
let s = String::from("hello");
s.push(','); // hello,
s.push_str(" rust"); // hello, rust
插入(Insert)
insert()
插入单个字符char
。insert_str()
插入字符串字面量&str
。
需要传入两个参数:
- 字符(串)插入位置的索引。(从0开始计数,越界会出现错误)。
- 要插入的字符串。
在原来的字符串上修改,字符串必须是 可变 的,必须由 mut
关键字修饰。
fn main() {
let mut s = String:from("hello");
s.insert(5,','); // hello,
s.insert_str(6," I like"); // hello, I like
}
替换(Replace)
replace
适用于 String
和 &str
类型。
需要两个参数:
- 要被替换的字符串。
- 新的字符串。
该方法会替换掉所有的匹配到的字符串。
该方法返回一个新的字符串,不是操作原来的字符串。
fn main() {
let s = String::from("hello rust");
let new_s = s.replace("rust", "RUST"); // hello RUST
}
replacen
适用于 String
和 &str
类型。
需要三个参数:
- 前两个参数与
replace()
方法一样。 - 第三个参数表示替换的个数。
该方法放回一个新的字符串,不是操作原来的字符串。
fn main() {
let s = String::from("hello rust rust");
let new_s = s.replacen("rust", "RUST",1); // hello RUST rust
}
replace_range
仅适用于 String
类型。
需要两个参数:
- 要替换字符串的范围(Range)。
- 新的字符串。
直接操作原来在字符串,不会返回新的字符串。
需要使用mut
关键字修饰。
fn main() {
let s = String::from("hello rust rust");
s.replace_range(0..1,"H"); // Hello rust rust
}
删除(Delete)
1. pop
—— 删除并返回字符串的最后一个字符
直接操作原来的字符串。
存在返回值,返回值是一个 Option
类型,如果字符串为空,则返回 None
。
fn main() {
let mut s = String::from("hello");
let s1 = s.pop(); // hell s1:o
let s2 = s.pop(); // hel s2:l
}
2. remove
—— 删除并返回字符串中指定位置的字符
直接操作原来的字符串。
存在返回值,返回值是删除位置的字符串。
接受一个参数:
- 表示该字符起始索引位置。
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);
}
3. truncate
—— 删除字符串中从指定位置到结尾的全部字符
直接操作原来的字符串。
无返回值。
truncate()
方法按照字节来处理字符串,要求参数所给的位置是合法的字符边界。
fn main() {
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3); // 测
dbg!(string_truncate);
}
4. clear
—— 清空字符串
直接操作原来的字符串。
调用后,删除字符串中的所有字符。
fn main() {
let mut string_clear = String::from("string clear");
string_clear.clear();
dbg!(string_clear);
}
连接(Concatenate)
1. 使用 +
或者 +=
连接字符串
右边的参数必须为字符串的切片引用类型(Slice)。
调用 +
时,相当于调用了 std::string
标准库中的 add()
方法,add()
方法的第二个参数是一个引用的类型。
因此,在使用 +
时,必须传递切片引用类型,不能直接传递 String
类型。
返回一个新的字符串。
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);
}
2. 使用 format!
连接字符串
适用于 String
和 &str
。
用法与 print!
的用法类型。
fn main() {
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
println!("{}", s);
}
字符串转义
通过转义的方式 \
输出 ASCII 和 Unicode 字符。
fn main() {
// 通过 \ + 字符的十六进制表示,转义输出一个字符
let byte_escape = "I'm writing \x52\x75\x73\x74!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// \u 可以输出一个 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);
}
不转义的情况:
fn main() {
println!("{}", "hello \\x52\\x75\\x73\\x74");
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
// 如果字符串包含双引号,可以在开头和结尾加 #
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果还是有歧义,可以继续增加,没有限制
let longer_delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", longer_delimiter);
}
操作 UTF-8 字符串
字符
以 Unicode
字符的方式遍历字符串,使用 chars
方法:
for c in "中国人".chars() {
// c 为 字符串中的每个字符
}
字节
遍历每个字节 ,使用 bytes()
:
for b in "中国人".bytes() {
// b 为 字符串的每个字节
}
获取子串
想要准确的从 UTF-8 中获取子串比较困难,标准库并没有提供对应的方法,可以使用第三方库。
推荐 库 utf8_slice
。