共计 5476 个字符,预计需要花费 14 分钟才能阅读完成。
什么是字符串(String)?
Rust 在外围语言中只有一种字符串类型,即字符串切片 str,通常以借用模式 &str 看到。
String 类型是 Rust 的规范库提供的,而不是编码为外围语言,它是一种可增长、可变、可领有的以 UTF- 8 模式编码的字符串类型。在 Rust 中援用 ”strings” 时,它们通常指的是 String 和字符串切片 &str 类型,但 strings 不仅仅是这两种类型。但两种类型在 Rust 的规范库中应用最多,并且 String 和字符串切片 &str 都是 UTF- 8 编码的。
Rust 的规范库还包含许多其余字符串类型,例如 OsString,OsStr,CString 和 CStr。图书馆包装箱 (Library crates) 能够提供更多用于存储字符串数据的选项。为啥这些名称如何都以 String 或 Str 结尾?因为它们指的是领有和借用的变体,就像 String 和 str 类型一样。例如,这些字符串类型能够用不同的编码存储文本,或以不同的形式在内存中示意。
创立一个新的字符串
fn main() {let mut s = String::new(); // 创立一个新的空字符串名为 s,之后就能够应用 s。}
通常,咱们会应用一些初始数据作为字符串的结尾。为此,咱们应用 to_string 办法,该办法可在实现 Display 特色的任何类型上应用,就像字符串文字一样。如下所示两个成果完全相同的示例:
// 此代码创立一个蕴含初始内容的字符串。fn main() {
let data = "初始内容";
let s = data.to_string();
// 该办法也能够间接解决文字:let s = "初始内容".to_string();}
咱们还能够应用函数 String::from 从字符串文字创立字符串。成果等同于应用 to_string:
fn main() {let s = String::from("初始内容");
}
认为字符串是 UTF- 8 编码的,所以咱们能够在其中蕴含任何正确编码的数据(不论它长啥样),如下所示:
fn main() {let hello = String::from("你好");
let hello = String::from("Hello");
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
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");
}
更新字符串
如果将更多数据推入字符串中,则字符串的大小会减少,其内容也会发生变化,就像 Vec <T> 的内容一样。另外,咱们能够不便地应用 + 运算符或格局!
应用 push_str 和 push 附加到字符串
fn main() {let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {}", s2);
}
// 如果运行的话能够失常打印出后果:s2 is bar。所以 push_str 办法不会获得所有权
push 办法将单个字符作为参数,并将其增加到 String。如下示例显示了应用 push 办法将字母 l 增加到 String 的代码:
fn main() {let mut s = String::from("lo");
s.push('l');
}
留神应用 push 办法的时候必须应用单引号,如果应用了双引号的话会呈现谬误:
|
4 | s1.push(“l”);
| ^^^ expected `char`, found `&str`
与 + 运算符或格局串联——宏
通常,须要合并两个现有字符串。一种办法是应用 + 运算符,下所示:
fn main() {let s1 = String::from("Hello,");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 留神 s1 已移至此处,无奈在之后应用
println!("s1 + &s2 = {}", s3);
}
D:\learn\cargo_learn>cargo run
Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
Finished dev [unoptimized + debuginfo] target(s) in 0.60s
Running `target\debug\cargo_learn.exe`
s1 + &s2 = Hello, world!
+ 运算符应用 add 办法,其签名如下所示:
fn add(self, s: &str) -> String {
通过上例咱们能够看到咱们能够应用 + 号运算获取一个新的字符串,然而如果加号比拟多的状况下会很难看到产生了什么。对于更简单的字符串组合,咱们能够应用format!
宏:
fn main() {let s1 = String::from("你");
let s2 = String::from("好");
let s3 = String::from("啊!");
let s = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s1);
println!("{}", s)
}
D:\learn\cargo_learn>cargo run
Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
Running `target\debug\cargo_learn.exe`
你
你 - 好 - 啊!
能够看到应用
format!
只是参考借阅变量,并不会获取变量的所有权
索引到字符串
咱们是否能够像 js 中获取 js 某一个片段的形式一样在 rust 中应用呢,比方:
fn main() {let s1 = String::from("hello");
let h = s1[0];
println!("{}", h);
}
答案是不能够的:
D:\learn\cargo_learn>cargo run
Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
error[E0277]: the type `std::string::String` cannot be indexed by `{integer}`
--> src\main.rs:3:13
|
3 | let h = s1[0];
| ^^^^^ `std::string::String` cannot be indexed by `{integer}`
|
= help: the trait `std::ops::Index<{integer}>` is not implemented for `std::string::String`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
error: could not compile `cargo_learn`.
To learn more, run the command again with --verbose.
为什么不能够?先来看看 Rust 是如何在内存中存储字符串的。
外部代表
字符串是 Vec<u8> 的包装。先看一下通过正确编码的 UTF- 8 示例字符串:let hello = String::from("Hola");
在这种状况下,len 将为 4,这意味着存储字符串“”Hola 的向量的长度为 4 个字节。以 UTF- 8 编码时,每个字母占用 1 个字节。但要是这样呢?let hello = String::from("Здравствуйте"); //(请留神,此字符串以西里尔字母大写 Ze 结尾,而不是阿拉伯数字 3。)
当询问该字符串有多长时,咱们可能会说 12。然而,Rust 的答案是 24:这是在 UTF- 8 中编码“Здравствуйте”所需的字节数,因为该字符串中的每个 Unicode 标量值都占用 2 个字节的存储空间。
因而,字符串字节的索引并不总是与无效的 Unicode 标量值相干。因而为了演示,看看以下有效的 Rust 代码:
let hello = "Здравствуйте";
let answer = &hello[0];
答案的价值是什么?应该是З,第一个字母吗?当以 UTF- 8 编码时,З的第一个字节为 208,第二个字节为 151,因而答案实际上应为 208,但 208 自身不是无效字符。如果用户要求输出此字符串的第一个字母,则返回 208 可能不是用户想要的。然而,这是 Rust 领有字节索引 0 的惟一数据。即便字符串仅蕴含拉丁字母,用户也通常不心愿返回字节值:如果 &”hello”[0]是返回字节值的无效代码,它将返回 104,而不是 h。为了防止返回意外的值并导致可能不会立刻发现的谬误,Rust 齐全不编译该代码,并防止了开发过程晚期的误会。
字节 (bytes) 和标量值 (scalar values) 以及字素簇(grapheme clusters)!
对于 UTF- 8 的另一点是,从 Rust 的角度来看,实际上有三种相干的形式来查看字符串:字节,标量值和字素簇(最靠近字母的货色)。
如果咱们看一下用梵文脚本编写的北印度语单词“नमस्ते”,它会存储为 u8 值的向量,如下所示:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,224, 165, 135]
那是 18 字节,这是计算机最终存储此数据的形式。如果咱们将它们视为 Unicode 标量值(即 Rust 的 char 类型),则这些字节如下所示:['न', 'म', 'स', '्', 'त', 'े']
这里有六个 char 值,然而第四个和第六个不是字母:它们是变音符号,它们本人没有意义。最初,如果咱们将它们视为字素簇,咱们将失去一个人所说的组成印地语单词的四个字母:["न", "म", "स्", "ते"]
Rust 提供了不同的形式来解释计算机存储的原始字符串数据,以便每个程序都能够抉择所需的解释形式,而与数据所用的语言无关。
Rust 不容许咱们索引到 String 中以获取字符的最初一个起因是索引操作总是须要恒定的工夫(O(1))。然而用 String 不能保障性能,因为 Rust 必须从头到尾遍历所有内容以确定有多少个无效字符。
切片字符串
将字符串索引化通常是一个坏主意,因为不分明字符串索引操作的返回类型应该是什么:字节值,字符,字形簇或字符串切片。因而,Rust 要求更具体地说明是否真的须要应用索引来创立字符串切片。为了更具体地批示索引并批示要应用字符串切片,而不是应用带单个数字的 [] 进行索引,能够将 [] 与范畴联合应用以创立蕴含特定字节的字符串切片:
#![allow(unused_variables)]
fn main() {
let hello = "Здравствуйте";
let s = &hello[0..4];
println!("{}",s);
}
D:\learn\cargo_learn>cargo run
Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
Running `target\debug\cargo_learn.exe`
Зд
遍历字符串的办法
侥幸的是,您能够通过其余形式拜访字符串中的元素。
如果您须要对单个 Unicode 标量值执行操作,最好的办法是应用 chars 办法。在“नमस्ते”上调用 char 会拆散出并返回 char 类型的六个值,您能够遍历后果以拜访每个元素:
#![allow(unused_variables)]
fn main() {for c in "नमस्ते".chars() {println!("{}", c);
}
}
D:\learn\cargo_learn>cargo run
Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
Finished dev [unoptimized + debuginfo] target(s) in 0.52s
Running `target\debug\cargo_learn.exe`
न
म
स
्
त
े
bytes 办法返回每个原始字节,这可能适宜咱们的初衷:
#![allow(unused_variables)]
fn main() {for b in "नमस्ते".bytes() {println!("{}", b);
}
}
这段代码将打印出组成此字符串的 18 个字节:
224
164
// --snip--
165
135
无效的 Unicode 标量值可能由 1 个以上的字节组成。
从字符串获取字素簇很简单,因而 rust 规范库不提供此性能。然而咱们能够在 crates.io 上获取应用
字符串不是那么简略
总而言之,字符串很简单。Rust 抉择将正确处理 String 数据作为所有 Rust 程序的默认行为,这意味着咱们必须在解决 UTF- 8 数据方面投入更多精力,尽管这种折衷会带来更多的字符串复杂性,然而这能够避免在开发生命周期的前期不得不解决波及非 ASCII 字符的谬误。