前言#
昨天学完了vector
今天该学String了
String#
这玩意儿应该算是我们用的最多的类型了,但是这玩意儿居然是一个集合collection,是一堆UTF-8字符char的集合?
实际上并不是,rust开发者将String定义为一堆bytes字节的集合。
rust的核心代码里是没有String的,只有字符串切片str,基本上都是&str,它是一些存储在某些地方的UTF-8字符char数据的引用。比如你声明的字符串字面量
let a = "test";而String上面文章说过了,是标准库里的类型。和字符串切片不同的是,它可变、拥有所有权。
有一点需要强调的一点是,不管是str还是String,它们都是基于UTF-8编码的。如果使用超出UTF-8范围的符号就会有问题。
另外,说一下rust中String为啥是一个集合。它其实是一个wrapper包裹着一个vector,然后再加点限制、功能等。而这个vector是一个u8类型的vector。
创建字符串#
既然是包裹的vector,那么自然可以用和vector的new关联函数。
相信大家都很熟悉了,我们直接看例子吧
let mut s = String::new();new是String的关联函数,返回一个String实例。
我们也习惯这么写了,如果有初始数据的话基本都是用下面这种
let mut s = String::from("test");但其实,我们也可以用字符串切片来实现,这是之前从来没用过的。

使用to_string方法会返回一个字符串类型,并且拥有了所有权。
两个都可用,看你自己的习惯。
更新字符串#
我们之前好像有用到过一个方法push_str的方法,来看下例子

push_str需要的参数类型是字符串切片。
或者我们可以用push添加char给这个字符串

但这两种方式未免会有些麻烦,如果可以和别的于艳一样使用+直接加起来那该多好。
rust确实有这种方式,但是做了一定的限制。来看下例子

两个字符串类型相加会直接报错,需要调整为下面这种

第二个参数相加的变量得是字符串切片,而第一个参数必须要是字符串类型(拥有所有权)。

为什么会这样呢?因为+操作符实际上会被转换为类似add的方法

可以看到第一个参数接受的是自己并且是拥有所有权,这也就意味着s1在add完之后就失效了。
第二个参数则是一个字符串切片,但是我们明明用的是一个&String的参数。
这也就是我为什么要说是类似add方法了,还是有差别的。


可以看到使用&str也是可行的。
为什么可以这样呢。。。实际上是编译器帮我们把&String强制转换成了&str。
另外,如果有多个字符串相加,那用+来写还是很麻烦,在js中,可以直接使用 ` ${}将多个字符串拼接到一起。
rust中虽然没这么方便,但是还是有差不多功能的,也就是format!,看到这个!大家应该都知道是一个macro了。来看下例子
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
}

通过下标获取字符串元素#
在js中,我们可以用过下标直接获取字符串的元素,但是在rust中这么做是不行的。

为什么不行呢?
我们来看下字符串内部表现[5]
前面说过了String实际上是Vec加了一层wrapper,里面的元素都是UTF-8编码的字符。
我们来看下两个例子
let hello = String::from("Hola"); 这个hello字符串的len长度是4,Hola每一个字符逗占一个byte。
let hello = String::from("Здравствуйте"); 来看这下俄语的长度,数了下应该是12,但实际上是24,为什么呢?因为一个俄语符号占两个byte,也就是双字节。
实际上中文也是双字节。
所以通过下标获取就会出问题,可能抓到的元素不是自己想要的。
为什么要用字节存储呢?我们再来看一个例子
“नमस्ते”这是一个印度语,看这个完全不知道有多长。。。。
而在电脑会把它们转换为
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]一个Vec。
试想一下,如果不是这个类型,那么就会被切割成这样
['न', 'म', 'स', '्', 'त', 'े'] 而这里面其实有俩符号并不是符合要求的,只是无意义语气符号,然后就会变成
["न", "म", "स्", "ते"]这样就错了。
所以rust会把他们会转换成u8类型,这样就能保留原始数据,到时候再转回来就不会有问题。
最后还有一个原因那就是时间复杂度,在rust中通过下标获取元素的时间复杂度为O(1)。但是这并不适用于字符串,因为字符串需要从头开始一直走到下标位置,为什么要这么做呢?因为并不确定有多少符号是正确有效的。
切割字符串(slicing Strings)#
如果你真有这个需求,那就用切片的方式,不过在这之前,你得知道你截取的是啥语言,比如
let hello = "Здравствуйте";
let s = &hello[0..4];前面说了,俄语符号占两个字节,所以这里截取的应该是Зд

如果你截取双字节符号是只截取了一半也就是一个字节,rust会直接崩溃

遍历字符串#
由于符号字节占位不同,所以我们并不能直接遍历,来看下例子
#![allow(unused)]
fn main() {
for c in "Зд".chars() {
println!("{}", c);
}
}chars会返回字符串的符号迭代器
这样打印出来的符号就是正常的。

当然,如果你想获取字节,你也可以直接用bytes方法
#![allow(unused)]
fn main() {
for b in "Зд".bytes() {
println!("{}", b);
}
}

总结#
相信看到这里,你也觉得rust里的字符串好麻烦,好复杂。。。但是这也是rust解决问题的一种方式吧,谁又能想到把字符串类型当做一个集合呢?
最后,如果觉得对你有帮助的话,请务必点个赞,谢谢~
参考#
- ^rust-String https://doc.rust-lang.org/book/ch08-02-strings.html#storing-utf-8-encoded-text-with-strings
- ^rust-create-String https://doc.rust-lang.org/book/ch08-02-strings.html#creating-a-new-string
- ^rust-updating-string https://doc.rust-lang.org/book/ch08-02-strings.html#updating-a-string
- ^rust-index-of-string-element https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings
- ^rust-inernal-representtation https://doc.rust-lang.org/book/ch08-02-strings.html#internal-representation
- ^rust-slicing-strings https://doc.rust-lang.org/book/ch08-02-strings.html#slicing-strings
- ^rust-interate-strings https://doc.rust-lang.org/book/ch08-02-strings.html#methods-for-iterating-over-strings
发布于 2022-12-10 18:37・IP 属地广东
