第二章 变量与常见数据类型

97 阅读5分钟

1. 变量与不可变性

1.1 基础知识

  1. 在Rust中,使用let关键字来声明变量

  2. 在Rust支持类型推导,但你也可以显示指定变量的类型:

    • let x: i32 = 5 // 显示指定x的类型为i32
  3. 变量名蛇形命名法(Snake Case),而枚举和结构体命名使用帕斯卡命名法,如果变量没有用到可以前置下划线,消除警告

  4. 强制类型转换 Casting a Value to a Different Type

    • let a = 3.1; let b = a as i32
  5. 打印变量 ({}与{:?}需要实现特质之后章节会介绍,基础类型默认实现)

    • println!("val: {}", x)
    • println!("val: {x}")

1.1 Rust中的变量是默认不可变的

不可变性是Rust实现其可靠性和安全性目标的关键

他迫使程序员更深入地思考程序状态的变化,并明确哪些部分的程序状态可能会发生变化的

不可变性有助于防止一类常见的错误,如数据竞争和并发问题

使用mut关键字进行可变声明:

  • 如果你希望一个变量是可变的,你需要使用mut关键字进行明确声明

    • let mut y = 10; // 可变变量
    • y = 20; // 合法的修改

Shadowing Variables 并不是重新赋值

Rust允许你隐藏一个变量,这意味着你可以声明一个与现有变量同名的新变量,从而有效地隐藏前一个变量。

  • 可以改变值
  • 可以改变类型
  • 可以改变可变性
fn main() {
    // 不可变与命名
    let nice_count = 100;   // 自动推导
    let nice_number: i64 = 54;
    // nice_count = 23; // 变量不可变

    // 声明可变
    let mut count: i32 = 3;
    count = 4;

    // Shadowing 
    let x: i32 = 5;
    {
        // 命名空间
        let x = 10;
        println!("inner x: {}", x) // 10
    } // 内部的x被销毁了
    print!("outer x: {}", x); // 5

    let x: &str = "hello"; // 在同一作用域下重新声明了x, 最终覆盖了之前的x
    println!("New x: {x}"); // hello

    let mut x = "this"; // 重新定义可变性
    println!("x: {x}"); // this
    x = "that";
    println!("x: {x}"); // that
}

2. 常量const与静态变量static

2.1 常量

  • 常量的值必须是在编译时已知的常量表达式,必须指定类型与值
  • 与C语言的宏定义(宏替换)不同,Rust的const常量的值被直接嵌入到生成的底层机器代码中,而不是进行简单的字符替换
  • 常量名与静态变量命名必须全部大写,单词之间加入下划线
  • 常量的作用域是块级作用域,它们只在声明它们的作用域内可见

2.2 static静态变量

  • 与const常量不同,static变量是在运行时分配内存的
  • 并不是不可变的,可以使用unsafe修改
  • 静态变量的生命周期为整个程序的运行时间
static MY_STATIC: i32 = 42;
static mut MY_MUT_STATIC: i32 = 42;

fn main() {
    // const
    const SECOND_HOUR: usize = 3_600; // 常量必须规定类型和值
    const SECOND_DAY: usize = 24 * SECOND_HOUR; // 编译的时候就已经确定了值

    {
        const SE: usize = 1_1000;
        println!("{SE}");
    }
    // println!("{SE}"); // 无法打印, const无法超出它的作用域

    println!("{SECOND_DAY}");

    println!("{MY_STATIC}");
    unsafe {
        MY_MUT_STATIC = 32;
        println!("{MY_MUT_STATIC}");
    }
    // println!("{MY_MUT_STATIC}"); // 这里已经不能进行打印了, 只能在unsafe里面打印
}

3. Rust基础数据类型

  • Integer types默认推断为i32

    • i8, i16, i32, i64, i128
  • Unsigned Integer Types

    • u8, u16, u32, u64, u128
  • Platform-Specific Integer Type(由平台决定)

    • usize
    • isize
  • Float Types

    • f32与f64
    • 尽量用f64, 除非你清楚边界需要空间
  • Boolean Values

    • true
    • false
  • Character Types

    • Rust支持Unicode字符
    • 表示char类型使用单引号
fn main() {
    // 进制的字面量
    let a1 = -125;  // 10进制
    let a2 = 0xFF;  // 16进制
    let a3 = 0o13;  // 8进制
    let a4 = 0b10;  // 2进制
    println!("{a1} {a2} {a3} {a4}");   

    // Max Min
    println!("u32 max: {}", u32::MAX);  // 2^32 - 1
    println!("u32 min: {}", u32::MIN);  // 0

    println!("isize is {} bytes", std::mem::size_of::<isize>());    // 8
    println!("usize is {} bytes", std::mem::size_of::<usize>());    // 8

    // float
    let f1: f32 = 1.23234;
    let f2: f64 = 9.88888;
    println!("Float are {:.2} {:.2}", f1, f2); // 1.23 9.89
    
    // bool
    let is_ok = true;
    let can_ok = false;
    println!("{is_ok} {can_ok}"); // true false
    // 与,或运算
    println!("is_ok or can_ok: {}", is_ok || can_ok); // true
    println!("is_ok and can_ok: {}", is_ok && can_ok); // false

    // char 
    let char_c = 'c';
    println!("{char_c}");
}

4. 元组与数组

4.1 相同点

  • 元组和数组都是Compound Types(复合类型),而Vec和Map都是Collection Types(集合类型)
  • 元组和数组长度都是固定的

4.2 不同点

  • Tuples不同类型的数据类型
  • Arrays同一类型的数据类型

4.3 数组

  • 数组是固定长度的同构集合

  • 创建方式:

    • [a, b, c]
    • [value; size]
  • 获取元素: arr[index]

  • 获取长度: arr.len()

fn main() {
    // array
    let mut arr = [11, 12, 34];
    arr[0] = 999; // 修改数组元素
    println!("arr len {}, first element is {}", arr.len(), arr[0]); // 3, 999

    // 遍历
    for i in arr {
        println!("{}", i);
    }

    let arr2: [i32; 3] = [11; 3];
    for i in arr2 {
        println!("{}", i); // 3个11
    }
}

4.4 元组

  • 元组是固定长度的异构集合
  • Empty tuple()
    • 为函数返回默认值
  • 元组获取元素
    • tup.index
    • 没有len()
fn main() {
    // tuple
    let tup = (0, "hi", 3.4);
    println!("tup elements {} {} {}", tup.0, tup.1, tup.2); // 0 hi 3.4

    let mut tup2 = (0, "hi", 3.4);
    tup2.1 = "f"; // 加上mut后元组的元素就可以进行修改, 必须和修改前的元素是同一类型
    println!("tup2 elements {} {} {}", tup2.0, tup2.1, tup2.2); // 0 f 3.4

    // 空元组
    let tup3 = ();
    println!("tup3 {:?}", tup3); // ()
}

4.5 Ownership所有权机制

类型基础类型与数组、元组,它们和String数据类型的不同

  • 基础类型与数组、元组:实现了copy的操作
  • String、结构体:没有实现copy的特质,只能实现所有权(ownership)的转移, 也就是move操作
fn main() {
    // ownership
    let arr_item: [i32; 3] = [1, 2, 3];
    let tup_item: (i32, &'static str) = (2, "ff");
    println!("arr {:?}", arr_item); // [1, 2, 3]
    println!("tup {:?}", tup_item); // (2, "ff")

    let arr_ownership: [i32; 3] = arr_item;
    let tup_ownership: (i32, &'static str) = tup_item;
    println!("arr {:?}", arr_item); // [1, 2, 3]
    println!("tup {:?}", tup_item); // (2, "ff")

    let a = 3;
    let b = a; // a在赋值给b后, a还是存在的(copy操作)
    println!("{a}"); // 3

    // move ownership
    // 什么数据类型会执行move操作: String
    let string_a = String::from("aaa");
    println!("{string_a}"); // "aaa"
    let string_b = string_a; // String类型就把Ownership进行move操作
    // println!("{string_a}"); // borrow of moved value: 'string_a' value borrowed here after move
    println!("{string_b}"); // "aaa"
}