🦀 第02课:变量与数据类型

Rust的类型系统是其安全保证的基石。本课将深入学习Rust的变量绑定机制、可变性控制,以及丰富的内置数据类型。

入门基础 第2/25课

学习目标:掌握变量绑定与可变性、理解基本数据类型、学会类型转换与推断、了解常量与遮蔽

📌 变量绑定:let与可变性

Rust使用let关键字绑定变量。默认情况下,Rust中的变量是不可变的(immutable),这是Rust安全哲学的核心之一。

不可变变量(默认)

fn main() {
    let x = 5;
    println!("x = {}", x);
    // x = 6;  // ❌ 编译错误!不能对不可变变量重新赋值
}
⚠️ 重要:当你尝试修改不可变变量时,编译器会报错cannot assign twice to immutable variable。这不是bug,而是Rust帮你避免意外的修改。

可变变量:mut

fn main() {
    let mut x = 5;
    println!("x = {}", x);
    x = 6;  // ✅ 可以修改
    println!("x = {}", x);
    x = x + 10;  // ✅ 可以多次修改
    println!("x = {}", x);
}
x = 5 x = 6 x = 16

✅ 验证通过

💡 最佳实践:默认使用不可变变量,只在确实需要修改时才加mut。这使代码意图更清晰,也帮助编译器做更好的优化。

🔄 变量遮蔽(Shadowing)

Rust允许用同一个名字声明新变量,新变量会遮蔽(shadow)之前的变量。遮蔽与mut完全不同:

fn main() {
    // 遮蔽允许改变类型
    let x = 5;          // x: i32
    let x = x + 1;      // x: i32, 值为6(新变量遮蔽旧变量)
    let x = x * 2;      // x: i32, 值为12
    println!("x = {}", x);
    
    // 遮蔽可以改变类型!这是与mut的关键区别
    let spaces = "   ";              // spaces: &str
    let spaces = spaces.len();       // spaces: usize
    println!("空格数: {}", spaces);
    
    // 用mut则不能改变类型
    // let mut y = 5;
    // y = "hello";  // ❌ 类型不匹配!
}
x = 12 空格数: 3

✅ 验证通过

🔢 整数类型

Rust提供多种整数类型,区分有符号(i)和无符号(u):

长度有符号无符号范围
8-biti8u8-128~127 / 0~255
16-biti16u16-32768~32767 / 0~65535
32-biti32u32±21亿 / 0~42亿
64-biti64u64±9.2×10¹⁸
128-biti128u128超大范围
架构isizeusize与CPU位数一致
fn main() {
    // 显式类型标注
    let a: i32 = 42;
    let b: u8 = 255;
    let c: i64 = -9223372036854775808;
    
    // 类型推断:默认i32
    let d = 10;  // 推断为i32
    
    // 各种字面量写法
    let dec = 1_000_000;      // 十进制,下划线分隔
    let hex = 0xff;           // 十六进制
    let oct = 0o77;           // 八进制
    let bin = 0b1111_0000;    // 二进制
    let byte = b'A';          // 字节(仅u8)
    
    println!("十进制: {} 十六进制: {} 八进制: {} 二进制: {}", dec, hex, oct, bin);
    println!("字节: {} 字符: {}", byte, byte as char);
    
    // 整数溢出
    // 在debug模式下溢出会panic
    // 在release模式下溢出会回绕(wrap)
    let mut e: u8 = 255;
    // e = e + 1;  // ❌ debug模式会panic!
    // 使用显式方法处理溢出
    e = e.wrapping_add(1);  // 回绕:0
    println!("wrapping_add(1)后: {}", e);  // 0
    
    // 检查溢出
    let (val, overflowed) = 255u8.overflowing_add(1);
    println!("255 + 1 = {} (溢出: {})", val, overflowed);
    
    // 饱和运算
    println!("饱和加法: {}", 255u8.saturating_add(1));  // 255
    
    // usize/isize用于索引和大小
    let arr = [1, 2, 3, 4, 5];
    let len: usize = arr.len();
    println!("数组长度: {}", len);
}
十进制: 1000000 十六进制: 255 八进制: 63 二进制: 240 字节: 65 字符: A wrapping_add(1)后: 0 255 + 1 = 0 (溢出: true) 饱和加法: 255 数组长度: 5

✅ 验证通过

🔢 浮点类型

fn main() {
    let x: f64 = 2.0;       // 64位浮点,默认
    let y: f32 = 3.0;       // 32位浮点
    
    // 浮点运算
    let sum = x + 2.5;
    let product = x * 3.0;
    let quotient = x / 4.0;
    let remainder = 5.0 % 2.0;  // 取余
    
    println!("和: {} 积: {} 商: {} 余: {}", sum, product, quotient, remainder);
    
    // 科学记数法
    let big = 1.0e10;
    let small = 1.0e-10;
    println!("大数: {} 小数: {}", big, small);
    
    // 特殊值
    let inf = f64::INFINITY;
    let neg_inf = f64::NEG_INFINITY;
    let nan = f64::NAN;
    println!("无穷: {} 负无穷: {} NaN: {}", inf, neg_inf, nan);
    
    // ⚠️ 浮点数比较要小心
    let a = 0.1 + 0.2;
    let b = 0.3;
    println!("0.1 + 0.2 == 0.3? {}", a == b);  // false!
    println!("0.1 + 0.2 = {:.17}", a);           // 0.30000000000000004
    
    // 安全的浮点比较
    let epsilon = 1e-10;
    println!("近似相等? {}", (a - b).abs() < epsilon);  // true
}
和: 4.5 积: 6 商: 0.5 余: 1 大数: 10000000000 小数: 0.0000000001 无穷: inf 负无穷: -inf NaN: NaN 0.1 + 0.2 == 0.3? false 0.1 + 0.2 = 0.30000000000000004 近似相等? true

✅ 验证通过

🔵 布尔类型

fn main() {
    let t: bool = true;
    let f: bool = false;
    
    // 布尔运算
    println!("true && false = {}", t && f);   // 与
    println!("true || false = {}", t || f);   // 或
    println!("!true = {}", !t);               // 非
    
    // 比较产生布尔值
    let x = 5;
    println!("5 > 3: {}", x > 3);
    println!("5 == 5: {}", x == 5);
    println!("5 != 3: {}", x != 3);
    
    // 布尔值占1字节
    println!("bool大小: {} 字节", std::mem::size_of::());
    
    // if条件必须是bool
    // let n = 5;
    // if n { }  // ❌ 错误!Rust不会自动转换
    if n != 0 { }  // ✅ 显式比较
}
true && false = false true || false = true !true = false 5 > 3: true 5 == 5: true 5 != 3: true bool大小: 1 字节

✅ 验证通过

🔤 字符类型

fn main() {
    let c: char = 'z';
    let z = 'ℤ';
    let heart = '❤';
    let ferris = '🦀';
    
    println!("字符: {} {} {} {}", c, z, heart, ferris);
    
    // char是4字节(Unicode标量值)
    println!("char大小: {} 字节", std::mem::size_of::());
    
    // Unicode码点
    println!("'A'的码点: {}", 'A' as u32);
    println!("'中'的码点: {}", '中' as u32);
    println!("'🦀'的码点: {}", '🦀' as u32);
    
    // 从码点创建字符
    let from_code = char::from_u32(0x1F980);
    println!("码点0x1F980: {:?}", from_code);  // Some('🦀')
    
    // 字符判断方法
    println!("'0'.is_numeric(): {}", '0'.is_numeric());
    println!("'a'.is_alphabetic(): {}", 'a'.is_alphabetic());
    println!("'中'.is_alphabetic(): {}", '中'.is_alphabetic());
    println!("' '.is_whitespace(): {}", ' '.is_whitespace());
    
    // 遍历字符范围
    for c in 'a'..='f' {
        print!("{} ", c);
    }
    println!();
}
字符: z ℤ ❤ 🦀 char大小: 4 字节 'A'的码点: 65 '中'的码点: 20013 '🦀'的码点: 129024 码点0x1F980: Some('🦀') '0'.is_numeric(): true 'a'.is_alphabetic(): true '中'.is_alphabetic(): true ' '.is_whitespace(): true a b c d e f

✅ 验证通过

📦 复合类型:元组与数组

元组(Tuple)

fn main() {
    // 创建元组
    let tup: (i32, f64, char) = (500, 6.4, '🦀');
    
    // 解构
    let (x, y, z) = tup;
    println!("x={}, y={}, z={}", x, y, z);
    
    // 索引访问(用.不是[])
    println!("第一个: {}", tup.0);
    println!("第二个: {}", tup.1);
    println!("第三个: {}", tup.2);
    
    // 单元素元组需要逗号
    let single = (42,);  // 元组
    let not_tuple = (42); // 只是括号里的数字
    
    // 空元组(单元类型)
    let unit = ();
    println!("单元类型大小: {} 字节", std::mem::size_of::<()>());
    
    // 嵌套元组
    let nested = ((1, 2), (3, 4));
    println!("嵌套: ({}, {}), ({}, {})", nested.0.0, nested.0.1, nested.1.0, nested.1.1);
}
x=500, y=6.4, z=🦀 第一个: 500 第二个: 6.4 第三个: 🦀 单元类型大小: 0 字节 嵌套: (1, 2), (3, 4)

✅ 验证通过

数组(Array)

fn main() {
    // 固定长度,类型相同
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    
    // 访问元素
    println!("第一个: {}", arr[0]);
    println!("最后一个: {}", arr[4]);
    
    // 初始化相同值
    let zeros = [0; 10];  // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    println!("零数组: {:?}", zeros);
    
    // 数组长度
    println!("长度: {}", arr.len());
    
    // 越界访问会panic(编译期检查或运行时检查)
    // let index = 10;
    // println!("{}", arr[index]);  // ❌ 运行时panic!
    
    // 安全访问
    if let Some(val) = arr.get(2) {
        println!("索引2: {}", val);
    }
    if arr.get(10).is_none() {
        println!("索引10不存在");
    }
    
    // 遍历
    for (i, &v) in arr.iter().enumerate() {
        println!("arr[{}] = {}", i, v);
    }
    
    // 可变数组
    let mut mut_arr = [1, 2, 3];
    mut_arr[0] = 10;
    println!("修改后: {:?}", mut_arr);
}
第一个: 1 最后一个: 5 零数组: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 长度: 5 索引2: 3 索引10不存在 arr[0] = 1 arr[1] = 2 arr[2] = 3 arr[3] = 4 arr[4] = 5 修改后: [10, 2, 3]

✅ 验证通过

🔄 类型转换:as关键字

fn main() {
    // 整数之间转换
    let a: i32 = 42;
    let b: i64 = a as i64;
    let c: u8 = a as u8;  // 截断!
    println!("i32->i64: {}, i32->u8: {}", b, c);
    
    // 浮点转整数(截断,不四舍五入)
    let f = 3.99;
    let i = f as i32;
    println!("3.99 as i32 = {}", i);  // 3
    
    // 整数转浮点
    let n = 42;
    let f2 = n as f64;
    println!("42 as f64 = {}", f2);
    
    // char与u32互转
    let ch = '🦀';
    let code = ch as u32;
    let ch2 = char::from_u32(code).unwrap();
    println!("char->u32->char: {} -> {} -> {}", ch, code, ch2);
}
i32->i64: 42, i32->u8: 42 3.99 as i32 = 3 42 as f64 = 42 char->u32->char: 🦀 -> 129024 -> 🦀

✅ 验证通过

⚓ 常量与静态变量

// 常量:编译期确定,不可变,无固定内存地址
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265358979;

// 静态变量:程序整个运行期存在,有固定内存地址
static LANGUAGE: &str = "Rust";
static mut COUNTER: u32 = 0;  // 可变静态变量(unsafe才能访问)

fn main() {
    println!("最大分数: {}", MAX_POINTS);
    println!("π = {}", PI);
    println!("语言: {}", LANGUAGE);
    
    // 常量可以在任何作用域声明
    {
        const LOCAL_CONST: i32 = 42;
        println!("局部常量: {}", LOCAL_CONST);
    }
    // println!("{}", LOCAL_CONST);  // ❌ 超出作用域
    
    // unsafe块中访问可变静态变量
    unsafe {
        COUNTER += 1;
        println!("计数器: {}", COUNTER);
    }
}
最大分数: 100000 π = 3.14159265358979 语言: Rust 局部常量: 42 计数器: 1

✅ 验证通过

⚠️ const vs let:
- const必须标注类型,let可以推断
- const在编译期内联,let在运行期求值
- const命名惯例全大写
- const不能使用mut

🎯 类型推断

fn main() {
    // Rust编译器很聪明,能从上下文推断类型
    let x = 5;           // i32(默认整数类型)
    let y = 2.0;         // f64(默认浮点类型)
    let z = "hello";     // &str
    let v = vec![1,2,3]; // Vec<i32>
    
    // 有时需要手动标注
    let guess: u32 = "42".parse().expect("不是数字");
    println!("解析: {}", guess);
    
    // 或者用类型后缀
    let a = 42i64;       // i64
    let b = 3.14f32;     // f32
    let c = 0xFFu8;      // u8
}

📝 练习

练习1:温度转换

编写函数将华氏温度转为摄氏温度:C = (F - 32) * 5 / 9,用不同整数类型测试。

练习2:数组操作

创建一个包含5个元素的i32数组,计算所有元素的和、最大值和最小值。

练习3:类型转换练习

将字符串"2024"解析为u32,然后转为f64,再计算它的平方根。

练习4:遮蔽vs可变

分别用遮蔽和mut实现:将一个字符串转为其长度,再加10。观察两种方式的区别。

🏆 本课成就

🔒 下一课解锁:控制流与函数 —— Rust的分支、循环与函数定义