Skip to content

variables.rs

文件信息

  • 📄 原文件:01_variables.rs
  • 🔤 语言:rust

完整代码

rust
// ============================================================
//                      变量与数据类型
// ============================================================
// Rust 是静态类型语言,编译器可以推断大部分类型
// Rust 的变量默认是不可变的(immutable),这是 Rust 安全性的基石
// 使用 mut 关键字使变量可变

fn main() {
    println!("=== 变量声明 ===");

    // ----------------------------------------------------------
    // 1. 不可变变量(默认)
    // ----------------------------------------------------------
    // 使用 let 声明变量,默认不可变
    // 【重要】Rust 默认不可变是有意为之,鼓励写出更安全的代码

    let x = 5;
    println!("不可变变量 x = {}", x);
    // x = 6;  // 错误!不可变变量不能重新赋值

    // ----------------------------------------------------------
    // 2. 可变变量(mut)
    // ----------------------------------------------------------
    // 使用 mut 关键字声明可变变量
    // 【建议】只有确实需要修改时才用 mut,减少 mut 使用是好习惯

    let mut y = 10;
    println!("可变变量 y = {}", y);
    y = 20;
    println!("修改后 y = {}", y);

    // ----------------------------------------------------------
    // 3. 变量遮蔽(Shadowing)
    // ----------------------------------------------------------
    // 可以用 let 重新声明同名变量,这叫遮蔽(shadowing)
    // 【与 mut 的区别】遮蔽创建了一个全新的变量,可以改变类型
    // 【适用场景】类型转换时非常有用,避免起 name_str、name_len 这样的名字

    let z = 5;
    let z = z + 1;       // 遮蔽:z 从 5 变成 6
    let z = z * 2;       // 再次遮蔽:z 从 6 变成 12
    println!("遮蔽后 z = {}", z);

    // 遮蔽可以改变类型(mut 做不到)
    let spaces = "   ";           // &str 类型
    let spaces = spaces.len();    // usize 类型,同名但不同类型
    println!("spaces 长度 = {}", spaces);

    // 如果用 mut 就不行:
    // let mut spaces = "   ";
    // spaces = spaces.len();  // 错误!类型不匹配

    // ----------------------------------------------------------
    // 4. 常量(const)
    // ----------------------------------------------------------
    // 使用 const 声明,必须标注类型,值必须是编译时常量
    // 【命名规范】全大写 + 下划线(SCREAMING_SNAKE_CASE)
    // 【与 let 的区别】
    //   - const 必须标注类型
    //   - const 不能用 mut
    //   - const 必须是编译时常量表达式
    //   - const 可以在任何作用域声明,包括全局

    const MAX_POINTS: u32 = 100_000;
    const PI: f64 = 3.141_592_653_589_793;
    println!("常量 MAX_POINTS = {}", MAX_POINTS);
    println!("常量 PI = {}", PI);

    // 【技巧】数字字面量可以用下划线分隔增加可读性
    let big_number = 1_000_000;
    println!("大数字 = {}", big_number);

    // ============================================================
    //                      基本数据类型
    // ============================================================
    println!("\n=== 基本数据类型 ===");

    // ----------------------------------------------------------
    // 整数类型
    // ----------------------------------------------------------
    // 有符号: i8, i16, i32, i64, i128, isize
    // 无符号: u8, u16, u32, u64, u128, usize
    //
    // isize/usize: 取决于平台,64位系统就是64位
    // 【默认】整数字面量默认是 i32
    // 【建议】一般用 i32,索引用 usize

    let a: i8 = 127;              // -128 到 127
    let b: i16 = 32_767;          // -32768 到 32767
    let c: i32 = 2_147_483_647;   // 约 21 亿(默认整数类型)
    let d: i64 = 9_223_372_036_854_775_807;
    let e: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727;

    println!("i8={}, i16={}, i32={}", a, b, c);
    println!("i64={}", d);
    println!("i128={}", e);

    let u: u8 = 255;
    let v: usize = 42;  // 平台相关,常用于索引
    println!("u8={}, usize={}", u, v);

    // 【技巧】整数字面量的多种写法
    let decimal = 98_222;       // 十进制
    let hex = 0xff;             // 十六进制
    let octal = 0o77;           // 八进制
    let binary = 0b1111_0000;   // 二进制
    let byte_val = b'A';        // 字节(仅 u8)
    println!("十进制={}, 十六进制={}, 八进制={}, 二进制={}, 字节={}",
             decimal, hex, octal, binary, byte_val);

    // 【警告】整数溢出
    // Debug 模式下会 panic,Release 模式下会回绕(wrapping)
    // 如果需要明确的溢出行为,使用 wrapping_*、checked_*、overflowing_*、saturating_* 方法
    let max_u8: u8 = 255;
    println!("u8 wrapping_add: {}", max_u8.wrapping_add(1));     // 0
    println!("u8 saturating_add: {}", max_u8.saturating_add(1)); // 255
    println!("u8 checked_add: {:?}", max_u8.checked_add(1));     // None

    // ----------------------------------------------------------
    // 浮点类型
    // ----------------------------------------------------------
    // f32: 单精度,约 6-7 位有效数字
    // f64: 双精度,约 15 位有效数字
    //
    // 【默认】浮点字面量默认是 f64
    // 【建议】默认使用 f64,在现代 CPU 上速度与 f32 相当

    let f1: f32 = 3.14;
    let f2: f64 = 3.141_592_653_589_793;
    println!("f32={:.7}, f64={:.15}", f1, f2);

    // 【警告】浮点精度问题
    println!("0.1 + 0.2 = {:.20}(不等于 0.3)", 0.1_f64 + 0.2_f64);

    // ----------------------------------------------------------
    // 布尔类型
    // ----------------------------------------------------------
    // 只有 true 和 false,占 1 字节
    // 【注意】不能用数字代替布尔值(不像 C/C++)

    let is_active: bool = true;
    let is_deleted = false;  // 类型推断
    println!("is_active={}, is_deleted={}", is_active, is_deleted);

    // ----------------------------------------------------------
    // 字符类型(char)
    // ----------------------------------------------------------
    // 4 字节大小,表示一个 Unicode 标量值
    // 使用单引号 '' 表示
    // 【重要】char 不是 1 字节,是 4 字节(与 C/C++ 不同)
    // 范围:U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF

    let c1 = 'z';
    let c2 = '中';
    let c3 = '🦀';  // Rust 的吉祥物螃蟹
    println!("字符: {} {} {}", c1, c2, c3);
    println!("char 大小 = {} 字节", std::mem::size_of::<char>());

    // ============================================================
    //                      复合类型
    // ============================================================
    println!("\n=== 复合类型 ===");

    // ----------------------------------------------------------
    // 元组(Tuple)
    // ----------------------------------------------------------
    // 固定长度,元素类型可以不同
    // 【用途】从函数返回多个值、临时分组数据

    let tup: (i32, f64, bool) = (500, 6.4, true);

    // 解构元组
    let (tx, ty, tz) = tup;
    println!("解构: x={}, y={}, z={}", tx, ty, tz);

    // 索引访问(从 0 开始,使用点号)
    println!("索引访问: {}, {}, {}", tup.0, tup.1, tup.2);

    // 单元元组(unit type):() 表示空值/无返回值
    let _unit: () = ();

    // ----------------------------------------------------------
    // 数组(Array)
    // ----------------------------------------------------------
    // 固定长度,所有元素类型相同,存储在栈上
    // 【与 Vec 的区别】数组长度固定,Vec 可变
    // 【适用场景】已知元素数量且不会变化时使用数组

    let arr = [1, 2, 3, 4, 5];
    println!("数组: {:?}", arr);
    println!("第一个元素: {}", arr[0]);
    println!("数组长度: {}", arr.len());

    // 声明类型和长度
    let arr2: [i32; 5] = [1, 2, 3, 4, 5];
    println!("显式类型数组: {:?}", arr2);

    // 用相同值填充
    let arr3 = [0; 10];  // 创建长度为 10、全部为 0 的数组
    println!("填充数组: {:?}", arr3);

    // 【安全】数组越界会在运行时 panic(不像 C/C++ 的未定义行为)
    // let bad = arr[10];  // 编译通过但运行时 panic: index out of bounds

    // ----------------------------------------------------------
    // 字符串(String 和 &str)
    // ----------------------------------------------------------
    // &str: 字符串切片,不可变引用,通常是字面量
    // String: 堆上分配,可增长的字符串
    // 【重要区别】
    //   - &str 是借用,String 是拥有
    //   - 字面量 "hello" 是 &str 类型
    //   - String 可以修改,&str 不能

    let s1: &str = "你好,世界";          // 字符串切片(存储在二进制文件中)
    let s2: String = String::from("你好"); // 堆上分配的 String
    let s3: String = "世界".to_string();  // 另一种创建 String 的方式

    println!("&str: {}", s1);
    println!("String: {} {}", s2, s3);

    // String 可以拼接
    let mut s4 = String::from("Hello");
    s4.push(' ');           // 追加字符
    s4.push_str("Rust!");   // 追加字符串
    println!("拼接: {}", s4);

    // 【技巧】format! 宏拼接(不会获取所有权)
    let greeting = format!("{}, {}!", s2, s3);
    println!("format!: {}", greeting);

    // 【注意】Rust 字符串是 UTF-8 编码
    // 不能直接用索引访问(因为字符可能是多字节的)
    // s1[0]  // 错误!
    // 正确的遍历方式:
    print!("遍历字符: ");
    for ch in "Rust语言".chars() {
        print!("{} ", ch);
    }
    println!();

    print!("遍历字节: ");
    for byte in "Rust".bytes() {
        print!("{} ", byte);
    }
    println!();

    // ============================================================
    //                      类型转换
    // ============================================================
    println!("\n=== 类型转换 ===");

    // Rust 没有隐式类型转换,必须使用 as 关键字显式转换
    // 【注意】as 转换可能会截断或丢失精度

    let integer: i32 = 42;
    let float: f64 = integer as f64;
    println!("i32 -> f64: {} -> {}", integer, float);

    let float2: f64 = 3.99;
    let integer2: i32 = float2 as i32;  // 截断,不是四舍五入
    println!("f64 -> i32: {} -> {}(截断)", float2, integer2);

    let big: i32 = 256;
    let small: u8 = big as u8;  // 溢出截断
    println!("i32 -> u8: {} -> {}(溢出截断)", big, small);

    // 字符与数字转换
    let ch = 'A';
    let code = ch as u32;
    println!("char -> u32: '{}' -> {}", ch, code);

    // 【推荐】更安全的转换方式
    // - TryFrom/TryInto: 可能失败的转换,返回 Result
    // - From/Into: 一定成功的转换
    let num: i32 = 42;
    let big_num: i64 = i64::from(num);  // i32 -> i64 一定成功
    println!("From 转换: {} -> {}", num, big_num);

    // TryInto 可能失败
    use std::convert::TryInto;
    let big_val: i64 = 1_000;
    let result: Result<i32, _> = big_val.try_into();
    println!("TryInto 转换: {} -> {:?}", big_val, result);

    // ============================================================
    //                      类型别名
    // ============================================================
    println!("\n=== 类型别名 ===");

    // 使用 type 关键字创建类型别名
    // 【用途】简化复杂类型、增加可读性

    type Meters = f64;
    type Seconds = f64;

    let distance: Meters = 100.0;
    let time: Seconds = 9.58;
    let speed = distance / time;
    println!("速度 = {:.2} 米/秒", speed);

    // 【注意】类型别名不创建新类型,只是另一个名字
    // Meters 和 f64 完全等价,可以互相赋值
    // 如果需要真正的新类型,使用新类型模式(Newtype Pattern):
    // struct Meters(f64);
}

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布