前言
在上一章当中,我们以变量与可变性作为切入点,正式敲开了 Rust 的语法学习之门,了解了在 Rust 当中如何定义变量、可变变量、常量,以及了解了 Rust 中的变量隐藏的概念以及它的使用场景。那么在这一章里,我们就来逐步学习一下 Rust 当中的各种数据类型。
Rust 是一门静态的语言,这就意味着,Rust 在编译的过程中,需要知道所有的变量的类型。而在大部分情况下,Rust 都可以基于变量使用的值推断出这个变量应该是什么类型的。但也有一些特殊情况,就拿我们之前开发的的 猜数游戏 小程序当中,我们将一个字符串转换成数字的方法 parse
来说,这个方法返回的类型可能是很多种的,如:布尔类型、整型(i32
、u32
、i64
)、浮点型等等,此时,Rust 也不知道这个变量应该是什么类型了,这个时候,就需要我们显式的标注出目标类型,否则编译器会报错 。
标量类型
一个标量类型代表了一个单个的值。在 Rust 当中有四种标量类型,分别是:
整数类型
整数类型是没有小数部分的,只能赋值为整数,整数类型也可以分为很多种:
大概分为有符号整数类型(以i
开头)和无符号整数类型(以u
开头),而有根据所占空间的大小可以分为:
有符号整数的取值范围:-(2 ^ n - 1) ~ (2 ^ n -1)
无符号的整数取值范围:0 ~ 2 ^ n -1
上面的 n
就是代表所占的位数。
上面表中最后一个需要特殊说明一下,arch
代表的是系统的架构,也就是说,isize
和 usize
会根据系统架构不同而改变,在 32
位的系统中是 i32
,而在 64
位的系统中,则是 i64
,举个例子:
rust
let str = "kiner";
let str_len = str.len();
我们定义了一个字符串类型的变量 str
,然后有定义了一个用于保存字符串长度类型的变量 str_len
,而 str_len
的值是调用字符串的 len
的方法获取字符串的长度,此时,这个方法返回的长度的类型就是 usize
,为啥用 usize
,而不是isize
呢?你见过字符串的长度是负数的么?字符串长度是不是最小只能是 0
,因此没必要使用有符号整数,无符号整数就够用了,因此使用的是 usize
。而我们上面说了,isize
和 uszie
占的空间大小是取决于当前设备的系统架构的,如果当前运行设备是 64
位的,那么这里的 usize
的大小就相当于 u64
,如果是 32
位的,就相当于是 u32
的大小了。
字面值
-
十进制 :如:
10_000
,可以使用_
进行格式化分隔,便于人眼识别 -
十六进制 :如:
0xff
转换成十进制就是255
-
八进制 :如:
0o77
,转换成十进制就是63
-
二进制 :如:
0b1100_0011
,转换成十进制就是195
-
字节表示,仅限于 u8 类型 :如:
b'A'
,十进制表示就是65
,相当与获取字符"A"的 charCode。类比到TS/JS
中就相当于是'A'.charCodeAt(0)
类型后缀
在 Rust 当中,上面所有的标识形式的字面值,都可以减伤类型后缀用于标识类型,如:
当然,你也可以在类型后缀和数值之间不加 _
,但这样可读性就变得很差了,个人建议还是要加类型后缀时,使用 _
将值与类型分隔开,这样可读性就更高了。
默认类型
在 Rust 当中,整数有那么多个子类型,但在很多情况下,你不知道该如何选择用那种类型,那这个时候,你可以考虑使用推荐的默认类型:i32,这个类型总体上来说,即使实在 64 位的系统中也是速度很快的。
整数的溢出
我们上面说了有符号整数和无符号的整数的取值范围:
有符号整数的取值范围:-(2 ^ n - 1) ~ (2 ^ n -1)
无符号的整数取值范围:0 ~ 2 ^ n -1
如,一个 u8 类型的整数,他的取值范围是 0 ~ 255
的,那假如说我们给它赋值:256
会怎样呢?我们直接来试一下:
不出意料的,如果我们这么赋值,Rust 会直接给我们一个无情地报错,并提示我们,取值范围应该是 0~255
的全闭区间
浮点类型
在 Rust 当中,跟 java 等高级语言一样,有两种浮点类型:
- f32:单精度浮点
- f64 :双精度浮点,类似与 java 中的 double
默认类型
因为在现代的 cpu 上, f32 和 f64 的运算速度是差不多的,为了能够精度更高,采用 f64 作为浮点类型的默认类型。
布尔类型
跟其他语言一样,只有 true
和 false
两个值,跟其他语言不一样的是,其他语言如 ts ,布尔类型表示为:boolean
,而在 Rust 当中,则是表示为:bool
,并且进占用一个字节的大小。
字符类型
使用 char
类型来描述语言中最基础的单个字符的类型。字符类型的字面值使用单引号,并且占用 4 个字节。
rust
let a: char = 'a';
// 这样会报错
let b char = "a";
复合类型
复合类型就是可以把多个类型放在一个类型里面,在 Rust 里面,提供了两种基础的复合类型,包括:元组(Tuple)、数组。
这两个其实在 TS/JS
中也有,而且很常用,如:
tsx
function Comp (){
// 这个就是一个由两个元素组成的元组,第一个元素的类型取决于 useState 中传入的初始值的类型 T,而第二个元素的类型则是:Dispatch<SetStateAction<T>>
const [showModal, setShowModal] = useState(false);
const arr: (string | number)[] = ['kiner', 0];
}
元组(Tuple)
元组可以将多个类型放在一个类型里面,并且元组的长度一旦初始化之后就是固定不能更改的。
声明元组
前端同学应该都很清楚在 TS/JS
当中应该如何声明一个元组:
typescript
// 匿名声明
const tuple1: [number, string] = [0, 'kiner'];
// 具名声明
const tuple2: [age: number, name: string] = [18, 'kiner'];
那么,在 Rust 当中应该如何声明元组呢?
在 Rust 当中,使用的是小括号来声明元组,这与 TS/JS
中用中括号声明是有区别的,可以在每个位置上都声明一个对应的类型,而且这些类型不要求一定要相同的,我们直接来看一个例子:
rust
let tuple = (0,'a',"kiner",3.0_f32, 2.88_f64);
获取元组中的值
在学习 Rust 中的元组如何取值之前,我们先来回想一下,在 TS/JS
当中我们应该如何取值呢?
typescript
const tuple2: [age: number, name: string] = [18, 'kiner'];
// 1. 使用解构的方式获取元组中的值
const [age, name] = tuple2;
console.log(`My name is ${name},I'm ${age} years old!`);
// 2. 使用索引取值
console.log(`My name is ${tuple2[0]},I'm ${tuple2[1]} years old!`);
其实在 Rust 当中的取值也是很相似的:
rust
let tuple = (0,'a',"kiner",3.0_f32, 2.88_f64);
// 使用结构法获取元素值
let (a, b, c, d, e) = tuple;
println!("a: {},b: {},c: {},d: {},e: {}",a,b,c,d,e);
// 使用索引取值
// 跟 TS/JS 不一样的是,Rust 不是用 [索引] 这种方式,而是 .索引 的方式取值
println!("a: {},b: {},c: {},d: {},e: {}",tuple.0,tuple.1,tuple.2,tuple.3,tuple.4);
数组
数组也是可以将多个类型放在一个类型里面,但是与元组不同的是,在 Rust 当中,数组中每一个元素的类型都是一样的。(这与 TS/JS 当中也是有区别的,在 TS/JS 中是允许数组中每个元素的类型不一样的)。并且在 Rust 当中,数组的长度在声明之后也是固定的,不能变长,也不能缩短,这也是跟 TS/JS 中的数组的重要区别点之一。
数组的作用
在 Rust 当中,数组是很常用且有用的数据结构,它的应用场景如下:
- 数据存放在栈内存:当你想让你的数据存放在栈内存,而非堆内存时,就可以使用数组,因为数组是在 栈内存(Stack)分配的单块的内存。
- 保证固定数量的元素 :由于在 Rust 当中,数组一旦声明之后,长度就不可变了,因此,如果在某些场景下,我们想要确保获得固定数量的元素时,可以使用数组。
Vector
或许有同学会说,数组不能够动态伸缩了,这样在很多场景很不方便,有没有其他解决方法呢?
在 Rust 中,Vector
是一种动态数组类型,它可以在运行时进行大小的调整。Vector
提供了一些方法来方便地操作和管理数据。
Vector
的作用:
- 动态大小:
Vector
允许在运行时动态调整大小,可以根据需要添加或删除元素。 - 所有权管理:
Vector
可以拥有其元素的所有权,这意味着它负责在适当的时间释放内存。 - 灵活性:
Vector
可以存储任何类型的元素,并且可以轻松地在不同元素类型之间进行转换。
Vector
和数组的区别:
- 大小:数组在编译时就确定了其大小,而
Vector
可以在运行时根据需要动态调整大小。 - 所有权:数组的所有元素拥有相同的所有权属性,而
Vector
的元素可以拥有不同的所有权属性。这意味着Vector
可以存储任意不同所有权类型的元素。 - 可变性:数组是固定大小的,并且默认情况下是不可变的,而
Vector
是可变的,并且可以通过push
和pop
等方法在运行时添加或删除元素。
以下是一个示例,演示了 Vector
和数组的使用:
rust
fn main() {
// 使用数组
let arr: [i32; 3] = [1, 2, 3]; // 声明一个包含 3 个 i32 类型元素的数组
println!("Array: {:?}", arr);
// 使用 Vector
let mut vec: Vec<i32> = vec![1, 2, 3]; // 声明一个包含 3 个 i32 类型元素的 Vector
println!("Vector: {:?}", vec);
// 添加元素到 Vector
vec.push(4);
vec.push(5);
println!("Updated Vector: {:?}", vec);
// 删除 Vector 中的元素
vec.pop();
println!("Updated Vector after pop: {:?}", vec);
}
在上述示例中,我们声明了一个包含 3 个元素的数组 arr
和一个包含 3 个元素的 Vector
vec
。然后,我们使用 push
方法向 Vector
添加两个元素,并使用 pop
方法删除一个元素。
总结起来,Vector
在 Rust 中提供了动态大小、所有权管理和灵活性等特性,使其在需要动态调整大小的场景中非常有用。与之相比,数组在编译时就确定了大小,是一个固定大小的数据结构。当你不知道该使用数组还是使用 Vector
时,建议使用 Vector
。
数组的类型
在 Rust 当中,我们可以这么定义一个数组:
rust
// 其中,在中括号包含了两部分的信息,分号左边的是数组元素的类型,而分号右边则是数组的一个长度
let arr: [i32;5] = [1,2,3,4,5];
除了上面这种最常见的声明方式外,还可以这样声明:
rust
// 我们直接在赋值的时候,给数组一个初始值 false,然后分号后面跟着长度,就代表这个长度为 5 的数组里面所有的元素初始值都是 false
let arr = [false;5];
访问数组的元素
访问数组元素的方式其实跟其他语言是差不多的
跟 TS/JS 不一样的是,如果我们访问数组越界了,在 Rust 会报错:
结语
至此,我们已经相对详细的学习了在 Rust 当中的一些基础的标量类型和复合类型了。当然,Rust 中的类型肯定远远不止这些的,其他更复杂的类型,我们在后续的学习过程中再来深入学习一下。