一、前言
前面的章节介绍了Rust中的很多基本基本数据结构,并且常常用到String
这个类型,因为它与Rust中的一个核心概念有关:所有权。
所以前面一直没有对它进行解答,而本文的目的,就是深究这个String
的底层原理,并以此作为媒介,引出所有权的概念。
二、字符串浅析
从比较宏观的角度去看你经常接触到的软件可以发现,对于控制台程序,你看到的只有字符。
而对于有图形界面的软件(GUI),除了图案、就是各种文字了(字符串)。
从这你便能看出字符串的重要性:无论什么程序,基本都离不开字符串的使用。
在rust中使用字符串,可以用以下代码:
fn main() {
let s=String::from("hello world");
println!("{}", s);
}
方式是用String
这个crate(箱子、包)
中的from
函数,来从一个字面量hello world
得到一个字符串String
类型:
那看到这里你可能觉得有些奇怪了,明明我直接等于一个字符串字面量也可以呀!
比如像下面这样:
fn main() {
let s="hello world";
println!("{}", s);
}
这确实是没问题的,但如果你看到编译器自动推导的类型就会发现,此刻的s
已经不是String
类型了
是不是觉得很奇怪!这里的是&'static str
类型,并不是字符串String
类型。
这里我们先不管这两个类型之间的区别,仅从现象上看就能说明,字符串字面量的类型就是&' static str
因为编译器推断s
的类型是通过右边的字面量来推断的,既然是s
是&' static str
类型,那么右边的字面量肯定也是&'static str
类型才对。
那么就此可以推断出,String::from
这个函数的作用就是将一个字符串字面量&'static str
类型转换为String
类型。
因为经由这个函数之后,编译器就推断出s
为String
类型了。
注意这个'static
的前缀,它实际上并不是类型、而是生命周期标注,static意味着它是静态的、全局可用的,所以其更简洁的类型表示实际上是&str
。
那么这两种类型有什么区别呢?
目前来看,最直观的区别就是:String
类型是可变的,而&str
类型是不可变的。
比如下面的代码:
fn main() {
let mut s=String::from("hello world");
s=s+"world"; //正确
println!("{}", s);
let mut s="hello world"; //用到变量隐藏特性
s=s+"test"; //错误
println!("{}", s);
}
即使你添加了mut
关键字,对于&str
类型,仍然是不可变的。
对于字符串的+
运算符,会将后面的字符串追加到第一个字符串并返回,比如s+"test"
,就是尝试将"test"
追加到s
之后、并返回拼接的结果,如果s
不可变,那么就会拼接失败。
原因在于mut
指代的是s
本身可变,比如你完全可以让s
等于一个新的字符串:
let mut s="hello";
s="world"; //s变量本身可变,所以可以重新赋值为新的字符串。
s+"world"; //+运算符的底层逻辑是在s所指向的字符串后拼接、追加新的字符串,由于字符串s的类型为&str,不可变,所以失败
下面我们就要开始分别深度探讨两个几个问题:
String
类型与&str
类型到底代表什么意思?- 为什么两者会有这个特性?
只要搞懂了这两个问题,你对于rust的设计理念认知便又进了一大步,这对于之后的学习是很有帮助的。
因为除了字符串之外,其它很多地方都会用到这个特性,这并非是字符串所独有的。
三、&str与String
首先看到第一个问题: String
类型与&str
类型到底代表什么意思?
我们要知道&
这个符号是可以剥离出来的,实际上应该是:String
与str
。
如何你对C++熟悉的话,那么可以认为&
基本就等价于C++中的引用。
其中String
代表一个可独立操作的字符串,也就是说,它拥有一个字符串的所有权力,比如下面这段代码:
let mut s=String::from("hello world");
此时变量s
就完全拥有了操作这个字符串"hello world"
的权利,增、删、改、查均可以,一旦你这个变量s
没有了,那么"hello world"
这个字符串同样也会在消失,两者是彻底绑定在一起的。
而str
这个类型,其意义仅在于你可以用它,比如打印,遍历,查看等等,除此之外,它对字符串本身根本做不了任何改变,因为它不拥有这个字符串。
let mut a="hello world";
比如上面这段代码,即使a
变量消失了,这个字符串"hello world"
实际上仍然在内存中,并没有消失,因为a
并不拥有这个字符串。
所以如果你想要修改一个str
类型的变量,你就必须先将其转换为String
类型
let mut a="hello world";
let mut s=a.to_string();