Rust之抽空学习系列(三)—— 编程通用概念(中)
1、变量&可变性
在Rust中,变量默认是不可变的
fn main() {
let x = 5;
println!("x is {}", x);
}
使用let
来声明一个变量,此时变量默认是不可变的,因此x的值是5,如果修改就会发生报错
想要再次将x赋值为6时,编译报错了,提示无法对不可变的变量x两次赋值,并且还贴心地标注了两次赋值发生的位置,也给出了修改建议,让我们考虑使用mut
来绑定变量x
这个不可变变量的感觉就像是Java中的final
修饰的变量或者是Kotlin中val
声明的变量,仅仅是赋值一次,而后不可修改
而将变量声明为可变,就要用到刚刚建议里提及的mut
关键字了
fn main() {
let mut x = 5; // 声明为可变
x = 6;
println!("x is {}", x);
}
此时代码正常运行,输出6,可变的需要开发者自己来添加,这无形中也是倒逼开发者去思考设置可变的用途和必要性
在Rust中不可变的行为是默认的,而可变的行为需要开发者自己指定,这也是安全性的体现
2、常量
在Rust中,常量是使用const
关键字来进行声明的
但是,在进行常量声明的时候,有一些需要注意的内容:
- 不可以使用
mut
- 必须标注类型
- 可在任意作用域内声明
- 仅可以使用常量表达式赋值
不能使用mut
很好理解了,本身常量和变量就是矛盾的嘛
使用常量表达式进行赋值的原因是由于它们能够在编译时被计算出来
fn main() {
const MAX_POINTS: u32 = 100000; // 指明类型
const ONE_HOUR: u32 = 60 * 60; // 常量表达式
println!("The maximum number of points is {}", MAX_POINTS);
}
const MY_NAME: &str = "John"; // 任何作用域
3、变量遮蔽
变量遮蔽指的是新声明的变量覆盖了旧的同名变量
fn main() {
let x = 5;
let x = x + 1; // 6
let x = x * 2; // 12
println!("The value of x is: {}", x);
}
使用变量遮蔽与将变量声明为mut
是不同的,使用变量遮蔽声明的新变量依旧是不可变的,只不过相当于是用来保存新的数据了;除此以外,变量遮蔽甚至还可以修改变量的类型(请牢记,这是新的变量)
那么,此时我就想到一个场景,很适合使用这个特性
比方说,在实际业务当中,后端接口返回了一个时间戳表示的长串数字,前端需要以yyyy-MM-dd
这样的格式展示,这样的话,通常我们是这样处理的:
fn main() {
let date = 1677386710000; // 可能是精确到毫秒的
let formatedDate = get_formated_date_str(date, "yyyy-MM-dd");
println!("formatedDate: {}", formatedDate);
}
此时,我们产生了一个中间变量date,但是它仅仅在这里用于转换,我们真正要显示的其实是formatedDate,那么我们可以借助变量遮蔽稍微修改下:
fn main() {
let date = 1677386710000; // 1.0版本 原材料
let date = get_formated_date_str(date); // 2.0版本 加工上市的成品
println!("formatedDate: {}", formatedDate);
}
使用变量遮蔽就可以将同一个或一类的内容的加工作为多个版本,最后用到的那一版作为结果
帮忙省去了那些命名费解、存在又很尴尬的临时量,使得代码的业务逻辑主线更加清晰
4、数据类型
4.1、标量类型
标量类型作为单个值类型的统称
Rust内部定义了4种基础的标量类型
- 整数
- 浮点数
- 布尔值
- 字符
4.1.1、整数类型
整数类型表示的就是不包含小数部分的整数,其中分为有符号和无符号
无符号数始终不为负,开头使用u
区别,比如u32
有符号数通过二进制补码的形式来存储
以下是关于各个类型的整数类型所能够表示的范围:
除了直接指明描述位数的类型,像i32
,u32
,还有isize
和usize
两种特殊的类型,它们的长度取决于程序运行的目标平台
字面量
整数的字面量可以有多种形式呈现:
整数溢出
整数溢出主要是由于存储的数超出类型的限制,就像往桶里倒水一样
在debug模式下,发生整数溢出会抛出panic
let big: u8 = 255;
let small = 3;
let result = small + big; // 会发生溢出
print!("结果={}", result)
很明显255已经是u8
的极限了
程序发现这个算术运算有溢出的风险,立马抛出错误,中止执行
4.1.2、浮点类型
Rust提供了两种基础的浮点数类型:f32
,f64
在Rust中,浮点类型默认会被推导为f64
类型,因其在现代CPU执行中相较于f32
有更高精度,执行效率也相差无几
使用f32
需要显式进行指定
Rust中的f32
和f64
对应的就是IEEE-754标准中的单精度浮点数和双精度浮点数
4.1.3、布尔类型
布尔类型与其他语言是一样的,也是true和false两个值,并且仅占据一个字节
在条件表达式等控制语句中会有大量的出场
4.1.4、字符类型
字符类型用以描述单个字符
char
类型占4个字节的空间,并且是一个Unicode标量值
4.2、复合类型
在Rust当中,复合类型表示将不同的类型组合为一个类型
在Rust当中,提供了两种基础的复合类型:
- 元组(tuple)
- 数组(array)
4.2.1、元组类型
元组能够将多个不同类型的值组合进一个类型,但是一旦声明结束便不可修改元素的数量
let tup: (i32, char, bool) = (15, 'A', false); // 声明
如果需要对于其中的元素进行访问,可以使用解构的方式
let (a, b, c) = tup; // 几个变量将和里面的元素对应上 比如:a: 15, b: 'A', c: false
除了解构的方式,元组还可以支持使用.
配合索引的方式对元组内部的元素进行访问
let tup: (i32, char, bool) = (15, 'A', false);
print!("{}", tup.1); // 下标从0开始
4.2.2、数组类型
数组有固定的长度,而且数组中的各个元素必须为相同的类型
let list: [char; 3] = ['A', 'B', 'C']; // 前面是类型,后面是长度
let zeros = [0; 5]; // 长度为5,都是0
数组的声明可以使用[]
根据类型和长度初始化,也可以指定默认值和长度
数组的访问和其他语言也几乎没有什么不同
print!("元素2={}", zeros[1]);
数组对应的是内存栈中一块连续的内存
另外,提到数组,就不得不说一个名为数组越界的问题
fn main() {
let zeros = [0; 5];
print!("元素9={}", zeros[8]); // 必定越界
}
将索引修改为一个没有的量(无法访问到),这样,在程序执行的过程中会抛出panic
,进而中止了程序的运行
https://kaisery.github.io/trpl-zh-cn/ch03-01-variables-and-mutability.html
https://www.bilibili.com/video/BV1182ZYhEdV