在上一篇文章中介绍了Rust字符串中的&str(切片),这篇文章将介绍String类型。Rust中的String类型比其他语言的String更加复杂。
Rust中的String类型
通常说的字符串是哪一个?
Rust中所说的字符串为&str和String,而不是其中的一种
其他类型的字符串
Rust的标准库还包含了很多其他的字符串类型,如:OsString、OsStr、CString、Cstr
上面的类型有String结尾的,有Str结尾的,这两者的区别在于String结尾的为可获得所有权的类型,而Str结尾的为可借用的。
此外,某些Libaray create(第三方库)针对存储字符串可提供更多的选项
创建一个新的字符串(String)
- 很多Vec\<T>的操作都可作用于String
- String::new()函数
let mut s = String::new();
通常情况下,我们声明一个字符串都是带有初始值的。我们可以使用to_string()
方法,可用于实现了Display trait的类型,包括字符串字面值
let data = "this is a data";
let s = data.to_string();
let s1 = "ssss ss".to_string();
此外,我们还可以使用String::from()函数来创建带初始值的字符串:
let s = String::from("ssss yige sss");
更新String
- push_str()方法:把一个字符串切片附加到String:
let mut s = String::from("foo");
s.push_str("string is pushed");
println!("{}", s); //foostring is pushed
let mut s1 = "first ".to_string();
let s2 = String::from("ss");
s1.push_str(&s2);
println!("{}", s2); //ss
我们查看push_str
的签名:pub fn push_str(&mut self, string: &str)
传入的是一个引用,所以我们将s2传入s1后任然可以使用。
- push()方法:把单个字符附加到String:
let mut x = "sss".to_string();
x.push('c');
println!("{}", x); //sssc
- 使用
+
拼接字符串(前面为String类型,后面为一个&str)
let s1 = String::from("aa");
let s2 = String::from("bb");
let s3 = s1 + &s2;
println!("{}", s3); //aabb
//println!("{}", s1); //报错
println!("{}", s2); //bb
其中,s2为String类型,而&s2类型为&str,这是因为Rust会使用解引用进行类型的强制转换(deref coercion)
+
类似于这个函数签名:add(self,s:&str)->String
如果我们连接多个字符串,使用+
要这样写
let s1 = String::from("aa");
let s2 = String::from("bb");
let s3 = String::from("cc");
let s4 = s1 + "-" + &s2 + "-" + &s3;
println!("{}", s4); //aa-bb-cc
但我们有更灵活快速的方法:
format!
宏:连接多个字符串:
let s1 = String::from("aa");
let s2 = String::from("bb");
let s3 = String::from("cc");
let s4 = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s4); //aa-bb-cc
println!("{}", s1); //aa
println!("{}", s2); //bb
println!("{}", s3); //cc
我们可以发现format!
与println!
有些相似,不过前者是返回一个String类型的值,而后者为打印值。并且,重要的是,format!
不会获取任何变量的所有权。
对String按照索引的形式进行访问
按索引语法访问String的某部分,会报错:
let s1 = String::from("aa");
s1[1]; //the type String cannot be indexed by {integer}
所以,rust不支持索引语法访问String
内部表示
string是对Vec\<u8>的包装
- len()方法
let s1 = String::from("aabbcc");
let len = s1.len();
println!("{}", len); //6
但并不是说len()方法的返回值为字符串的长度:
let s1 = String::from("Этострока"); //9个字符(俄文)
let len = s1.len();
println!("{}", len); //18
let s2 = String::from("哈哈哈哈"); //4个字符
let len1 = s2.len();
println!("{}", len1); //12
在俄语中Unicode标量值占两字节,中文占3个。
所以说如果使用索引,不一定能取到一个合法的字符,比如Э
,它的Unicode标量值为:208,151。如果能够使用索引访问字符串,s1[0]将会取到208,这并不是一个合法的字符(就算合法,也不是用户所想要的),所以Rust直接不允许这种操作。
字节、标量值、字形簇(Bytes,Scalar Values,Grapheme Clusters)
Rust有三种看待字符串的方式:
- 字节
- 标量值
- 字形簇(最接近所谓的“字母”)
我们现在来使用对应的方法来对String进行遍历
字节:
let s1 = String::from("Это");
for b in s1.bytes() {
println!("{}", b);
}
//208
// 173
// 209
// 130
// 208
// 190
Unicode标量值:
let s1 = String::from("Это");
for c in s1.chars() {
println!("{}", c);
}
// Э
// т
// о
字形簇(最接近所谓的“字母”),实现比较复杂,标准库中没有提供这个功能,可以安装第三方库。
Rust不允许对String类型进行索引的最后一个原因:
- 索引操作应消耗一个常量时间(O(1))
- 而String无法保证:需要遍历所有内容,来确定有多少个合法的字符
切割String
可以使用[]
和一个范围来创建字符串的切片:
let s1 = String::from("Этострока");
let s = &s1[4..8];//参数为字节的位置
println!("{}", s); //ос
- 必须谨慎使用
- 如果切割时跨越了字符边界,程序就会panic:
(b1,b2),(b3, b4),(b5,b6),(b7,b8)如果从b3,b4之间切割,程序将会panic
Rust中的String并不简单
Rust选择将正确处理String数据作为所有Rust程序的默认行为,程序员必须在处理UTF-8数据之前投入更多的精力
不过这样的好处也是明显的:可防止在开发后期处理涉及非ASCII字符的错误。