varHarrie / varharrie.github.io

:blue_book: Personal blog site based on github issues.
https://varharrie.github.io
MIT License
3.66k stars 544 forks source link

Rust笔记:从入门到再次入门 #44

Open varHarrie opened 2 years ago

varHarrie commented 2 years ago

笔记内容大多引用于:

1、Cargo常用命令

# 创建项目
cargo new <project_name>

# 创建库
cargo new --lib <library_name>

# 构建项目
cargo build

# 构建并运行项目
cargo run

# 检查代码
cargo check

# 发布构建
cargo build --release

2、变量声明

let a = 5;     // 不可变变量
a = 6;         // Error

let mut b = 5; // 可变变量
b = 6;         // OK

let spaces = "     ";
let spaces = spaces.len(); // Shadowing,隐藏先前的变量
println!("{}", spaces ); // 5

3、基本标量数据类型

(1)整形

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32(默认) u32
64-bit i64 u64
128-bit i128 u128
arch(依赖计算机架构) isize usize
取值范围 ⁍ ~ ⁍ ⁍ ~ ⁍
数字字面值 例子
Decimal(十进制) 98_222
Hex(十六进制) 0xff
Octal(八进制) 0o77
Binary(二进制) 0b1111_0000
Byte(单字节字符,仅限于u8) b'A'

整形溢出:当在 debug 模式编译时,Rust 检查这类问题并使程序 panic。在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装(two’s complement wrapping )的操作。

(2)浮点型

浮点数采用 IEEE-754 标准表示。

长度 类型
32-bit f32 单精度
64-bit f64(默认) 双精度

(3)数值运算

fn main() {
    // 加法
    let add = 5 + 10;

    // 减法
    let subtract = 95.5 - 4.3;

    // 乘法
    let multiple = 4 * 30;

    // 除法
    let divide = 56.7 / 32.2;
    let divide_floored = 2 / 3; // 整数除法会向下取整

    // 取余
    let remain = 43 % 5;
}

(4)布尔型

fn main() {
    let t = true;
    let f: bool = false;
}

(5)字符型

fn main() {
    let char = 'a';
    let emoji = '😻'; // 4 bytes Unicode

    let str = "Hello World";
}

(6)元组类型

fn main() {
    let tup = (500, 6.4, "hello");
    let tup: (i32, f64, &str) = (500, 6.4, "hello");

    // 解构
    let (x, y, z) = tup;
    let (first, _, _) = tup;

    println!("x: {}, y: {}, z: {}", x, y, z);
    println!("x: {}, y: {}, z: {}", tup.0, tup.1, tup.2);
    println!("first: {}", first);
}

(7)数组类型

数组中的每个元素的类型必须相同,数组长度是固定的。

如需不固定长度,使用标准库提供的Vector 类型,一个允许增长和缩小长度的类似数组的集合类型。

fn main() {
    let a = [1, 2, 3, 4, 5];
    let a: [i32; 5] = [1, 2, 3, 4, 5];

    let first = a[0];
    let second = a[1];
}

4、函数

几个概念:

函数由一系列语句组成,最后由一个可选的表达式结束。

fn five () -> i32 {
    5
}

fn add(x: i32, y: i32) -> i32 {
    return x + y;
}

fn foo() -> i32 {
    let x = 5;
    x + 1; // 分号结束是语句,不能作为返回值
    x + 1 // 没有分号,作为表达式
}

fn bar() -> i32 {
    // 代码块也是表达式
    {
        let i = 5;
        i + 2 // 表达式,作为代码块的返回值
    }
}

5、条件

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }

    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}
fn main() {
    let n = 5;
    let m = 10;
    let max = if n > m { n } else { m };

    println!("{}", max);
}

6、循环

(1)loop

fn main() {
    loop {
        println!("again!");
        // break; // 退出循环
        // continue; // 进入下一次循环
    }
}
fn main() {
    'outer: loop {
        loop {
            break 'outer; // 通过循环标签
        }
    }
}
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2; // 返回循环的值
        }
    };

    println!("The result is {}", result);
}

(2)while

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }

    println!("LIFTOFF!!!");
}

(3)for

fn main() {
    let arr = [10, 20, 30, 40, 50];

    for el in arr.iter() { // iter 迭代器每个元素 el 都是引用类型
        println!("the value is: {}", el);
    }
}
fn main() {
  for num in (1..4).rev() { // rev 反转元素顺序
      println!("the value is: {}", num);
  }
}

7、栈(Stack)与堆(Heap)

在很多语言中,你并不需要经常考虑到栈与堆。不过在像 Rust 这样的系统编程语言中,值是位于栈上还是堆上在更大程度上影响了语言的行为以及为何必须做出这样的抉择。我们会在本章的稍后部分描述所有权与栈和堆相关的内容,所以这里只是一个用来预热的简要解释。

栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出(last in, first out)。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 进栈(pushing onto the stack),而移出数据叫做 出栈(popping off the stack)。栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。 堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的,你可以将指针存储在栈上,不过当需要实际数据时,必须访问指针。想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。

入栈比在堆上分配内存要快,因为(入栈时)分配器无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为分配器必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。

访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A,然后再桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。

当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。

跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间,这些问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过明白了所有权的主要目的就是为了管理堆数据,能够帮助解释为什么所有权要以这种方式工作。

上面提到的基础标量数据类型存放在上。其他复杂数据类型存放在上。

8、所有权

所有权是针对堆数据产生的,规则如下:

fn main() {
    // Stack:基础标量类型将进行复制,即 Copy
    let x = 5;
    let y = x;

    println!("x: {}, y: {}", x, y); // OK

    // Heap:复杂数据类型将进行所有权转移,即 Move
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}, world!", s1); // ERROR,s1 已失效

    let s2 = s1.clone(); // 如必须要复制,使用 clone 函数,可能相当消耗资源
}
fn main() {
    let s = String::from("hello");  // s 进入作用域
    takes_ownership(s);             // s 的值移动到函数里
    println!("{}", s);              // ERROR,当前作用域不再有效

    let x = 5;                      // x 进入作用域
    makes_copy(x);                  // x 应该移动函数里,但 i32 是 Copy 的
    println!("{}", x);              // 所以在后面可继续使用 x
}

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法,占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // 这里,some_integer 移出作用域。没有特殊处理

9、引用与借用

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len); // s1 仍然有效
}

fn calculate_length(s: &String) -> usize {
    s.len()
} // 因为没有所有权,所以 s 内存不会释放
fn main() {
    let mut s = String::from("hello");

    change(&mut s); // 传入可变引用
}

fn change(some_string: &mut String) { // 声明可变引用
    some_string.push_str(", world");
}

对同一数据的多个可变引用,作用域不可重叠:

fn main(){
    let mut s = String::from("hello");

    let r1 = &mut s;  // OK
    let r2 = &mut s;  // ERROR,在同一时间只能有一个对某一特定数据的可变引用

    println!("{}, {}", r1, r2);
}
fn main (){
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
    } // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用

    let r2 = &mut s;
}

可变引用与不可变引用作用域不可重叠:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;     // OK
    let r2 = &s;     // OK
    let r3 = &mut s; // ERROR,不可同时拥有可变引用和不可变引用

    println!("{}, {}, and {}", r1, r2, r3);
}
fn main(){
    let mut s = String::from("hello");

    let r1 = &s; // OK
    let r2 = &s; // OK
    println!("{} and {}", r1, r2);
    // 此位置之后 r1 和 r2 不再使用

    let r3 = &mut s; // OK
    println!("{}", r3);
}

10、Slice

fn main(){
    let s = String::from("hello world");
    let len = s.len();

    let hello = &s[0..5];
    let world = &s[6..11];

    // 等价
    let slice = &s[0..2];
    let slice = &s[..2];

    // 等价
    let slice = &s[3..len];
    let slice = &s[3..];

    // 等价
    let slice = &s[0..len];
    let slice = &s[..];
}
fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s); // word 为 s 的一个不可变引用
    s.clear(); // ERROR,clear 尝试获取可变引用,同时存在不可变引用

    println!("the first word is: {}", word); // word 到此依然有效
}

字符串字面值就是 slice,即&str,一个不可变引用。

fn first_word(s: &str) -> &str {
    // ...
}

fn main() {
        let my_string_literal = "hello world";

    let word = first_word(&my_string_literal[0..6]);  // OK
    let word = first_word(&my_string_literal[..]);  // OK
    let word = first_word(my_string_literal);  // OK

    let my_string = String::from("hello world");

    let word = first_word(&my_string[0..6]);  // OK
    let word = first_word(&my_string[..]);  // OK
    let word = first_word(&my_string);  // OK
}

11、结构体

(1)基本用法

// 结构体定义
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // 实例化
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // 属性赋值
    user1.email = String::from("anotheremail@example.com");

    // 从已有的实例创建新实例
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1 // 此时 username、email 所有权转移到 user2 的对应属性中,user1将失效
    };
}

(2)元组结构体

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

(3)类单元结构体

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

(4)结构体方法(关联函数)

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 可实现与属性同名方法
    fn width(&self) -> bool {
        self.width > 0
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    // 使用.调用方法
    println!("The area of the rectangle is {} square pixels.", rect1.area());
}

(5)结构体非关联函数

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    // 使用::调用非关联函数
    let sq = Rectangle::square(3);
}

12、枚举

(1)基本用法

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
}

(2)绑定值

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

fn main() {
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
}

(3)复杂的绑定值、定义枚举方法

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        // 在这里定义方法体
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

13、Match控制流运算符

(1)基本用法

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

// 根据硬币类型,返回美分值
fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {                 // 可以是代码块
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,               // 或者直接返回值
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

(2)绑定值

enum Option<T> {
    None,
    Some(T),  // 绑定泛型值
}

fn main() {
    let nan: Option<i8> = Option::None;
    let num: Option<i8> = Option::Some(1);

    match num {
        Option::None => {
            println!("num is not a number");
        }
        Option::Some(n) => {  // 访问绑定值
            println!("nam value is {}", n);
        }
    }
}

(3)通用匹配模式

fn main() {
    let roll = 9;
    match roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),  // other 通用匹配
        // _ => reroll(),             // 忽略绑定值
        // _ => (),                   // 空处理
    }
}

fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}

14、if let控制流

fn main() {
  // ===== 使用 match =====
    let num = Some(5);
    match num {
        Some(n) => println!("{}", n),
        _ => (),
    }

  // ===== 使用 if let 简写=====
    let num = Some(5);
  if let Some(n) = num {
      println!("{}", n);
  } // 还可以支持 else 处理其他情况
}

待续。。。