【Rust入门练中学】 第3课:数据类型

🦀目标:

掌握标量类型(整数、浮点、布尔、字符)、复合类型(元组、数组),理解类型推断与整数溢出。


一、核心概念

  1. 标量类型:一个值

整数分有符号 i 和无符号 u,按位宽分:i8/i16/i32/i64/i128/isize、u8/u16/u32/u64/u128/usize。

isize 和 usize 的位宽取决于机器(64 位机就是 64 位),常用于索引和长度。

默认整数类型是 i32。

rust 复制代码
let a = 42;          // i32
let b: u8 = 255;     // 显式标注
let c = 0xff;        // 十六进制 255
let d = 0o77;        // 八进制 63
let e = 0b1010;      // 二进制 10
let f = 1_000_000;   // 下划线分隔,便于阅读

浮点有 f32 和 f64,默认 f64(精度更高,速度在现代 CPU 上几乎无差)。

rust 复制代码
let x = 2.0;        // f64
let y: f32 = 3.14;  // f32

布尔:bool,只有 true 和 false。

rust 复制代码
let t = true;
let f: bool = false;

字符:char,用单引号,占 4 字节,是 Unicode 标量值,可以放中文、emoji。

rust 复制代码
let c = 'A';
let z = '中';
let e = '😀';

注意 char 和字符串 &str 不同:'A' 是 char,"A" 是字符串。

  1. 复合类型:多个值

元组:固定长度,元素类型可不同。用 () 创建。

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

// 解构
let (a, b, c) = tup;

// 用点号索引
let first = tup.0;
let second = tup.1;

数组:固定长度,元素类型必须相同。用 \[\] 创建。

rust 复制代码
let arr = [1, 2, 3, 4, 5];
let zeros = [0; 5];          // [0, 0, 0, 0, 0]
let first = arr[0];
let len = arr.len();

数组长度是类型的一部分,i32; 5i32; 3 是不同类型。

如果长度需要动态变化,用 Vec(第 6 课讲)。

  1. 类型推断与显式标注

Rust 通常能推断类型,但有时需要你帮忙:

rust 复制代码
let x = 5;                    // 推断 i32
let y: u64 = 5;               // 显式
let z = "42".parse::<i32>();  // 需要 turbofish 或标注
let z: i32 = "42".parse().unwrap();
  1. 整数溢出

在 debug 模式下,整数溢出会 panic(程序崩溃)。在 release 模式下,会"回绕"(wrapping)。

rust 复制代码
let x: u8 = 255;
let y = x + 1;  // debug: panic;release: 0

想明确控制溢出行为,用这些方法:

rust 复制代码
let x: u8 = 255;
x.checked_add(1);   // None,安全返回
x.wrapping_add(1);  // 0,回绕
x.saturating_add(1); // 255,饱和到最大值
x.overflowing_add(1); // (0, true),返回值加是否溢出

二、多元化练习题(含答案与解读)

🟢 练习 1(基础):标注类型

要求:让以下代码输出 u8 类型的 200,并说明为什么不能写 300。

rust 复制代码
fn main() {
    let x = 200;
    println!("{}", x);
}

参考答案

rust 复制代码
fn main() {
    let x: u8 = 200;
    println!("{}", x);
}

解读:u8 范围是 0 到 255。写 let x: u8 = 300; 会编译报错 literal out of range for u8。编译器在编译期就帮你挡住了越界,这是 Rust 安全性的体现。


🟢 练习 2(基础):元组解构

要求:创建元组 ("Rust", 2015, 1.0),分别用解构和索引两种方式取出三个值并打印。
参考答案

rust 复制代码
fn main() {
    let info = ("Rust", 2015, 1.0);

    // 方式一:解构
    let (name, year, version) = info;
    println!("{} {} {}", name, year, version);

    // 方式二:索引
    println!("{} {} {}", info.0, info.1, info.2);
}

解读:解构适合一次性取出全部;索引适合只取某一个。元组索引从 .0 开始,不是 0


🟡 练习 3(数组操作):求和与长度

要求:定义一个数组 10, 20, 30, 40, 50,打印它的长度和所有元素之和。
参考答案

rust 复制代码
fn main() {
    let arr = [10, 20, 30, 40, 50];
    println!("长度: {}", arr.len());
    println!("和: {}", arr[0] + arr[1] + arr[2] + arr[3] + arr[4]);
}

解读:数组长度用 .len()。这里手动相加只是为了练习索引,实际会用循环(第 5 课讲)。数组越界在 Rust 中会 panic,不会像 C 那样读到垃圾内存。


🟡 练习 4(类型推断):修复编译错误

以下代码无法编译,请指出原因并修复。

rust 复制代码
fn main() {
    let x = 5;
    let y = 2.5;
    let z = x + y;
    println!("{}", z);
}

参考答案

rust 复制代码
fn main() {
    let x = 5.0;      // 改成浮点
    let y = 2.5;
    let z = x + y;
    println!("{}", z);  // 7.5
}

解读:Rust 不会隐式转换数字类型。x 是 i32,y 是 f64,相加报错 cannot add integer and float。必须显式统一类型,比如都用 f64,或用 as 转换:x as f64 + y。


🟡 练习 5(字符 vs 字符串):找出错误

以下代码有几处错误?

rust 复制代码
fn main() {
    let a = 'hello';
    let b = "x";
    println!("{} {}", a, b);
}

参考答案

1 处错误:'hello' 用了单引号但内容是多个字符,char 只能装一个 Unicode 标量值。

正确写法:

rust 复制代码
fn main() {
    let a = 'h';       // char
    let b = "x";       // &str
    println!("{} {}", a, b);
}

或者如果 a 想存字符串:

rust 复制代码
let a = "hello";  // &str

解读:单引号是 char,双引号是字符串。'h' 合法,'hello' 不合法,"x" 合法。编译器报错会提示 "character literal may only contain one codepoint"。


🔴 练习 6(整数溢出):预测结果

在 debug 模式下,以下代码运行结果是什么?

rust 复制代码
fn main() {
    let x: u8 = 250;
    let y = x.wrapping_add(10);
    println!("{}", y);
}

参考答案

输出 4。

解读:wrapping_add 明确表示"回绕"。250 加 10 是 260,超过 u8 最大值 255,回绕后为 260 减 256 等于 4。如果用普通 x + 10,debug 下会 panic,release 下才回绕。用 wrapping_add 则两种模式都回绕,行为一致。


🔴 练习 7(综合):温度转换

要求:

· 定义变量 celsius: f64 = 36.6

· 用公式 fahrenheit = celsius * 1.8 + 32.0 计算华氏温度

· 打印结果,保留一位小数
参考答案

rust 复制代码
fn main() {
    let celsius: f64 = 36.6;
    let fahrenheit = celsius * 1.8 + 32.0;
    println!("{:.1}°C = {:.1}°F", celsius, fahrenheit);
}

输出:

复制代码
36.6°C = 97.9°F

解读:注意 1.8 和 32.0 都写成浮点,避免整数浮点混合。{:.1} 保留一位小数。这个例子综合了类型标注、浮点运算、格式化输出。


🔵 思考题 1:为什么 Rust 不隐式转换数字类型?
参考答案

因为隐式转换是 bug 的温床。

比如 C 语言里 int 和 float 混用会悄悄转换,可能丢失精度或产生意外结果。Rust 选择让转换显式化:x as f64 或 f64::from(x)。这样你每次转换都要思考"这里会不会丢精度",编译器也帮你确保类型一致。

解读:这是 Rust "显式优于隐式"哲学的体现。代价是写代码时多打几个字,收益是类型相关的 bug 大幅减少。


🔵 思考题 2:数组和元组该用哪个?
参考答案

看两点:元素类型是否相同、是否需要按顺序索引。

元组:元素类型可不同,适合表示"一组相关的异构数据",比如 (name, age, score)。语义上更像"打包"。

数组:元素类型必须相同,适合表示"一组同质数据",比如 1, 2, 3, 4, 5。可遍历,有 .len()。

rust 复制代码
let person = ("Alice", 30, 95.5);  // 元组
let scores = [88, 92, 79];          // 数组

解读:实际项目中,长度固定的同质数据用数组,异构数据用元组或结构体(第 7 课讲)。需要动态长度就用 Vec。


🔵 思考题 3:usize 和 u64 有什么区别?
参考答案

u64 永远是 64 位。usize 的位宽取决于目标平台:64 位系统是 64 位,32 位系统是 32 位。

usize 的用途是索引和长度,因为它必须能表示内存中任意位置。arr.len() 返回的就是 usize,arri 的 i 也必须是 usize。

解读:写跨平台代码时,涉及索引和长度一律用 usize,其他数值用 u32、u64 等明确位宽的类型。在 64 位机器上 usize 等于 u64,但语义不同,别混用。


三、知识点总结(第3课)

整数:i8 到 i128、u8 到 u128,加 isize、usize,默认 i32。

浮点:f32、f64,默认 f64。

布尔:bool,true 或 false。

字符:char,单引号,4 字节,可放 Unicode。

元组:(T1, T2, ...),长度固定,类型可不同,用 .0、.1 索引或解构。

数组:T; N,长度固定,类型相同,用 \[\] 索引,.len() 取长度。

类型推断:编译器尽量推断,必要时显式标注或 turbofish。

整数溢出:debug panic,release 回绕;用 checked_、wrapping_ 、saturating_、overflowing_ 明确控制。

无隐式转换:数字类型混合运算需显式 as 或 from。


四、下一课预告

第4课:函数与所有权入门

· 函数定义、参数、返回值

· 表达式 vs 语句

· 所有权的三条规则

· 移动(move)与克隆(clone)


📌 本课心法:

类型是 Rust 的第一道防线。它不会让你悄悄丢精度、悄悄越界、悄悄混用。每次编译通过,都是编译器替你排除了一批潜在 bug。

相关推荐
humors2211 小时前
今年我正在看、看完和还没看的书单(不定期更新)
学习·阅读·读书·书单·书籍
孙启超1 小时前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
泡泡鱼(敲代码中)2 小时前
MySQL 学习笔记:DCL、函数与约束 —— 安全、效率、完整性的三板斧
开发语言·笔记·sql·学习·mysql·gitee
qq_452396232 小时前
第十篇:《异步编程:Tokio 入门与 async/await》
rust
爱吃苹果的日记本2 小时前
数据结构第五课
数据结构·学习
Tairitsu_H3 小时前
[C++] 拷贝构造还是赋值重载?类默认成员函数细节详解
开发语言·c++·类和对象
goodlook01233 小时前
opencode调用本地模型一(1.18.30版本安装)
开发语言
菜鸟~noob2333 小时前
【电子战】第14篇:TOA 定位——圆交汇与到达时间【含matlab代码】
开发语言·matlab
itmigrate4 小时前
Redis 开发高频隐形踩坑记录
开发语言·java-ee