🦀目标:
掌握标量类型(整数、浮点、布尔、字符)、复合类型(元组、数组),理解类型推断与整数溢出。
一、核心概念
- 标量类型:一个值
整数分有符号 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" 是字符串。
- 复合类型:多个值
元组:固定长度,元素类型可不同。用 () 创建。
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; 5 和 i32; 3 是不同类型。
如果长度需要动态变化,用 Vec(第 6 课讲)。
- 类型推断与显式标注
Rust 通常能推断类型,但有时需要你帮忙:
rust
let x = 5; // 推断 i32
let y: u64 = 5; // 显式
let z = "42".parse::<i32>(); // 需要 turbofish 或标注
let z: i32 = "42".parse().unwrap();
- 整数溢出
在 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。