strings.md
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
對于每一個(gè)程序,字符串都是需要掌握的重要內(nèi)容。由于Rust主要著眼于系統(tǒng)編程,所以它的字符串處理系統(tǒng)與其它語言有些許區(qū)別。每當(dāng)你碰到一個(gè)可變大小的數(shù)據(jù)結(jié)構(gòu)時(shí),情況都會變得很微妙,而字符串正是可變大小的數(shù)據(jù)結(jié)構(gòu)。這也就是說,Rust的字符串與一些像C這樣的系統(tǒng)編程語言也不相同。
讓我們進(jìn)一步了解一下。一個(gè)字符串是一串UTF-8字節(jié)編碼的Unicode量級值的序列。所有的字符串都確保是有效編碼的UTF-8序列。另外,字符串并不以null結(jié)尾并且可以包含null字節(jié)。
Rust有兩種主要的字符串類型:&str
和String
。讓我們先看看&str
。這叫做字符串片段(string slices)。字符串常量是&'static str
類型的:
let greeting = "Hello there."; // greeting: &'static str
"Hello there."
是一個(gè)字符串常量而它的類型是&'static str
。字符串常量是靜態(tài)分配的字符串切片,也就是說它儲存在我們編譯好的程序中,并且整個(gè)程序的運(yùn)行過程中一直存在。這個(gè)greeting
綁定了一個(gè)靜態(tài)分配的字符串的引用。任何接受一個(gè)字符串切片的函數(shù)也接受一個(gè)字符串常量。
字符串常量可以跨多行。有兩種形式。第一種會包含新行符和之前的空格:
let s = "foo
bar";
assert_eq!("foo\n bar", s);
第二種,帶有\
,會去掉空格和新行符:
let s = "foo\
bar";
assert_eq!("foobar", s);
Rust 當(dāng)然不僅僅只有&str
。一個(gè)String
,是一個(gè)在堆上分配的字符串。這個(gè)字符串可以增長,并且也保證是UTF-8編碼的。String
通常通過一個(gè)字符串片段調(diào)用to_string
方法轉(zhuǎn)換而來。
let mut s = "Hello".to_string(); // mut s: String
println!("{}", s);
s.push_str(", world.");
println!("{}", s);
String
可以通過一個(gè)&
強(qiáng)制轉(zhuǎn)換為&str
:
fn takes_slice(slice: &str) {
println!("Got: {}", slice);
}
fn main() {
let s = "Hello".to_string();
takes_slice(&s);
}
這種強(qiáng)制轉(zhuǎn)換并不發(fā)生在接受&str
的trait而不是&str
本身作為參數(shù)的函數(shù)上。例如,TcpStream::connect,有一個(gè)ToSocketAddrs
類型的參數(shù)。&str
可以不用轉(zhuǎn)換不過String
必須使用&*
顯式轉(zhuǎn)換。
use std::net::TcpStream;
TcpStream::connect("192.168.0.1:3000"); // &str parameter
let addr_string = "192.168.0.1:3000".to_string();
TcpStream::connect(&*addr_string); // convert addr_string to &str
把String
轉(zhuǎn)換為&str
的代價(jià)很小,不過從&str
轉(zhuǎn)換到String
涉及到分配內(nèi)存。除非必要,沒有理由這樣做!
因?yàn)樽址怯行TF-8編碼的,它不支持索引:
let s = "hello";
println!("The first letter of s is {}", s[0]); // ERROR!!!
通常,用[]
訪問一個(gè)數(shù)組是非??斓摹2贿^,字符串中每個(gè)UTF-8編碼的字符可以是多個(gè)字節(jié),你必須遍歷字符串來找到字符串的第N個(gè)字符。這個(gè)操作的代價(jià)相當(dāng)高,而且我們不想誤導(dǎo)讀者。更進(jìn)一步來講,Unicode實(shí)際上并沒有定義什么“字符”。我們可以選擇把字符串看作一個(gè)串獨(dú)立的字節(jié),或者代碼點(diǎn)(codepoints):
let hachiko = "忠犬ハチ公";
for b in hachiko.as_bytes() {
print!("{}, ", b);
}
println!("");
for c in hachiko.chars() {
print!("{}, ", c);
}
println!("");
這會打印出:
229, 191, 160, 231, 138, 172, 227, 131, 143, 227, 131, 129, 229, 133, 172,
忠, 犬, ハ, チ, 公,
如你所見,這有比char
更多的字節(jié)。
你可以這樣來獲取跟索引相似的東西:
# let hachiko = "忠犬ハチ公";
let dog = hachiko.chars().nth(1); // kinda like hachiko[1]
這強(qiáng)調(diào)了我們不得不遍歷整個(gè)char
的列表。
你可以使用切片語法來獲取一個(gè)字符串的切片:
let dog = "hachiko";
let hachi = &dog[0..5];
注意這里是字節(jié)偏移,而不是字符偏移。所以如下代碼在運(yùn)行時(shí)會失?。?/p>
let dog = "忠犬ハチ公";
let hachi = &dog[0..2];
給出如下錯(cuò)誤:
thread '<main>' panicked at 'index 0 and/or 2 in `忠犬ハチ公` do not lie on
character boundary'
如果你有一個(gè)String
,你可以在它后面接上一個(gè)&str
:
let hello = "Hello ".to_string();
let world = "world!";
let hello_world = hello + world;
不過如果你有兩個(gè)String
,你需要一個(gè)&
:
let hello = "Hello ".to_string();
let world = "world!".to_string();
let hello_world = hello + &world;
這是因?yàn)?code>&String可以自動轉(zhuǎn)換為一個(gè)&str
。這個(gè)功能叫做[Deref
轉(zhuǎn)換](Deref
coercions Deref
強(qiáng)制多態(tài).md)。