5、初识Rust – 数据类型

目录 编程

一、整型

Rust中,每一个值都有一个数据类型,以便明确数据的处理方式。Rust中存在两类数据类型子集,一种是标量(scalar),一种是复合(compound)。

Rust是一门静态类型语言,在编译时就必须明确所有变量的类型,根据值和其使用方式,编译器推断出想要使用的类型,例如

fn main() {
    let x: i8 = 2;
    println!("{x}");
}

变量x中的值就是一个整型类型,整数是一个没有小数部分的数字。除了i8整数类型,你也许还能看见下面这重类型

fn main() {
    let x: u8 = 2;
    println!("{x}");
}

这其实也是一个整数类型,那u8和i8有什么区别呢?

它们其实代表了2种整数,一种是有符号整数,一种是无符号整数。

有符号整数,通俗点就是负整数,无符号整数,则是正整数。有符号整数是包括正整数的。

i8可以存储  -(27) 到 27 – 1 在内的数字(-128-127);

u8可以存储0 到 28 – 1在内的数字(0 到 255);

以下为整型表

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

其中有2个特殊的isize与usize,这2个是依赖与运行程序的计算机架构的,64位架构运行时就是64位,32位架构时运行就是32位,一般这2个类型主要作为某些集合的索引。

可以按下面表内的任何一种形式编写数字字面值。请注意可以是多种数字类型的数字字面值允许使用类型后缀,例如 57u8 来指定类型,同时也允许使用 _ 做为分隔符以方便读数,例如1_000,它的值与你指定的 1000 相同。

数字字面值 例子
Decimal (十进制) 98_222
Hex (十六进制) 0xff
Octal (八进制) 0o77
Binary (二进制) 0b1111_0000
Byte (单字节字符)(仅限于u8) b’A’

还有下面这种类型的代码。你也许会很疑惑,不是要指定类型么,为什么下面这段代码可以正常运行,而不会抛出未定义类型的错误。

fn main() {
    let x = 2;
    println!("{x}");
}

这是因为,Rust存在默认类型这个概念,而数字默认类型就是i32。

补充一个整型溢出的概念

整型溢出

比方说有一个 u8 ,它可以存放从零到 255 的值。那么当你将其修改为 256 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),这会导致以下两种行为之一的发生。当在 debug 模式编译时,Rust 检查这类问题并使程序 panic,这个术语被 Rust 用来表明程序因错误而退出。第九章 panic! 与不可恢复的错误” 部分会详细介绍 panic。

在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装(two’s complement wrapping)的操作。简而言之,值 256 变成 0,值 257 变成 1,依此类推。依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping。 为了显式地处理溢出的可能性,你可以使用标准库在原生数值类型上提供的以下方法:

  • 所有模式下都可以使用 wrapping_* 方法进行包装,如 wrapping_add
  • 如果 checked_* 方法出现溢出,则返回 None
  • 用 overflowing_* 方法返回值和一个布尔值,表示是否出现溢出
  • 用 saturating_* 方法在值的最小值或最大值处进行饱和处理

二、浮点型

除了不带小数点的整型类型外,还有带小数点的浮点类型。相对于整型,浮点的类型就少的多了,分别是f32与f64,分别占32位与64位。默认类型是f64也就是未声明类型时的默认类型。

fn main() {
    let x = 2.0; // f64
    let t: f32 = 3.0; // f32
}

浮点数采用 IEEE-754 标准表示。f32 是单精度浮点数,f64 是双精度浮点数。

三、数值运算

Rust中所有的数字类型都支持基本数学运算:加减乘除以及取余。整数除法会向下舍入到最接近的整数。

fn main() {
     // 加法
     let sum = 5 + 10;

     // 减法
     let difference = 95.5 - 4.3;

     // 乘法
     let product = 4 * 30;

     // 除法
     let quotient = 56.7 / 32.2;

     // 因为整数除法会向下取整数,所以结果为0
     let floored = 2 / 3;

     // 取余
     let remainder = 43 % 5;
}

四、布尔型

布而型是一个非常重要的类型,Rust也和其它的编程语言一样使用2个可能的布尔类型值: true 和 false。在Rust中,声明布尔类型使用bool表示。

fn main() {
    let x = true;
    let f: bool = true;
}

使用布尔类型的基本场景都是在表达式,比如if这些表达式。

另外博主还有一个想法,像整数类型因为类型非常多,所以依靠Rust默认附值可能达不到需求最后产生报错,但bool类型值都比较简单,是否可以在实际生产中也省略: bool?

五、字符类型

Rust的 char类型是语言中最原生的字母类型。

fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // with explicit type annotation
    let heart_eyed_cat = '😻';
}

特别需要注意的是,使用单引号来声明 char 变量,用双引号声明字符串变量。

Rust 的char 类型大小为四个字节,并代表了一个Unicode 标量值(Unicode Scalar Value),这表示它可以比ASCII表示更多的内容。在Rust中,拼音字母,中文,日文,韩文, emoji和零长度的空白字符,都是有效的 char值 ,Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。

六、复合类型

Rust中的复合类型可以将多个值组合成一个类型,有两个原生的复合类型,分别是:元祖和数组。

1、元祖类型

元祖类型是将多个其它类型的数值组合进一个复合类型的主要方式。元祖类型的长度声明后就无法变更。

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

上面就是一个元祖类型,变量声明后使用括号并用逗号分隔指定类型来创建一个元组。元祖中每个位置都有一个类型。

如何使用元祖?

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    let (x,y,z) = tup;

    let a = tup.0;
    let b = tup.1;
    let c = tup.2;
}

上面这段代码,就演示了如何在使用元祖 tup ,使用let 和一个模式将tup分成了3个不同的变量x,y,z。这个叫做解构,因为它将一个元祖拆分成了3个部分。除了结构,我们也可以使用(.)后面跟值的索引来获取数值,索引从0开始。

不带任何值的元祖有一个特别的名称,叫做 单元(unit) 元组。这种值和对应的类型都写作()表示空值或者空的返回类型。如果表达式不返回任何其他的值,则会隐式返回单元值。

2、数组类型

另一个复合类型就是数组了。和元祖不一样,数组中每个类型都是相同的。Rust中的数组和其它语言的数组不太一样。Rust中的数组长度是固定的。

fn main() {
    let a = [1,2,3,4,5];
}

当你想要在栈(stack)而不是在堆(heap)上为数据分配空间,或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,那么很可能应该使用 vector。

fn main() {
    let b = [1,1,1,1,1];
    let a: [u8; 5] = [1,1,1,1,1];
    let c = [1; 5];
    
    let x = a[0];
    let y = a[1];
}

上面的代码展示了数组的3种创建方式

第一种是使用Rust默认的也是最简单的创建方式。

第二种是在声明变量后写一个方括号,内部第一个值是数据类型,第二个值是数据个数,然后写入内容。

第三种是在声明变量后,在值处写一个方括号,内部第一个值是每个数的值,第二个值是该值创建多少个。

数组访问方式和其它编程语言差不多,都是在数组变量后写一个方括号,使用索引访问。因为数组是在堆栈上分配已知的固定大小的单个内存块,所以它可以使用索引访问。

当获取的数组索引大于数组长度时,就会报错,所以在用有变动性的值访问数组时,先做判断,因为在编译时Rust不会检测,也无法检测这个错误。