再继续快速学习一下 Rust 的以下几个知识点,就可以开始着手做点小工具了
- 基本数据类型
- 复合数据类型
- 基本的流程控制
Rust 设计为有效使用内存考虑的,它提供了非常细力度的数据类型,如整数分为有无符号,宽度从 8 位到 128 位,分别表示为 i8, u8, u128 等。浮点数有 f32 和 f64,以及 bool 和 range 类型。
元组
元组和 Python 的元组用法类似,Immutable, 可混合类型
数组
数组类型中的元素类型相同,表示为 [T; n], T 为类型,n 为元素个数
切片(Slice)类型
相同类型但不同长度的数组是不同的类型,如 [T; 1], [T; 2], [T; 3], 但它们的 Slice 类型都表示为 &[T]. Rust 的切片与 Python 同名概念类似
&[i32], &str 是 slice1, slice2, slice3 的推断类型,Slice 是一个视图,编译期无法确定长度。
Rust 不同宽度的类型之间不会自动转换的
不同宽度类型之间比较大小也不允许。
结构体类型
Rust 仍然延续了 C 的结构体
枚举类型
struct 和 enum 都支持用 impl
块给它们附加方法。
有有参数的枚举
输出
Yellow("黄"), Yellow("Y"), y1==y2?: false, y1==y3?: true
黄
黄可以黄的不一样,Enum 可以这样用于模式匹配
Rust 的标准库 std::collections 提供了 4 种支持泛型的容器类型, Vec<T>, VecDeque<T>, LinkedList<T>, HashMap<K, V>, BTreeMap<K, V>, HashSet<T>, BTreeSet<T>, Binaryheap<T>。
Vec 的初始容量是 0, 不指定 mut 的话,对 v 的内容都不可修改。调用 v.push() 往其中添加元素过程中,v.len 与 v.buf.cap 之间的关系
- 0 -> 0
- 1 ~4 -> 4
- 5 ~ 8 -> 8
- 9 ~ 16 -> 16
- 17 ~ 32 -> 32
由此可见,至少从目前的 Vec 实现 (Rust 1.75), Vec 只在容量不足时增长,并且每次翻倍。这种实现效率会有两个问题
1) 扩容太频繁, 消耗资源, Vec 新创建一个 2 倍容量的底层数组,并将现有元素移新缓冲中去
2) 元素越多, v.len 与 v.buf.cap 差值越大,也就造成 Buffer 中浪费的空间就越大. 比如元素数量为 4096 时,再添加一个元素就扩容到了 8092,这时实际使用 4097, 空闲 4095
所以使用 Vec 最好预先估算好容量,或用 LinkedList,如用下面的方式声明 Vec
可用 vec! 宏来声明 Vec
Vec 的基本操作有 v[index], get(index), push(value), pop(), remove(index) 等。get(index) 得到的是一个 Option[&i32): Some(value) 或 None, 和 Scala 相似的 Option
其他的类型不具体说明,用到时再查 API,单独说一个 HashMap 的 map.insert(key, value) 和 entry(key).or_insert(value) 操作
- map.insert(key, value): 不存在 key, 插入新的键值对,已存在 key 则更新对应值, 总是覆盖旧值
- entry(key).or_insert(value):不存在 key, 插入新的键值对,已存在 key 则不作任何操作, 不覆盖旧值
字符串本质上是字符序列,分不可变的 str 和可变的 String
字符串(str 和 String)
&str 的创建
String 本质上是一个字段为 Vec<u8> 的结构体,声明为
它是可变的,字符串内容在堆中,由字符串长度读取到实际内容。创建 String 的方式有
String 的 vec buf 容量也是成倍的增长, 只是 buf 为零时第一次增长不同, 例如下面的代码
如果 x <=8, push_str 之后, s.vec.buf 缓冲大小为 8, x > 8 的话,v.vec.buf 缓冲大小就是 x。String 还可用 "+" 连接 &str 字符串,它会返回新的 String
format! 宏可连接 str 和 Strign
Rust 的 String 不能直接使用索引(s[idx]方式) 来访问其中的字符, 字符串迭代分 bytes() 和 chars() 两种方式。bytes() 迭代出来的是字节的整数值. len() 获取的是以字节为单位的长度。更多 String 的操作就查 API 吧。
数字字面的类型用后缀来表示,所 2u8, 1.2f32. Rust 不支持 ++, 和 -- 操作。Rust 的各种算术运算,关系运算,逻辑运算,位运算和 C 和 Java 是一样的,运算优先级也没必要去记,知道乘除优先级大于加减就够了,其他的时候就加括号吧。
Rust 的流程控制
条件语句:if - else if - else 和 Java 的用法类似,但 Rust 的 if-else 还是个表达式,是有返回值的
循环语句有三种:
- loop {...} 其中加条件执行 break,有类似 Java 的 do {...} while(true) 类似功用
- while true {...}
- for count in 1..=10 {...}
Rust 循环中的 break, continue 和 Java 类似,不过 break 还能有返回值
要在遍历时修改元素内容,用
不推荐用 index 去访问集合,两原因: 1) index 要求 Rust 去做越界检查,影响性能, 2) 安全, for loop 访问一次可让集合下次不可用
break + label 跳出外层循环
break 的返回值
n 的值为 123
Rust 有现代语言的模式匹配功能,用 match, Rust 没有 switch..case 语句。Rust 的 match 必须穷举所有可能性,默认分支用 _
可放在最后。
也可以用 match 来匹配 Some(7), None 等 Option<i32> 值.
匹配 Option 时自动推断类型也有意思,比如
age 推断为 Option<i32>, 所以 Some(6) 也是 Some(i32), 如果把 Some(6) 改为 Some(6i8), age 也会被重新推断为 Option<i8>。要是不让 age 自动推断,写成
以上代码无法通过编译
let 是变量绑定(变量声明), if let 和 while let 模式匹配它 if 和 while 在判断条件的同时还能进行变量的绑定,这与 Scala 的 case Some(x) 类似。
只要不是 None 就能匹配为 Some(x), 同时捕获 value 中的值绑定到 x
if let 像是下面的 match 语法糖
let Some(x) 的另类用法
while let 也是一样的
它相当的 match 写法是
如果把 if let, while let 靠向 if, while 语句去理解,它们后面要求的是一个 bool 值,就会有疑问,为什么
if let Some(x) = value 中的 let Some(x) = value
只有有值时整体才为 true 呢?同样的在
while let Some(value) = vec.pop() 中为什么 let Some(value) = vec.pop()
有值时整体才为 true。
后记:
Rust 变量默认是 Immutable, 那是来真的,没有用 mul 修饰的话,变量的内容也是无法修改的,这与 Java 不同。
println!
宏后端调用了 std::fmt::format 宏,它的具体用法见 Rust 官方的 Module std::fmt, 它像 Python 的 format 那样可以指定输出格式,用序号或命名变量,它与 to_string() 方法,fmt::Display, fmt::Debug trait 相关。
Rust 没有构造函数的概念,许多类型实现了 new() 静态方法(这不是 Rust 语言的一部分),创建一个类型一般用 Complex {re: 2.1, im:-1.2} 或 Complex::new(11.1, 22.2) 两种方式。
Rust 的 if, match 都是表达式,是有返回值的, {}
也是有返回值
在 Rust 中,后面不加 ;
分号的是表达式(Expression),有返回值,没 ;
分号的是语句(Statement),无返回值(或说返回值为 Unit), return x;
本身是一条句,但 return
表达了返回值。
本文链接 https://yanbin.blog/rust-language-learning-2/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。