Rust 学习笔记 - 详解数据类型

前言

任何一门编程语言几乎都脱离不了:变量、基本类型、函数、注释、循环、条件判断,这是一门编程语言的语法基础,只有当掌握这些基础语法及概念才能更好的学习 Rust。

标量类型(Scalar Types)

在 Rust 中,标量类型代表单个值,Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。

整数类形

整数是没有小数部分的数字,可以是正数、负数或零。整数类型分为两大类:有符号无符号有符号 整数可以存储包括负数在内的值,而无符号整数只能存储零和正数。

rust 复制代码
fn main() {
   let x: i32 = -123; // 有符号整数
   let y: u32 = 456; // 无符号整数
}

整数类型参照:

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

isizeusize 两种整数类型的位长度取决于所运行的平台,如果是 32 位架构的处理器将使用 32 位长度整型 (即 i32 / u32),64 位架构的处理器上使用 64 为长度整型 (即 i64 / u64)。

整数类型具有明确的大小(取值范围),每个有符号整数类型可以存储 − 2 n − 1 -2^{n-1} −2n−1 到 2 n − 1 − 1 2^{n-1} - 1 2n−1−1,其中 n 为数据类型的位数,每个无符号整数类型可以存储 0 到 2 n − 1 2^{n} - 1 2n−1。

举个🌰:

  • i8 可以存储 − 2 8 − 1 -2^{8-1} −28−1 到 2 8 − 1 − 1 2^{8-1} - 1 28−1−1 的数字,即: -128 到 127。
  • u8 可以存储 0 到 2 8 − 1 2^{8} - 1 28−1 的数字,即: 0 到 255。

字面量表示:

整数字面量可以通过不同的进制来表示,包括十进制、十六进制、八进制、二进制,以及字节(只适用于 u8)。

进制 前缀 示例
十进制 98_222
十六进制 0x 0xff
八进制 0o 0o77
二进制 0b 0b111_0000
字节(u8 专用) b b'A'

注意:在 Rust 中,数字可以使用 _ 作为可视分隔符来提高可读性,编译器在处理数值时会忽略这些 _

浮点类型

浮点数是用来处理带有小数部分的数值。Rust 提供了两种基本的浮点类型,分别是 f32f64,其中 f32 是单精度浮点类型,f64 是双精度浮点类型。Rust 中的浮点类型遵循 IEEE-754 标准。

rust 复制代码
fn main() {
 let x: f32 = 3.14; // f32 单精度浮点数
 let y: f64 = 2.71828; // f64 双精度浮点数
 let z = 2.71828; // 默认是 f64 双精度浮点数
}

f32 类型

f32 类型的浮点数是单精度浮点数,占用 32 位(4 字节)的内存空间。它的范围大约是 1.4E-45 到 3.4E+38,精度大约有 6-7 位十进制数。f32 类型的浮点数对于需要较高性能但不需要非常高精度的场景来说是一个不错的选择,因为它在处理速度和内存使用上都比 f64 更加高效。

f64 类型

f64 类型的浮点数是双精度浮点数,占用 64 位(8 字节)的内存空间。它的范围大约是 4.9E-324 到 1.8E+308,精度大约有 15-16 位十进制数。相比 f32f64 提供了更大的数值范围和更高的精度,适合对数值精度要求较高的计算任务。由于在现代 CPU 架构中,f64 的性能通常也非常高效,所以 Rust 默认的浮点类型就是 f64

浮点数的字面量表示法

浮点数可以使用字面量表示法来表示,例如:

  • 直接给出小数:3.14
  • 使用科学记数法:2.5e102.5E10 表示 2.5 × 10¹⁰
  • 在数字中使用下划线以提高可读性:1_000.75_001

特点和使用注意事项

  • 浮点数类型具备加 (+)、减 (-)、乘 (*)、除 (/)、求余 (%) 等算术运算能力。
  • 浮点数在运算时可能会出现"舍入错误",这是因为许多实数不能被精确表示为 f32f64 类型的数值。这类舍入错误是所有使用 IEEE-754 标准的浮点数表示法的编程语言的通病。
  • 浮点数比较需要特别注意,直接比较两个浮点数是否相等 (==) 可能会因为舍入误差导致不符合预期的结果。在涉及浮点数比较的时候,通常需要判断两个数的差值是否足够小。

布尔类型

布尔(Boolean)类型用 bool 表示,它是最简单的类型,只有两个值:truefalse。布尔类型通常用于执行逻辑操作,条件判断和控制流程(例如,if 条件语句和循环控制)

rust 复制代码
fn main() {
   let t: bool = true;
   let f: bool = false;
}

基础

  • 类型名为 bool
  • 取值范围仅为 truefalse
  • 布尔值用一个字节(1 byte8 bits)来存储

字面量表示法

  • true 表示逻辑真。
  • false 表示逻辑假。

操作

布尔类型支持多种逻辑操作,包括但不限于:

  • 逻辑与 (&&): 当两个操作数都为 true 时,返回 true;否则返回 false
  • 逻辑或 (||): 只要一个操作数为 true,就返回 true;如果两个都为 false,则返回 false
  • 逻辑非 (!): 如果操作数为 true,返回 false;如果为 false,返回 true
rust 复制代码
let a = true;
let b = false;

let and_result = a && b; // 返回 false
let or_result = a || b; // 返回 true
let not_result = !a; // 返回 false

字符类型

字符类型(char)用于表示单个 Unicode 标量值,这意味着它可以表示比 ASCII 更广泛的字符集。在 Rust 中,字符(char)是通过单引号(')来表示的,而字符串(String&str)是通过双引号(")来表示的。

rust 复制代码
fn main() {
   let c: char = 'z';
   let z: char = 'ℤ'; // Unicode值
   let heart_eyed_cat = '😻';
}

基础

  • 类型名为 char
  • char 类型在 Rust 中是四个字节的大小,即 32 位(与 UTF-32 编码相同)。这是因为 char 需要能表示任意一个 Unicode 标量值,其范围从 U+0000U+D7FFU+E000U+10FFFF
  • 每个字符是单独的 char 类型实例,并且占用 4 个字节的存储空间。这与其他一些语言中的字符类型(例如 C/C++ 中的 char)不同,其通常是基于 ASCII 并且仅占用 1 个字节。

字面量表示法

  • 普通字符:'a', 'Z', '7' 等。
  • 特殊字符:可以使用转义序列表示,如换行符 '\n',制表符 '\t',单引号 '\'',反斜杠 '\\' 等。
  • Unicode 字符:使用 \u{} 转义和大括号内的十六进制数值来表示,如 '好' 可以用 '\u{597D}' 表示。

操作

  • char 类型的值可以直接参与比较操作(==, !=, <, >, 等)。
  • char 类型拥有多种方法用于检查字符的属性(例如 is_alphabetic, is_numeric 等)。
rust 复制代码
let c1 = 'A';
let c2 = '\u{597D}'; // 表示 "好"

if c1.is_alphabetic() {
    println!("{} 是字母", c1);
}

if c2.is_numeric() {
    println!("{} 是数字", c2);
}

复合类型(Compound Types)

复合类型可以将多个值组合成一个类型。Rust 主要有两种复合类型:元组(Tuple)和数组(Array)。

元组(Tuple)

元组是可以包含多个不同类型值的一种集合。元组的长度固定,一旦声明,它的长度不会改变。

声明元组

rust 复制代码
let tup: (i32, f64, char) = (500, 6.4, 'y');

在这个例子中,tup 是一个元组,包含了一个 i32 类型的整数、一个 f64 类型的浮点数,以及一个 char 类型的字符。

元组解构

元组可以被解构(destructured),为其内部的每个值匹配一个变量名称。

rust 复制代码
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y); // 输出 6.4

通过索引访问元组元素

也可以使用索引访问元组中的元素。

rust 复制代码
let five_hundred = tup.0;
let six_point_four = tup.1;
let y = tup.2;

数组(Array)

数组是有多个相同类型值组成的集合。与元组一样,数组的长度也是固定的,Rust 的数组一旦声明,它的长度不能改变。

声明数组

rust 复制代码
let a = [1, 2, 3, 4, 5];

这里,a 是一个整型数组,包含五个整数。

带类型注解的数组

你也可以在声明数组时添加类型注解,指明数组中的元素类型以及数组的长度。

rust 复制代码
let a: [i32; 5] = [1, 2, 3, 4, 5];

初始化数组

如果数组中的每个元素都是相同的值,可以使用以下方式进行初始化。

rust 复制代码
let a = [3; 5]; // 等同于 let a = [3, 3, 3, 3, 3];

这里 a 是一个整型数组,包含五个都是数字3的元素。

访问数组元素

通过索引来访问数组中的元素。

rust 复制代码
let first = a[0]; // 访问第一个元素
let second = a[1]; // 访问第二个元素

在访问时,如果索引超出了数组的边界,Rust 会在编译时或运行时(取决于索引是否为常量表达式)抛出错误,这是 Rust 的安全性特性之一。

选择元组还是数组?

  • 当想要在一个复合类型中包含多个不同类型的值时,应当使用元组。
  • 当需要一个包含多个相同类型值的集合时,应当使用数组。

元组非常适合用于有结构的数据,而数组非常适合用于有相同数据类型要求的连续数据序列。通过使用元组和数组,可以创建出符合你需要的各种数据结构。

自定义类型(Custom Types)

自定义类型主要指的是通过使用结构体(struct)和枚举(enum)来创建的数据类型。这两种类型允许开发者定义和使用更丰富且符合业务逻辑的数据结构。

结构体(Struct)

结构体是将零个或多个不同类型的数据聚合到一个复合类型中的一种方式。他们在概念上类似于其他语言中的类(但没有继承功能),是用于创建自定义数据类型的集合。

声明结构体

rust 复制代码
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

在这个例子中,User 结构体包含了四个字段,每个字段都有指定的类型。

实例化结构体

要使用结构体,你需要创建其实例并为其字段提供相应的值。

rust 复制代码
let user1 = User {
    email: String::from("admin@example.com"),
    username: String::from("ziyang"),
    active: true,
    sign_in_count: 1,
};

可以通过派生特性 #[derive(Debug)] 来允许结构体实例在使用 println! 宏时使用 {:?}{:#?} 格式化输出。

枚举(Enum)

枚举允许定义一个类型,它可以是几个不同的变体中的一个。枚举在那些一次只能有一个值从多个可能的值中选取的情况下特别有用。

声明枚举

rust 复制代码
enum IpAddrKind {
   V4,
   V6,
}

这里,IpAddrKind 枚举有两个变体:V4V6

枚举也可以关联数据。

rust 复制代码
enum IpAddr {
    V4(String),
    V6(String),
}

甚至每个枚举变体关联的数据都可以有不同类型。

rust 复制代码
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

枚举类的使用

rust 复制代码
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

match 控制流运算符来处理枚举有助于确保你考虑到了所有可能的情况。

Never (!

Rust 中有一个表示"永不返回"的特殊类型,称作 ! 类型,也就是所谓的 "Never" 类型。这个类型用于那些不返回任何值的函数,或者那些不会正常返回,因为它们会无限循环或者结束当前进程的函数。

Never 类型用于表示永不返回的函数。这有助于 Rust 进行更严格的类型检查和控制流分析。

示例:

rust 复制代码
fn forever() -> ! {
    loop {
        // 无限循环,永不返回
    }
}

在这个示例中,forever 函数有一个 ! 返回类型,表示此函数将永远不会返回一个值。

Never 类型 (!) 的用途

  1. 控制流运算符 :
    ! 类型主要与 Rust 中的 match 表达式一起用于保证所有可能情况都已处理。如果 match 的一个分支结束于一个永不返回的函数,Rust 知道不需要返回值。这就是所谓的"穷尽性检查"(exhaustiveness checking)。

  2. 错误处理 :

    经常与 panic! 宏一起使用,它会使当前线程崩溃,并可以带有一个错误消息。由于 panic! 永远不会返回,它的返回类型是 !

结语

本章深入讲解了 Rust 中的标量类型、复合类型、自定义类型以及特殊的 never 类型。此外,还有指针类型、动态大小类型、函数类型等其他重要的数据类型将在后续文章中陆续进行逐一介绍。

相关推荐
GGBondlctrl15 分钟前
【SpringAOP】Spring AOP 底层逻辑:切点表达式与原理简明阐述
java·后端·代理模式·spring aop·切点表达式
代码驿站52020 分钟前
Scala语言的软件开发工具
开发语言·后端·golang
wlyang66625 分钟前
2. Scala 高阶语法之集合与元组
开发语言·后端·scala
喜欢猪猪30 分钟前
大厂架构之极致缓存策略实战与原理剖析
java·后端·spring
JINGWHALE135 分钟前
设计模式 行为型 责任链模式(Chain of Responsibility Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·责任链模式
AI向前看39 分钟前
PHP语言的函数实现
开发语言·后端·golang
豌豆花下猫1 小时前
Python 潮流周刊#85:让 AI 帮你写出更好的代码(摘要)
后端·python·ai
SyntaxSage1 小时前
Scala语言的面向对象编程
开发语言·后端·golang
Linux520小飞鱼1 小时前
Go语言的循环实现
开发语言·后端·golang
CyberScriptor1 小时前
PHP语言的字符串处理
开发语言·后端·golang