之前看过对rust string的吐槽有点多,今天看了一下果然比较复杂,说说一个初学者的看法。
一般rust里面的字符串实际上有两种类型,第一种就是我们常见的不固定string,也就是我们使用String::from()这样的方式构建的一个类型,第二种类型是&str,实际上我们string类型的一个切片。
官网上给出了下面这样的例子:
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
如果在编辑器里面去看这个hello和world类型可以明确的看到,这两个都是&str的类型
我们可以注意到这个&str实际上带“&”指针符号的,而str又是string,这让我想起了前面学习过的reference的概念,string类型在作为参数进入函数之后,其所有权应该是被消耗掉的,&str类型作为一种reference,应该是会保留所有权的,因此我搞了个试验,如下
comsume_str(hello);
comsume_str(hello);
fn comsume_str(s: &str) {
println!("{}", s)
}
这个comsume_str确实是能执行两次的,确实证实了上面的猜想。当然我在这个过程中遇到了一个小问题,我这边记录一下
fn main(){
let s11 = String::from("hello world");
let hello = &s11[0..5];
let world = &s11[6..11];
comsume_string(s11); //在这里产生了一个报错
comsume_str(hello);
comsume_str(hello);
}
fn comsume_string(s: String) {
println!("{}", s)
}
fn comsume_str(s: &str) {
println!("{}", s)
}
我想了一下原因,如果我在错误发生的这一行消耗掉s11的所有权,下面关于hello的reference也就没有了意义,于是我改了一下循序
fn main(){
let s11 = String::from("hello world");
let hello = &s11[0..5];
let world = &s11[6..11];
comsume_str(hello);
comsume_str(hello);
comsume_string(s11); //放到了这个位置
}
fn comsume_string(s: String) {
println!("{}", s)
}
fn comsume_str(s: &str) {
println!("{}", s)
}
换了位置之后果然不报错了,rust果然很严谨啊。
话题越说越远了,接下来还是要看看再第八章之中对string类型的拓展
第一部分是string类型初始化
这里新给了一个new函数去初始化一个字符串
//new初始化一个空字符串
let mut s = String::new();
//to_string方法
s = "hello world".to_string();
教程上显示拥有display特征的都可以调用to_string,这里的tostring把一个$str变成了string并放入到我们可变的s空字符串中
第二部分是字符串的添加,或者说是拼接
首先是push_str的方式,这个方法的参数应该是一个&str,当然注意我们self,也就是这个s必须是可变的
s.push_str("string");
然后是push的方式,这个添加是char类型,依然要求我们的self是可变的
s.push('l');
使用+的方式进行拼接,这个就有点复杂了,这里的+并不是简单的+,而是一个这样的方法fn add(self, s: &str) -> String
let s3 = String::from("huangfeng");
let s4 = s2 + &s3;
println!("s4的值是:{}", s4); // 真是有点巧妙啊
也就是意味着+运算的顺序是不能变的,始终是string+&str
最后给了一个format的方式让你并且较长的字符串
let s5 = format!("{} vs {}", s4, "hoih");
println!("s5的值是:{}", s5);
这里我们看到了format后面有一个!符号,也就是和println!是一样的,暂时我的理解是,这并非是函数,因为其不消耗所有权(应该是吧,错误的话请指正),教程上有个专业名词来着,但是我忘了。
接下来教程讨论是字符串的难以切割的问题
实际上指的是难以通过&string[0]这样索引号方式获取的问题,因为每个字符所占的字节不一样多,但是还是可以通过这样的方式获取
let s6 = String::from("黄烽无敌");
// let c1 = &s6[3];因为字符所占的字节不一样,所以这边不是好切割
// 但是可以通过这样的方式拿取
let first = &s6[0..3];
println!("我的姓是{}", first);
let s7 = String::from("he");
// 现在我想拿到第一个字符
let first_char = &s7[0..1];
println!("第一个字母是{}", first_char);
也就是可以通过制定range方式来获取(比如中文占3个字节,我就拿取0-2),官网说这个first_char获取会报错,但是我这边是能拿到的。
最后一部分是两个遍历,分别是字节的遍历和字符rune的遍历
for i in s6.as_bytes() {
println!("{}", i);
}
for i in s7.chars() {
println!("{}", i);
}
没啥好说的,按照教程一步步来吧。