Rust-3 数据类型
前两节讲完了数据的声明,以及rust中的变量以及常量,这次我们开始进入数据类型的章节。
同大多数强类型的语言一样,rust也有着自己的确切的类型,与其他语言一样也分为基础类型和复合类型。基础类型很好理解,或许有基础的一下子就能想到一下几种:
- 整型:很好理解,就是我们常见的类型,在rust中分别是,有符号整数 (
i8
,i16
,i32
,i64
,isize
)、 无符号整数 (u8
,u16
,u32
,u64
,usize
) - 浮点数:
f32
,f64
- 字符串: 就是字符串罢了
- 布尔类型:true , false
- 字符类型:在rust中表示单个 Unicode 字符,存储为 4 个字节
- 单元类型:这个是rust的一个特殊类型,就是一个
()
,唯一值也是()
.在当前还真不知道是做啥用的,等后面用到时候再说。
从别的语言上我们也都接触过各种变量,这篇文章就简单写一下,各种基础数据类型的大小和范围也都跟其他语言大差不差
基础类型
数值类型
整型
长度 | 有符号整型 | 范围 | 无符号整型度 | 范围 |
---|---|---|---|---|
8位 | i8 | -128 ~ 127 | u8 | 0 ~ 255 |
16位 | i16 | -128 ~ 127 | u16 | 0 ~ 255 |
32位 | i32 | -128 ~ 127 | u32 | 0 ~ 255 |
64位 | i64 | -128 ~ 127 | u64 | 0 ~ 255 |
128位 | i128 | -128 ~ 127 | u128 | 0 ~ 255 |
与架构相关 | isize | -128 ~ 127 | isize | 0 ~ 255 |
可能我们平时并不会在声明变量的时候指定变量的类型,这时候我们就有疑问了,那么声明的这个变量是什么类型的呢?在rust中,使用let a = 1;
声明的一个整型变量a
, 此时编译器会默认a的类型是 i32
的。我们可以用过 type_name_of_val()
方法来获取a的类型。
fn main() {
use std::any::type_name_of_val;
let x = 1;
println!("type_of(x)={}", type_name_of_val(&x));
}
执行结果:
我们可以我们可以看到 最终得到的类型是 i32。 因此我们可以首选这个类型,同时这个往往也是性能最好的。
整型溢出问题
在日常开发的时候,我们可能会遇到整型溢出的问题,如果溢出以后在业务上就会有奇奇怪怪的事情,但是其他语言基本不会检测这个东西,但是rust
编译器有一个很有趣的处理:会在当在 debug 模式编译时,Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 panic。
在当使用 --release 参数进行 release 模式构建时,Rust 不检测溢出。相反,当检测到整型溢出时,Rust 会按照补码循环溢出(two’s complement wrapping)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 u8 的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是你期望的值。依赖这种默认行为的代码都应该被认为是错误的代码。
经过线下用golang
测试了一下,最后如果溢出的话,也是按照跟rust同样的一个处理,并且程序不会panic。
浮点数类型
浮点数,就是带小数点的数字咯,跟其他语言相似,rust
中针对浮点数也是有两种的,分别的f32
和 f64
。 也就是32位和64位。rust中默认的浮点类型是64位的。
话不多说,浮点型没啥好说的,只是有一点就是由于浮点数本身在底层存储的特殊性,导致了我们是没办法直接对浮点数测试相等,以及对于未定义的浮点数,更是要格外小心。也就是在使用的时候要尽量遵守两个准则:
- 避免对浮点数判断相等性
- 对于出现的未定义,要格外小心。
可能如果接触过其他语言的,可能预先了解过浮点数其实并不是表面上看到的那样子,可能我们表面看到的是0.3
但是可能底层存的可能就是0.333333334
。这里就不赘述了。
Nan
Nan (Not a Number),未定义,比如,我们对负数取平方根-2.1.sqrt()
最后的结果是一个未定的数,那么我们就会得到一个NaN。可不要小看这个NaN呢,确实是导致很多问题。
如果大家还记得那年Bilibili出现的重大事故的话,就应该记得,最后就是因为lua脚本中对NaN的一个数做了运算,最终导致了问题。所以究竟多么严重就不说了吧。
在rust中个所有跟NaN的运算最终都会返回一个NaN。
fn main() {
let x = (-42.0_f32).sqrt();
println!("x = {}", x)
}
但是我们如何判断这种情况呢? 有一个方法可以来判断is_nan()
fn main() {
let x = (-42.0_f32).sqrt();
if x.is_nan() {
println!("未定义的数学行为")
}
}
数字运算
数字运算一个老生常谈的话题了,或许大家在大学接触编程的时候,前几道oj题里面一定有一道是 a+b
。 我们来看看rust里面的数字运算吧。不说其他的了直接来点测试代码吧。
fn main() {
// 加法
let sum = 5 + 10;
println!("执行加法:5 + 10 = {}", sum) ;
// 减法
let difference = 95.5 - 4.3;
println!("执行减法:5 + 10 = {}", difference) ;
// 乘法
let product = 4 * 30;
println!("执行乘法:5 + 10 = {}", product) ;
// 除法
let quotient = 56.7 / 32.2;
println!("执行除法:5 + 10 = {}", quotient) ;
// 求余
let remainder = 43 % 5;
println!("执行求余:5 + 10 = {}", remainder) ;
}
没啥说的就是一些基础运算了,也就不赘述了。
基本类型的变量声明
我们在上一章的时候已经提过了变量声明,但是呢我们都是直接用的编译器默认类型也就是:let a = 0
。 编译器会自动推导成i32类型但是如果我们想声明成其他类型的呢,这个时候就要进行显示的声明变量了。直接看看代码吧,这里也是比较简单的。
fn main() {
use std::any::type_name_of_val;
// 编译器会进行自动推导,给予twenty i32的类型
let twenty = 20;
println!("type_of(twenty)={}", type_name_of_val(&twenty));
// 类型标注
let twenty_one: i32 = 21;
println!("type_of(twenty_one)={}", type_name_of_val(&twenty_one));
// 通过类型后缀的方式进行类型标注:22是i32类型
let twenty_two = 22i64;
println!("type_of(twenty_two)={}", type_name_of_val(&twenty_two));
// 只有同样类型,才能运算
let addition = twenty + twenty_one + twenty_two as i32;
println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition);
// 对于较长的数字,可以用_进行分割,提升可读性
let one_million: i64 = 1_000_000;
println!("{}", one_million.pow(2));
// 定义一个f32数组,其中42.0会自动被推导为f32类型
let forty_twos = [
42.0,
42f32,
42.0_f32,
];
println!("type_of(forty_twos[0])={}", type_name_of_val(&forty_twos[0]));
// 打印数组中第一个值,并控制小数位为2位
println!("{:.2}", forty_twos[0]);
}
最后的执行结果是这样的。这里要注意一个问题,
我在执行加和的时候,进行了一次类型转换,也就是把i64转换成了i32以后才进行的相加。
原因是在rust中,是不支持不同类型进行加和的,哪怕都是整型,但是是不同位数的,这时候就需要用的类型转换。具体的代码可以看let addition = twenty + twenty_one + twenty_two as i32;
这句。
在rust中进行类型转换,并不像其他语句中那种 比方说golang 可以直接int64(a)
这样子,是需要用的一个 关键字as
。这里不进行多说只需要知道怎么用就行,或许后面会单独学习as
这个关键字的。
总结
洋洋洒洒的写了一堆,也写了好几天,都是下班以后抽空写的。总之,rust语言在类型上跟其他语言也是比较相似的 。只是可能部分地方不一样。
- 需要了解rust中每种变量所占用的字节数,这样后续开发的时候可以选择更准确的变量类型来使用
- rust不会主动进行类型转换,需要我们自己使用
as
来进行类型转换。 - 在rust中数值是可以使用方法的,例如你可以用以下方法来将 13.14 取整:
13.14_f32.round()
,在这里我们使用了类型后缀,因为编译器需要知道 13.14 的具体类型。 我理解这个可以把rust中所有的东西都想象成一个对象就可以了,每个对象都有自己的方法。
吾生也有涯,而知也无涯。以有涯随无涯,殆已!已而为知者,殆而已矣 —— 庄周