rust笔记-基本数据类型

基本类型

rust 中的数据类型分两种:基本类型和复合类型。基本类型是最小化的原子类型,一般来说无法解构为其他类型。基本类型如下:

  • 数值类型: 有符号整数 (i8, i16, i32, i64, isize)、 无符号整数 (u8, u16, u32, u64, usize) 、浮点数 (f32, f64)、以及有理数、复数
  • 字符串:字符串字面量和字符串切片 &str
  • 布尔类型: true和false
  • 字符类型: 表示单个 Unicode 字符,存储为 4 个字节
  • 单元类型: 即 () ,其唯一的值也是 ()

类型推导和标注

rust是静态类型语言,也就是在编译器必须要能确定所有的变量类型,但是并不是我们在定义变量的时候一定要指定类型,rust的编译器会根据上下文自动推导类型。但是在某些情况下,编译器无法推导出变量的类型,比如函数的参数,这时候就需要我们显示的标注类型。当然除了函数参数,还有其他一些场景,我们看下面的列子

let guess = "42".parse().expect("not a number");

这段代码的目的是从字符串中解析出一个数字,但是编译器无法推导出guess的类型,编译的时候会报错,这时候就需要我们显示的标注类型。

let guess: u32 = "42".parse().expect("not a number");

或者改为下面这样

let guess = "42".parse::<32>().expect("not a number");

整数类型

整数就是没有小数点的数字,有有符号和无符号整数,具体类型如下表:

存储大小 有符号类型 无符号类型
8 bit i8 u8
16 bit i16 u16
32 bit i32 u32
64 bit i64 u64
128 bit i128 u128
跟架构相关 isize usize

isize和usize是平台相关的,也就是取决于程序运行的CPU类型,在32位的平台下,isize和usize都是32位,在64位的平台下,isize和usize都是64位。 Rust 整型默认使用 i32,例如 let i = 1,那 i 就是 i32 类型,因此你可以首选它,同时该类型也往往是性能最好的。isize 和 usize 的主要应用场景是用作集合的索引。

整数溢出

假设有一个 u8 ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生整型溢出。关于这一行为 Rust 有一些有趣的规则:当在 debug 模式编译时,Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 panic(崩溃,Rust 使用这个术语来表明程序因错误而退出)。

在当使用 --release 参数进行 release 模式构建时,Rust 不检测溢出。相反,当检测到整型溢出时,Rust 会按照补码循环溢出(two’s complement wrapping)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 u8 的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是你期望的值。依赖这种默认行为的代码都应该被认为是错误的代码。

要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:

  • 使用 wrapping_* 方法在所有模式下都按照补码循环溢出规则处理,例如 wrapping_add
  • 如果使用 checked_* 方法时发生溢出,则返回 None 值
  • 使用 overflowing_* 方法返回该值和一个指示是否存在溢出的布尔值
  • 使用 saturating_* 方法使值达到最小值或最大值

下面代码演示了wapping_* 方法:

fn main() {
    let a : u8 = 255;
    let b = a.wapping_add(10);
    println!("b is: {}", b); //此处b为9
}

浮点类型

Rust 提供了两种浮点类型: f32 和 f64。默认情况下,Rust 代码使用 f64 类型,在现代CPU中它的速度和f32几乎相同,精度比 f32 类型要好。浮点类型根据IEEE 754标准定义, f32是单精度浮点类型,f64是双精度浮点类型。

浮点数的限制
由于浮点数是二进制表示的,实际我们想要表达的数一般都是十进制数,而有些浮点数在二进制中并不存在,比如:0.1在十进制是明确存在的,但是在二进制中不存在精确的表达式,因此浮点数存在精度损失的问题。当然如果如果不限制精度,那么可以表示的数就更多了,但是这样就会导致计算机存储的数过大,影响计算机的性能。所以记住浮点数很多是近似的,不要期望它和十进制数完全一样。
基于以上原因在使用浮点数时,需要记住以下两点:

  • 避免在浮点数上进行比较,因为它们可能不是精确的
  • 当结果在数学上可能未定义时,尽量不要使用浮点数

我们看个例子:

fn main() {
    let abc: (f32, f32, f32) = (0.1, 0.2, 0.3);
    let xyz: (f64, f64, f64) = (0.1, 0.2, 0.3);

    println!("abc (f32)");
    println!("   0.1 + 0.2: {:x}", (abc.0 + abc.1).to_bits());
    println!("         0.3: {:x}", (abc.2).to_bits());
    println!();

    println!("xyz (f64)");
    println!("   0.1 + 0.2: {:x}", (xyz.0 + xyz.1).to_bits());
    println!("         0.3: {:x}", (xyz.2).to_bits());
    println!();

    assert!(abc.0 + abc.1 == abc.2);
    assert!(xyz.0 + xyz.1 == xyz.2);
}

程序输出如下:

abc (f32)
   0.1 + 0.2: 3e99999a
         0.3: 3e99999a

xyz (f64)
   0.1 + 0.2: 3fd3333333333334
         0.3: 3fd3333333333333

thread 'main' panicked at 'assertion failed: xyz.0 + xyz.1 == xyz.2',
➥ch2-add-floats.rs.rs:14:5
note: run with `RUST_BACKTRACE=1` environment variable to display
➥a backtrace

对 f32 类型做加法时,0.1 + 0.2 的结果是 3e99999a,0.3 也是 3e99999a,因此 f32 下的 0.1 + 0.2 == 0.3 通过测试,但是到了 f64 类型时,结果就不一样了,因为 f64 精度高很多,因此在小数点非常后面发生了一点微小的变化,0.1 + 0.2 以 4 结尾,但是 0.3 以3结尾,这个细微区别导致 f64 下的测试失败了,并且抛出了异常。

NaN

对于数学上未定义的结果,例如对负数取平方根 -42.1.sqrt() ,会产生一个特殊的结果:Rust 的浮点数类型使用 NaN (not a number)来处理这些情况。
所有跟 NaN 交互的操作,都会返回一个 NaN,而且 NaN 不能用来比较,下面的代码会崩溃:

fn main() {
  let x = (-42.0_f32).sqrt();
  assert_eq!(x, x);
}

出于防御性编程的考虑,可以使用 is_nan() 等方法,可以用来判断一个数值是否是 NaN :

fn main() {
    let x = (-42.0_f32).sqrt();
    if x.is_nan() {
        println!("未定义的数学行为")
    }
}

数字运算

rust 提供了丰富的数字运算符,包括加减乘除、取模、幂运算等,具体如下表:

运算符 描述
+

详细可参考此链接:
https://course.rs/appendix/operators.html#运算符

下面看一个运算的例子:

fn main() {
  // 编译器会进行自动推导,给予twenty i32的类型
  let twenty = 20;
  // 类型标注
  let twenty_one: i32 = 21;
  // 通过类型后缀的方式进行类型标注:22是i32类型
  let twenty_two = 22i32;

  // 只有同样类型,才能运算
  let addition = twenty + twenty_one + twenty_two;
  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,
  ];

  // 打印数组中第一个值,并控制小数位为2位
  println!("{:.2}", forty_twos[0]);
}

位运算

Rust的位运算基本上和其他语言一样,只是没有++、--这样的自增自减运算符。支持的位运算如下表:

运算符 描述 说明
& 按位与 相同位置均为1时则为1,否则为0
^ 按位异或 相同位置不相同则为1,相同则为0
<< 左移 左移运算符将值的所有位向左移动指定的位数(在右边用 0 填充)
>> 右移 右移运算符将值的所有位向右移动指定的位数(在左边用 0 填充)
! 按位取反 按位取反运算符~对值的每个二进制位进行取反操作

下面是位运算的例子:

fn main() {
    // 二进制为00000010
    let a:i32 = 2;
    // 二进制为00000011
    let b:i32 = 3;

    println!("(a & b) value is {}", a & b);

    println!("(a | b) value is {}", a | b);

    println!("(a ^ b) value is {}", a ^ b);

    println!("(!b) value is {} ", !b);

    println!("(a << b) value is {}", a << b);

    println!("(a >> b) value is {}", a >> b);

    let mut a = a;
    // 注意这些计算符除了!之外都可以加上=进行赋值 (因为!=要用来判断不等于)
    a <<= b;
    println!("(a << b) value is {}", a);
}

布尔类型

Rust 提供了两个布尔类型: truefalse。布尔值占用 1 个字节。下面是使用bool类型的例子:

fn main() {
  let is_active = true;
  let is_verified = false;

  println!("is_active is {}", is_active);
  println!("is_verified is {}", is_verified);
}

字符类型

Rust 提供了 char 类型来代表一个 Unicode 字符。Unicode 是一种可以表示世界上所有语言的字符系统。下面是使用char类型的例子:

fn main() {
  let c = 'z';
  let z = 'Z'; // 大写 Z
  let heart_eyed_cat = '😻';

  println!("c is {}", c);
  println!("z is {}", z);
  println!("heart_eyed_cat is {}", heart_eyed_cat);
}

Rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。Unicode 值的范围从 U+0000 ~ U+D7FF 和 U+E000 ~ U+10FFFF。不过“字符”并不是 Unicode 中的一个概念,所以人在直觉上对“字符”的理解和 Rust 的字符概念并不一致。由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节。Rust 的字符只能用 '' 来表示, "" 是留给字符串的。

单元类型

Rust 提供了 () 类型,它被称为“单元类型”,因为它的值就是 ()。() 类型只占用一个字节。单元类型的用途可以作为函数的返回值类型,表示函数没有返回值。也可以作为 0 个参数的函数的参数类型,表示函数不接受参数,也可以作为map的值,表述我们不关注具体的值,值关注key;当然它的作用不仅仅只有这些场景。需要我们在后续的编程实践中去探索,下面是使用()类型的例子:

fn main() {
  let unit = ();
  println!("{}", unit);
}

序列

Rust提供了一种方式用于生成连续的数值,例如需要生成 1 到 10 之间的数字,可以使用 1..11 的方式,其中 .. 表示范围,1 表示范围开始,11 表示范围结束。如下代码,在for循环中,i 表示范围,i 每次自增1,直到 i 等于 11 时循环结束。

fn main() {
  for i in 1..11 {
    println!("{}", i);
  }
}

序列只允许生成数字或者字符类型,原因是它们要可以连续,而且在编译器就要能检查该序列是否为空,字符和数字值是rust中仅有的可以判断是否为空的类型,下面是一个使用字符类型序列的例子:

fn main() {
  for c in 'a'..='z' {
    println!("{}", c);
  }
}

类型转换

Rust 提供了多种方法来将一种整型类型转换成另一种整型类型,具体如下表:

转换方法 描述
as 操作符 用于不同类型之间的转换
TryInto 用于不同类型之间的转换,如果转换失败,则返回错误
ToXXX 用于不同类型之间的转换,如果转换失败,则 panic

关于类型转换的话题,有很多值得关注的点,将在后续章节中进行详细介绍。

有理数和复数

Rust 标准库中没有直接提供有理数和复数类型,但是可以通过标准库中的 num 模块中的 rationalcomplex 模块来使用有理数和复数。
按照下面方法使用num库:

  • 在Cargo.toml中添加依赖项:num = "0.4.0"

下面是有理数和复数的例子:

use num::rational;
use num::complex;

fn main() {
    let r = rational::Rational::new(1, 2);
    println!("r = {}", r);

    let c = complex::Complex::new(1.0, 2.0);
    println!("c = {}", c);
}

小结

rust中的数值类型和运算跟其他语言类似,但也有差异,使用时注意以下几点:

  • 需要熟悉所使用的类型占的字节数,明确各类型的表示范围,使用浮点数的时候需要注意精度丢失问题
  • 类型转换必须是显式的,类型转换时,需要明确转换的方向,以及转换失败的处理
  • rust的数值上可以使用方法:例如可以下面方法将13.14转换为整数:13.14_f32.round(),在这里我们使用了类型后缀,因为编译器需要知道 13.14 的具体类型
posted @   NoodlesYang  阅读(80)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示