02-字符串
本文内容摘编自: 快速入门:Rust中有哪些你不得不了解的基础语法?(著作权归AI悦创所有) Rust语言圣经(Rust Course)-字符串与切片
字符串
x = " "
Rust 字符串对 Unicode 字符集有着良好的支持。
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
let hello = String::from("你好");
&str
本质是一种切片Slice
。切片详见 切片与引用。其中字符也由UTF-8编码。它既单独输出一些内容,也可以截取String
中的内容。
- 语法
let hello = ""
或者截取
let s = String::from("可爱的人");
let slice = &s[0..2];
__
提示
注意,Rust 中的String
不能通过下标(索引)去访问。
let hello = String::from("你好");
// let a = hello[0]; // 你可能想把“你”字取出来,但实际上这样是错误的
该代码会产生如下错误:
3 | let h = hello[0];
| ^^^^^^^^ `String` cannot be indexed by `{integer}`
|
= help: the trait `Index<{integer}>` is not implemented for `String`
因为 String 存储的 Unicode 序列的 UTF-8 编码,而 UTF-8 编码是变长编码。
UTF-8为何是变长编码[Ref2]
拉丁字母
对于
let hello = String::from("Hola");
这行代码来说,Hola
的长度是4
个字节,因为"Hola"
中的每个字母在 UTF-8 编码中仅占用 1 个字节,但是对于下面的代码呢?中文
let s = String::from("中国人");
该字符串是 9
个字节的长度,因为大部分常用汉字在 UTF-8 中的长度是 3
个字节。
再来看一下用梵文写的字符串
它底层的字节数组如下形式:नमस्ते
224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135
长度是 18 个字节,这也是计算机最终存储该字符串的形式。如果从字符的形式去看,则是:
'न', 'म', 'स', '्', 'त', 'े'
但是书写过程中,第四和六两个字母根本就不存在,在这种语言中只是两个辅助值并不占位。接着再从字母串的形式去看:
["न", "म", "स्", "ते"]
所以,可以看出来 Rust 提供了不同的字符串展现方式,这样程序可以挑选自己想要的方式去使用,而无需去管字符串从人类语言角度看长什么样。
还有一个原因导致了 Rust 不允许去索引字符串:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 String
类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。
强行采用&s[]
取值
如果你想从索引 0 开始,可以使用如下的方式,这两个是等效的:
fn main() {
let s = String::from("中国人");
let _slice = &s[0..3];
let _slice = &s[..3];
println!("{}",_slice);
}
::: dinger
错误示例:
let s = String::from("中国人");
// let a = &s[0..2];
// println!("{}",a);
:::
这种情况下对 s
进行索引,访问 &s[0..1]
没有任何意义,因为我们只取 s
字符串的前两个字节,但是本例中每个汉字占用3个字节,因此没有落在边界处,也就是连 中
字都取不完整,这是一个非常奇怪而且难以理解的返回值。此时程序会直接崩溃退出。
如果改成 &s[0..3]
,则可以正常通过编译。 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点。
字符串的转义
与 C 语言一样,Rust 中转义符号也是反斜杠 \
,可用来转义各种字符。
fn main() {
/* 将""号进行转义 */
let byte_escape = "I'm saying \"Hello\"";
println!("{}", byte_escape);
/* 分两行打印(转义换行符) */
let byte_escape = "I'm saying \n 你好";
println!("{}", byte_escape);
/* Windows下的换行符 */
let byte_escape = "I'm saying \r\n 你好";
println!("{}", byte_escape);
/* 打印出 \ 本身 */
let byte_escape = "I'm saying \\ Ok";
println!("{}", byte_escape);
/* 强行在字符串后面加个0,与C语言的字符串一致。*/
let byte_escape = "I'm saying hello.\0";
println!("{}", byte_escape);
}
除此之外,Rust 还支持通过 \x
输入等值的 ASCII
字符,以及通过 \u{}
输入等值的 Unicode 字符。
fn main() {
// 使用 \x 输入等值的ASCII字符(最高7位)
let byte_escape = "I'm saying hello \x7f";
println!("{}", byte_escape);
// 使用 \u{} 输入等值的Unicode字符(最高24位)
let byte_escape = "I'm saying hello \u{0065}";
println!("{}", byte_escape);
}
不转义字符串
有时候,我们不希望字符串被转义,也就是想输出原始字面量。这个在 Rust 中也有办法,使用 r""
或 r#""#
把字符串字面量套起来就行了。
fn main() {
// 字符串字面量前面加r,表示不转义
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
// 这个字面量必须使用r##这种形式,因为我们希望在字符串字面量里面保留""
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果遇到字面量里面有#号的情况,可以在r后面,加任意多的前后配对的#号,
// 只要能帮助Rust编译器识别就行
let longer_delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", longer_delimiter);
}
一点小提示,Rust 中的字符串字面量都支持换行写,默认把换行符包含进去。
注:字符串转义的详细知识点,请参考 Tokens - The Rust Reference (rust-lang.org)
在Rust中,标准输入的读取通常是通过 io::stdin().read_line()
实现的。如果你想要一种更简便的方式,尤其是对于需要从命令行读取单个值的场景,可以使用第三方的库,比如 text_io
或 clap
来简化输入。
这里是一个使用 text_io
库的示例,它可以简化用户输入的读取过程:
使用 text_io
库
首先,你需要在 Cargo.toml
文件中添加 text_io
作为依赖项:
[dependencies]
text_io = "0.1"
然后,你可以使用以下代码来简化输入过程:
use text_io::read;
fn main() {
println!("Enter value: ");
let input: f32 = read!();
let fahrenheit = (input * 1.8) + 32.0;
println!("{:.2}°C = {:.2}°F", input, fahrenheit);
}
这个示例中,read!()
宏会自动从标准输入读取并解析值,使代码更加简洁。