Rust 变量和基础数据类型
2024-03-13
Rust
通过 let
关键字声明变量, 变量默认是不可变 (只读) 的.
let foo = 5; // 创建了一个不可变的变量 foo, 并绑定了 5
foo = 10; // 不能对 foo 重新绑定
// 使用 mut 关键字创建可变的
let mut bar = 5;
bar = 10; // 可以重新绑定
不需要写变量类型, 编译器可以自己推导, 但是常量 (const) 和静态变量 (static) 必须声明类型.
也可以使用相同的名字声明新的变量, 新的变量就会隐藏之前的声明的同名变量.
let x = 1;
// 第二次声明的 x 可以和第一次声明的 x 类型不一样.
let x = "";
常量
使用 const
关键字声明常量.
它和变量的区别:
-
不能使用
mut
关键字, 常量必须是不可变的. -
常量必须指定类型.
-
常量可以在任何作用域内进行声明, 包括全局作用域.
-
常量只可以绑定到常量表达式, 无法绑定到函数的调用结果或只能在运行时才能计算出的值.
比如:
10*10
就是常量表达式,10*i
就是通过运行时才能计算.但是有另外一种情况, 如果
i
也是常量, 那么就没问题.
const MAX_POINTS: u32 = 100_000; // 使用下划线是为了提高可读性.
// 在代码中使用了 MAX_POINTS 常量, 在编译的时候会直接替换为实际值 100_000.
静态变量
静态变量的语法和常量的语法相似, 只不过静态变量可以使用 mut
关键字.
static HELLO: &str = "Hello, world!";
静态变量的生命周期和整个程序的生命周期一样.
数据类型
栈上保存的是固定大小类型的值, 比如指针, u8, char 类型等, 这些类型都是固定大小的.
堆上保存的是非固定大小类型的值, 这些值在使用的时候会动态调整大小.
Rust 不支持隐式类型转换, 比如不会自动将 16 位整数转换为 32 位整数.
数字类型
分组 | 说明 |
---|---|
i8, i16, i32, i64, i128 | 有符号整数类型, 范围是 8 位到 64 位. |
u8, u16, u32, u64, u128 | 无符号整数类型, 范围是 8 位到 64 位. |
f32, f64 | 浮点数类型, 有 32 位和 64 位两种. |
isize, usize | 在 64 位 CPU 上, isize 和 usize 都是 64 位. |
虽然都是数字类型, 但是不同的数字类型不能比较.
比如
i16 > u16
不能比较,i16 > i32
也不能比较, 但是可以通过std::convert::TryFrom
进行转换.
元组 (Tuple)
元组的长度是固定的, 一旦声明就无法改变, 但是每一个元素都可以是不同的类型.
let tup:(i32, f64, u8) = (500, 6.4, 1);
// 不能使用 tup[0], 也不能使用变量 tup.(1-1).
println!("{}, {}, {}", tup.0, tup.1, tup.2);
// 第二种使用方式, 叫做元组的析构.
let (x, y, z) = tup;
println!("{}, {}, {}", x, y, z);
() 是什么意思
-
单元类型: 类似于其它编程语言中的
void
. -
占位符: 在使用
Result<T, E>
类型时, 如果你只关心错误情况, 而成功情况不返回任何有意义的值, 你可以使用()
作为Ok
类型的值. -
空元组
数组
数组长度也是固定的, 一旦声明就无法改变, 而且每一个元素的类型必须相同.
数组的大小也是类型的一部分, 比如
[i32; 5]
和[i32; 6]
是两个类型.
// 创建了一个长度为 5, 类型为 i32 的数组.
let a = [1, 2, 3, 4, 5];
// 创建了一个长度为 5, 类型为 i32 的数组,
// 但是数组中的元素都是 3.
let a = [3; 5];
// 这是错误的, 数组长度不能是变量.
// let a = [3; i];
切片
[T]
它就是一个切片类型, 表示一个动态大小的视图.
它只是对数据进行引用, 并不会复制数据或拥有数据的所有权.
动态大小是什么意思?
先来说一下静态大小, 比如说数组就是一个静态大小的, 因为数组在创建的时候就需要指定数组的大小.
然后动态大小就是反过来的, 就是说在创建的时候不需要指定大小, 而是在运行的时候才去确定大小.
不能直接使用切片类型, 因为编译器在编译的时候, 必须知道类型的大小.
你可能会想 t[0..3]
是用来创建切片的, 那么不是已经知道了切片的长度吗?
对, 确实可以在编译时推断出切片的长度, 但是编译器在处理切片的时候, 还是按照动态大小类型处理的.
let t = [3; 10];
// let t1: [i32] = t[0..3]; // 不能直接使用切片
// t[0..3] 创建切片, 然后获取这个切片的不可变引用.
let t1: &[i32] = &t[0..3];
下面是切片的使用:
let array = [1, 2, 3, 4, 5];
// 通过现有的数组创建出来一个切片,
// &[开始索引..结束索引],包头不包尾.
// &[开始索引..=结束索引],包头包尾.
//
// 还有一些简化方式:
// &[..结束索引]
// &[开始索引..]
// &[..]
&array[1..4]
可以对一个切片, 再进行切片.
下图比较清晰地呈现了切片和数据之间的关系:
切片可以是可变的, 也可以是不可变的, 而且还可以通过
Box<[T]>
, 在堆上分配切片.切片文档: https://doc.rust-lang.org/std/primitive.slice.html
下图描述了数组、Vec<T>
、切片引用 &[T]
、&mut [T]
, 以及在堆上分配的切片 Box<[T]>
之间的关系.
Vec
Vector 是数组实现的, 适用于随机访问和频繁在尾部插入元素.
创建 Vector:
let my_vector: Vec<i32> = Vec::new();
// 类型 i32.
let my_vector = vec![1, 2, 3];
// 保存 u8 类型, 长度是 10, 默认值是 0.
let my_vector = vec![0u8; 10];
Vector 中的所有元素都是保存在堆内存中的, 比如上面的元素 1, 2, 3 也是保存在堆内存中的.
还有两个常用类型:
-
VecDeque: 是一个双端队列, 适用于在两端频繁添加和移除元素.
-
LinkedList: 是一个双向链表, 适用于在任意位置频繁插入和删除元素, 但不需要高效随机访问的场景.
HashMap
键值对的形式存储数据, 一个键 (Key) 对应一个值 (Value).
use std::collections::HashMap;
let mut my_hashmap: HashMap<String, i32> = HashMap::new();
还有两个常用类型:HashMap 中的所有元素都是保存在堆内存中的.
- BTreeMap: 通过 B 树数据结构实现的有序键值对集合.
- BTreeSet: 和 BTreeMap 类似, 只不过它的 key 不能重复.
如何解决冲突?
使用开放寻址法的二次探查来解决.
要向 HashMap 中插入 e:5
数据, 通过 key: e
计算出来的 Hash 值, 是要将数据放在数组的 03 下标中.
但是 03 下标已经有数据了, 所以通过公式 hash(key)+N^2
计算下一个下标.
上面公式中的 N 是步长, 下标 03 中存放数据, 通过步长 1 找到下一个下标 04, 它也有数据所以步长加 1, 然后找到 07 下标.
标签:变量,..,切片,let,数组,类型,Rust From: https://www.cnblogs.com/zy24/p/18458155