字符串
字符串是由字符组成的连续集合。
在 Rust 中,字符类型即 char 用 Unicode 表示,因此每个字符占据 4 个字节内存空间。而字符串则是使用 UTF-8 进行编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。
在 UTF-8 编码中,英文字符通常只占 1 字节,汉字通常占 3 字节,特殊的 Unicode 字符可能占 4 字节。
字符串字面量
字面量(Literal)是程序中直接表示固定值的数据。
s
是被硬编码进程序里的字符串值,该值不可变。
Rust 提供了动态字符串类型 String
,该类型被分配到堆上,因此可以动态伸缩。可以使用下面的方法基于字符串字面量来创建 String
类型:
::
是一种调用操作符,这里表示调用 String
类型中的 from
关联函数。
切片
字符串字面量是切片。
**切片(Slice)**是一个对数组或向量的一部分的引用,对于字符串而言,切片就是对 String
类型中某一部分的引用。切片允许你借用数组或向量的一部分,而不需要复制数据。
我们可以使用 ..
range 序列简化字符串操作
字符串切片的类型标识是 &str
,因此我们可以这样声明一个函数,输入 String
类型,返回它的切片:fn first_word(s: &String) -> &str
。
字符串
Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,也就是字符串切片。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型。
str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,当 Rust 用户提到字符串时,往往指的就是 String 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码。
String 与 &str 的转换
-
&str → String
-
String → &str
字符串没有索引
在 Rust 中,字符串的底层存储实现是通过一个字节数组(类型为 [u8]
,即无符号 8 位整数数组)来完成的。例如,“你好”,它的底层存储是:[0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd]
。
因此在 Rust 中无法通过索引的方式访问字符串的某个字符或者子串。
切片的索引是通过字节来进行,但是字符串又是 UTF-8 编码,因此你无法保证索引的字节刚好落在字符的边界上。在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界,例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:
因为我们只取 s
字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 中
字都取不完整,此时程序会直接崩溃退出,如果改成 &s[0..3]
,则可以正常通过编译。 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点。
操作字符串
-
追加
这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut
关键字修饰。
-
插入
与追加一样,不会返回新的字符串,会修改原来的字符串。
-
替换
-
删除
这四个方法仅适用于 String 类型。
-
连接
使用 +
或者 +=
连接字符串。
右边的参数必须为字符串的切片引用类型。其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 + 时,必须传递切片引用类型。不能直接传递 String 类型。+ 是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰。
使用 format!
连接字符串。
format!
这种方式适用于 String
和 &str
。
字符串转义
通过转义的方式 \
输出 ASCII 和 Unicode 字符。
在某些情况下,可能你会希望保持字符串的原样,不要转义
元组(tuple)
Rust的元组(tuple)是一个可以存储多个不同类型的值的集合,它是一个固定大小的有序集合。
元组中的元素可以是不同类型,可以包含基本类型、结构体、数组甚至是其他元组等。元组不像数组那样所有元素必须是相同类型。
结构体(struct)
Rust中的结构体(struct)是用户定义的数据类型,用于将多个相关的数据项组合在一起。这些数据项称为字段(field),结构体的字段可以是同一类型,也可以是不同类型。
结构体非常适合表示具有不同属性的数据对象,例如一个 Person
,它可能有 name
、age
和 address
等不同字段。
基本操作
定义结构体
创建实例
结构体实例化时需要提供所有字段的值。字段名必须与定义时一致,除非使用 ..
来指定其余字段。
可以直接使用缩略的方式。
访问结构体
如果要修改则必须要将结构体实例声明为可变的,才能修改其中的字段。
也可以使用模式匹配,用于从 person 变量中解构出 name 和 age 字段。
结构体中所有权转移
-
直接赋值:将一个结构体赋值给另一个变量时,所有权会转移。原来的变量将不再拥有该结构体。
-
函数传递参数:当结构体作为函数参数传递时,默认情况下会发生所有权转移(如果不是通过引用传递)。
元组结构体(Tuple Struct)
结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体。
单元结构体(Unit-like Struct)
在 Rust 中,单元结构体是一种没有任何字段的结构体。它类似于元组中没有任何元素的单元(()
),因此也被称为“无字段结构体”或“空结构体”。
打印结构体
在需要打印的结构体定义前添加 #[derive(Debug)]
。
枚举(enum)
枚举(enum 或 enumeration)允许你通过列举可能的成员来定义一个枚举类型。枚举类型是一个类型,它会包含所有可能的枚举成员,而枚举值是该类型中的具体某个成员的实例。
枚举的每个选项被称为变体。每个变体可以是一个简单的标记(如 Direction::Up
)。
通过 :: 操作符来访问 Direction 下的具体成员。
枚举的变体不仅仅是“标签”,它们还可以携带数据。每个变体可以有不同的数据类型和数量。
Option 枚举
Rust 标准库中提供了一个非常常用的枚举 Option<T>
,用于表示一个可能存在或者不存在的值。Option 枚举有两个变体:Some(T) 表示一个值,None 表示没有值。
数组
在 Rust 中,最常用的数组有两种,第一种是速度很快但是长度固定的 array,第二种是可动态增长的但是有性能损耗的 Vector。