当你执行s [n],其中s是一个字符串时,为什么必须遍历字符串才能找到字符串的nᵗʰ字母。 (根据https://doc.rust-lang.org/book/strings.html)
根据我的理解,字符串是一个字符数组,一个字符串是一个4字节或4字节的数组。所以第n个字母就像这样做:v [4 * n..4 * n + 4]其中v是向量?
v [i..j]的费用是多少?
我认为v [i..j]的成本是j-i,所以s [n]的成本应该是4。
答案 0 :(得分:6)
注意:第二版The Rust Programming Language对Strings in Rust进行了改进和流畅的解释,您可能也希望阅读。下面的答案虽然仍然准确,但引用了本书的第一版。
我将尝试通过引用本书(https://doc.rust-lang.org/book/strings.html)来澄清关于Rust中字符串的这些误解。
'string'是一系列Unicode标量值,编码为UTF-8字节流。保证所有字符串都是UTF-8序列的有效编码。
考虑到这一点,加上UTF-8代码点的大小可变(1到4个字节,具体取决于字符),Rust中的所有字符串,无论是&str
还是String
,都是不是字符数组,也不能这样对待。它进一步解释了切片的原因:
因为字符串是有效的UTF-8,所以它们不支持索引:
let s = "hello"; println!("The first letter of s is {}", s[0]); // ERROR!!!
通常,使用[]访问矢量非常快。但是,因为UTF-8编码字符串中的每个字符都可以是多个字节,所以必须遍历字符串才能找到字符串的nᵗʰ字母。这是一项非常昂贵的操作,我们不想误导。
与问题中提到的不同,不能做s[n]
,因为虽然理论上这将允许我们在恒定时间内获取第n个字节,但该字节不能保证自己有意义。
v [i..j]的费用是多少?
切片的成本实际上是不变的,因为它是在字节级完成的:
您可以使用切片语法获取切片:
let dog = "hachiko"; let hachi = &dog[0..5];
但请注意,这些是字节偏移,而不是字符偏移。所以这会在运行时失败:
let dog = "忠犬ハチ公"; let hachi = &dog[0..2];
出现此错误:
线程''在'{1}}的'索引0和/或2'惊慌失措 字符边界'
基本上,切片是可以接受的,并且会产生该字符串的新视图,因此不会制作副本。但是,只有当您完全确定偏移在字符边界方面是正确的时,才应该使用它。
为了迭代字符串的每个字符,您可以改为调用忠犬ハチ公
:
chars()
即使考虑到这一点,请注意,如果您希望处理UTF-8中的字符修饰符(它们本身是标量值但不应单独处理),处理Unicode字符可能不是您想要的。现在引用str
API:
let c = s.chars().nth(n);
返回字符串切片的字符上的迭代器。
由于字符串切片由有效的UTF-8组成,我们可以通过char迭代字符串切片。此方法返回这样的迭代器。
重要的是要记住char表示Unicode标量值,并且可能与您对“字符”的概念不符。对字形集群的迭代可能就是你真正想要的。
请记住,字符可能与人类对人物的直觉不符:
fn chars(&self) -> Chars
unicode_segmentation包提供了一种定义字形簇边界的方法:
let y = "y̆";
let mut chars = y.chars();
assert_eq!(Some('y'), chars.next()); // not 'y̆'
assert_eq!(Some('\u{0306}'), chars.next());
assert_eq!(None, chars.next());
答案 1 :(得分:4)
如果您确实希望将字符串视为代码点数组(与字符不完全相同;有组合标记,表情符号和单独的肤色修饰符等),您可以将其收集到Vec
:
fn main() {
let s = "£10 !";
for (i,c) in s.char_indices() {
println!("{} {}", i, c);
}
let v: Vec<char> = s.chars().collect();
println!("v[5] = {}", v[5]);
}
通过一些不同字符宽度的奖金演示,输出:
0 £
2 1
3 0
4
5
9 !
v[5] = !