Rust 学习笔记
这是没什么技术含量的劣质学习笔记,或许并没有深刻理解语言的核心思想。我是废物。
写这个有一个目的是因为现在退役了所以去更好地给盾当馁个?
$\Theta$. Untitled: Throw Your C++ Away!
Rust 是内存安全的!C++ 是不安全的!
所以,把你的 C++ 杨掉吧!(这不是错别字)
$\text{I}$. A+B Problem: Basic points and Value
1. Something that you have to know at first
这是一些比较重要的总体上的知识点。
首先必须吸引人眼球:Rust 的速度和 C++ 完全可以媲美,甚至有些时候可以超越 C++,甚至或许可以帮你草题。
Rust 不属于传统的面向对象语言,也不完全是函数式语言,而是融合了多种编程范式的特性,实在是太先进了。所以说 Rust 里面也有面向对象相关的一些东西。
Rust 里有一个 C++ 没有的重要概念:“所有权”,这个东西保证了它的内存安全性,也是 Rust 的核心思想。
Rust 是缩进不敏感的,有分号和大括号来表示结构。
C++ 有头文件,但是 Rust 没有。
Rust 是没有 using namespace 的。但是它有 namespace std,也有 use std::xxx; 来允许访问命名空间中的某个成员。它的 std 库中包含的功能是相当多的。
2. Variables are Not Variable in fact
我猜你一定想要完全合规合法的 $128$ 位整形,Rust 有!C++ 做不到系列。
使用形如 let x=1; 来定义一个不可变变量。
使用形如 let mut x=1; 来定义一个可变变量。mut 在此处的效果就声明 x 可变。
这和 C++ 的逻辑是反的。
可以不显式的定义变量类型,编译器会自己猜测。
使用 const x=1 来定义常量。常量只能是确定的数值,不可变变量可以被赋为运行时计算出的某一个量。变量的名字里最好不要有大写字母,常量的名字里最好不要有小写字母,如果一个变量的名字开头是下划线,那么不调用它不会 Warning(比如用作循环变量时),否则会 Warning。
变量一定要初始化,没初始化的变量访问了会 Panic。(其实几乎相当于 RE 的意思)
Rust 里面变量是可以重复定义的。重复定义就会把前面的覆盖掉。这样可以修改不可变变量。重复定义也可以重定义成别的类型,都是没有影响的。这个事其实叫作“遮蔽”。
这表明了 let x=1 和 x=1 是不一样的。还有一个细节,就是如果遮蔽的时候把变量是否 mut 给改了,会爆 Warning,但是不会 CE。
也可以显式地定义变量类型,例如 let i32:x=1; 就是定义一个 32 位带符号整形变量,其实就是 C++ 中的 int。整形变量还有 i8~i128,isize,u8~u128,usize, 那个带 size 的就是依赖于系统架构,比如在你校的机子上 isize,usize 就等价于 i32,u32。你校真是太发达了!
你校农业不发达,必须要有金坷垃!
还需要注意的是,在一般情况下,Rust 是没有自然溢出这个东西的,超过范围会直接 Panic,只有编译命令加上 --release 才支持自然溢出,但是这也会爆 Warning。
可以用类似 1919810u64 这种东西,效果等价于 C++ 中的 1919810ull。可以用 0b101 这种来表示二进制数,又是 C++ 做不到系列。
浮点数只有 f32,f64。
加减乘除的话和 C++ 是一致的,整数除法向 0 取整,浮点数正常除。
3. “Why your code is so long?”
输入。Rust 的输入特别的赤石,如果你不做好的封装的话就很难绷,所以这里给个快读板子,你可以把它放进你的缺省源里。
[!abstract]- Fast IO
云剪贴板链接。
使得输入更方便。
以及主函数。Rust 也是从 main 开始执行,具体关于函数后文会讲。
输出的话,print!() 是不带换行,println!() 是带换行且清空缓冲区。
输出字符串比如 print!("ZHQ loves me");,输出变量打个大括号,比如 print!("{x}") 或 print!("{}", x)。那个感叹号是宏的意思,和 C++ 中的宏不太一样,后文会讲。
欸所以我们会 A+B Problem 了!离一个好的馁个又进了一步!
$\text{II}$. Suffix Array: Array and Control Flow
那么目前我们可以做到用 Rust 写大部分的红题了。
但是这远远不够。
1. Nietzsche, Camus, Give Me Back 100 Pts!
首先,C++ 里面有个东西叫 tuple,也就是“元组”,它使得你校某位学长在最后一次 CSP 挂了大分,因为它是 C++17 之后的产物,dzd 不让用。
C++ 跟我们不合适,退坑吧啊。
然而 Rust 里有 tuple 的原生支持,不需要依赖任何库就能用的标准语法,非常的好。但是整个 Rust dzd 都不让用,所以 CCF 跟我们不合适,退役来当馁个吧啊。
定义 tuple 使用小括号,tuple 中的元素类型可以不同。
比如说,let (i32, char, f64): x = (114514, 'Θ', 114.514); 定义了一个 tuple。
Rust 并不需要真的把 tuple 这几个字写出来,而是直接这样用小括号里若干个类型来定义。
可以用 tuple 来定义多个变量,比如说 let (i32, i16, i8): yhx = (11, 45, 14); let (d, u, n)=yhx; 这个其实叫作“模式匹配”,并且称此操作“解构”了元组值,可以用这种方式来访问元组值。需要注意的是,大部分情况下,解构 tuple 的值后原 tuple 还能继续访问,但是如果 tuple 中存在未实现 Copy Trait 的类型(例如 String,Rust 中的字符串类型),那么以后将不能访问!它的原因牵扯到“所有权”这个 Rust 中极其重要的概念,后文会讲。
访问 tuple 有更简单方便的方法,那就是使用点号“.”加值的索引直接访问。
比如说 yhx.1,注意这个索引是 0-index 的。比如说一个 tuple c 中的第一个元素是 c.0。
所以话说回来,CCF 什么时候才能改用 C++17/20 呢……
2. Arrays, Should Be Thrown in the Trash Bin
Rust 中的数组和 C++ 中的区别不大,数组中元素类型要一致。但是由于 Rust 没有全局变量放在堆内存里这个事情,就会导致在 Rust 中静态数组很少用。在定义一个数组时可以直接赋值:let a = [1, 2, 3, 4, 5]; 同样是 0-index 的。也可以直接给整个数组赋初值,比如 let a=[11; 4]; 注意中间是分号,它的作用是开一个长度为 $4$ 的可修改其中元素的数组,初始值全赋成 $11$,注意这两个值必须是 usize 类型,且必须是常量。
访问数组就是正常的方括号语法比如说 a[i]。这个 $i$ 也必须是 usize 类型的(但是可以不是常量),否则将会 CE。其实相当于 C++ 中的下标本质上是 size_t 一样的,但是我们 C++ 有隐式类型转换使得你可以直接用,但是 Rust 不能有隐式类型转换的(可以简单地理解为全部自动带上 explicit),所以如果下标是其他类型的变量,一定要显式标明,例如 a[i as usize],这个 as 的作用即为类型转换。
还有,数组是不能越界的,数组越界会导致 Panic。
诶,然而我们大 C++ 就不一样了,数组越界,某些神秘状况下,照样能跑!下标负的也能跑!嘿嘿。
刚才提到,Rust 里静态数组这个东西实在是太鸡肋了,不过 Rust 里面也有类似于 C++ 中 vector 的动态数组,它叫做 Vec。
定义 Vec 与定义静态数组差别不大,比如 let a: Vec<i64> = vec![1, 1, 4, 5];。
它提供了用来初始化的宏,只需要加一个 vec! 即可。尖括号内即为 Vec 中的元素类型,同样的,let a: Vec<u64> = vec![0, 114514],这两个是也必须是常量以及 usize,下标访问也和静态数组一样,也必须是 usize。还可以建立空 Vec,比如说:let v: Vec<i32> = Vec::new();
它占用的是堆内存,可以放心食用。
既然是动态数组,肯定得要支持 push_back。
在 Rust 中,这个功能叫 push。当然,如果想要 push,定义的时候一定要有 mut。clear,resize 的功能都是有的
其他功能我们后面再详细说明。
3. If Being a CM Is Harder Than Having a GF……
像 if,循环这些东西被称为控制流。
将 Rust 中的 if 和 C++ 中的 if 对比,可以发现,最明显的区别就是,Rust 的 if 后面的条件不需要带括号。
比如说:
1 | |
还有一处细节是,Rust 中,就算该语句内部只有一行,也不能不打大括号。
代码中的条件必须是 bool 值。而且不能把其他类型用 as 转换成 bool。
可以叠多个 else if 以及最后的一个 else。
在 Rust 中,用大括号打起来的东西叫作代码块,所有代码块都可以有返回值。
如果没有返回值,则默认返回值为空元组 (),这也被称为单位类型。
返回值放在代码块的最后,没有分号。
比如说:
1 | |
这个代码块的返回值即为 $a+b$。
这也是一个 Rust 的重要特性。