Bootstrap

2024 Rust现代实用教程 流程控制与函数

一、if流程控制与match模式匹配

1.流程控制

Execution Flow(流程)

  • 1.代码执行从上倒下Iine-by-line
  • 2.而我们执行操作时,控制流程可能会改变

主要的流程控制结构
1.顺序结构(Sequential Structure):程序按照代码的顺序一步一步执行,没有跳过或循环。

2.选择结构(Selection Structure):
-根据条件选择不同的路径执行。常见的选择结构有:

  • if语句:根据条件执行不同的代码块。
  • switchi语句(在某些编程语言中):根据不同的条件值执行不同的代码块。

3.循环结构(Iteration Structure):重复执行一段代码,直到满足某个条件为止。常见的循环结构有:

  • for循环:按照指定的次数重复执行一段代码。
  • while循环:在条件为真的情况下重复执行一段代码。
  • do-while循环:类似于while循环,但是保证至少执行一次循环体。

4.跳转结构:控制程序的执行流程跳转到指定的位置。常见的跳转结构有:

  • break语句:终止循环或switch语句的执行。
  • continue语句:跳过当前循环中的剩余代码,进入下一次迭代。
  • goto语句(在某些编程语言中):直接跳转到指定的标签处。

2. IF流程控制

代码一行行执行
执行流程,被If(else)改变
可以嵌套使用,但容易引起可读性问题

在这里插入图片描述

3.match 表达式

match用于模式匹配,允许更复杂的条件和分支。

可以处理多个模式,提高代码的表达力。

·match是表达式,可以返回值。

match value{
	pattern1 =>/code block executed if value matches pattern1,
	pattern2 if condition =>/code block executed if value matches pattern2 and condition is true,
	_=>/code block executed for any other case,
}

IF流程控制与match表达式对比
复杂性:if适用于简单的条件判断,而match更适用于复杂的模式匹配。
表达力:match更灵活,可以处理多个条件和模式,使代码更清晰。
返回值:两者都是表达式,可以返回值,但match通常用于更复杂的场景。

fn main() {
    let age = 50;
    if age < 50 {
        println!("You are young");
    } else {
        println!("You are old");
    }
    // if 的表达能力很弱
    let scores = 70;
    if scores > 90 {
        println!("Good!!!");
    } else if scores > 60 {
        println!("You are OK!");
    } else {
        println!("Bad!!!");
    }
    // 类似C++ 三元表达式
    let msg = if age > 50 { "old" } else { "young" };
    println!("You are {msg}");

    // match
    let num = 90;
    match num {
        80 => println!("80"),
        90 => println!("90"),
        _ => println!("Some else"),
    }
    match num {
        25..=50 => println!("25 ... 50"),
        51..=100 => println!("51 ... 100"),
        _ => println!("Some else"),
    }
    match num {
        25 | 50 | 75 => print!("25 or 50 or 75"),
        100 | 200 => println!("100 or 200"),
        _ => println!("Some else"),
    }
    // match 里面使用if
    match num {
        x if x < 60 => println!("bad"),
        x if x == 60 => println!("luck"),
        _ => println!("Some else"),
    }
    let num = 60;
    let res = match num {
        x if x < 60 => "bad".to_owned(),
        x if x == 60 => "luck".to_owned(),
        _ => "Some else".to_owned(),
    };
    println!("res value : {res}");
}

编译及运行

 cargo run
   Compiling ch11_if_match v0.1.0 (/home/wangji/installer/rust/project/ch11_if_match)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.55s
     Running `target/debug/ch11_if_match`
You are old
You are OK!
You are yong

二、循环与break continue以及与迭代的区别

1.Rust中的循环Loops

Rust提供了几种循环结构,其中最常见的是Ioopwhilefor

  • 1.Ioop循环:
loop{
///无限循环的代码块
}

Ioop创建一个无限循环,可以通过break语句来中断循环。

  • 2.while循环:
while condition{
//条件为真时执行的代码块
}

while循环在每次迭代之前检查一个条件,只有在条件为真时才执行循环体。

  • 3.for循环:
for item in iterable
{
	//遍历可迭代对象执行的代码块
}

for循环用于迭代集合或范围,执行代码块来处理每个元素。

2.break && continue

·break关键字用于立即终止循环,并跳出循环体

  • 可以用于跳出指定标签循环

·continue关键字用于立即跳过当前循环中剩余的代码,直接进入下一次循环

3.迭代

Rust的迭代主要通过迭代器(iterators)来实现。迭代器是一个抽象,它提供了一种访问集合元素的统一方式。

从实现上讲在Rust中,迭代器是一种实现了Iterator trait的类型!

简化源码:

pub trait lterator{
	type Item;
	fn next (&mut self)->Option<Self:Item>;
}

4.循环与迭代的不同

循环适用于需要明确控制循环流程的情况,而迭代器则提供了一种更抽象的方式来处理集合元素。通常,推荐使用迭代器,因为它们可以提高代码的可读性和表达力。

fo循环是一种语法结构,用于遍历集合中的元素。它依赖于集合类型实现Iterator trait.

  • 在Rust中,迭代器提供了一系列用于遍历集合元素的方法,比如next()、mapO、filter)等,可以让我们的代码更具有表达性。

Example

fn main() {
    // loop {
    //     println!("Ctrl+C");
    //     std::thread::sleep(std::time::Duration::from_secs(1));
    // }
    // while循环
    let mut i = 0;
    while i < 10 {
        println!("{}", i);
        i += 1;
    }
    println!("for");

    // for 循环
    let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
    for element in arr {
        println!("{}", element);
    }

    // 打印不包括10
    for i in 0..10 {
        println!("{}", i);
    }
    // 打印包括10
    for i in 0..=10 {
        println!("{}", i);
    }

    // break
    let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
    for element in arr {
        if element == 10 {
            break;
        }
        println!("{element}");
    }
    let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
    for element in arr {
        if element == 10 {
            continue;
        }
        println!("{element}");
    }
    'outer: loop {
        println!("outer");
        loop {
            println!("inner");
            // break;
            break 'outer; //直接跳出外部的循环
        }
    }

    // 循环的写法
    let numbers = [1, 2, 3, 4, 5];
    let mut for_numbers = Vec::new();
    for &number in numbers.iter() {
        let item = number * number;
        for_numbers.push(item);
    }
    println!("for : {:?}", for_numbers);


    // 迭代的写法,其性能与循环的性能差不多
    let numbers = [1, 2, 3, 4, 5].to_vec();
    let iter_number: Vec<_> = numbers.iter().map(|&x| x * x).collect();
    println!("iter : {:?}", iter_number);
}

编译及测试:

 cargo run 
   Compiling ch12_loop_iter v0.1.0 (/home/wangji/installer/rust/project/ch12_loop_iter)
    Building [                             ] 0/1: ch12_loop_iter(bin)                                                                                                                                                                                                  


    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.99s
     Running `target/debug/ch12_loop_iter`
0
1
2
3
4
5
6
7
8
9
for
0
1
2
3
4
5
6
7
8
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
11
outer
inner
for : [1, 4, 9, 16, 25]
iter : [1, 4, 9, 16, 25]

三、函数基础与Copy值参数传递

1.函数的基础知识

1.函数的定义:在Rust中,你可以使用fn关键字声明和定义函数,而main是程序的入口点是一种特殊的函数。
2.参数和返回值
函数可以接受零个或多个参数,每个参数都需要指定类型。
函数可以有返回值,使用->指定返回值类型。如果函数没有返回值,可以使用->()、或省略这部分。
3.调用函数:调用函数时,使用函数名和传递给函数的实际参数。

2.Copy by value

如果数据类型实现Copy特质,则在函数传参时回实现Copy by value操作。

会将实参拷贝为形参,参数改变并不会影响实参。

如果要改变形参,需要加上mut

Struct、枚举、集合等并没有实现copy trait,会实现move操作失去所有权

为数据类型实现copy trait,就可实现copy by value

Example:

fn add(x: i32, y: i32) -> i32 {
    x + y //直接作为返回值进行返回,如果不加;
}

fn change_i32(mut x: i32) {
    x = x + 4;
    println!("fn {x}");
}

// 修改实参
fn modify_i32(x: &mut i32) {
    *x += 4;
}

#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

fn print_point(point: Point) {
    println!("point x {}", point.x);
}

fn main() {
    let a = 1;
    let b = 2;
    let c = add(a, b);
    println!("c: {c}");
    let mut x = 1;
    change_i32(x);
    println!("x {x}");
    modify_i32(&mut x); //变成可变引用
    println!("x {x}");
    let s = Point { x: 1, y: 2 };
    print_point(s); // 由于打了标签#[derive(Copy, Clone)],所以可以直接传值
    println!("{}", s.x);
}

编译及运行:

 cargo run
   Compiling ch13_fn v0.1.0 (/home/wangji/installer/rust/project/ch13_fn)
warning: field `y` is never read
  --> src/main.rs:18:5
   |
16 | struct Point {
   |        ----- field in this struct
17 |     x: i32,
18 |     y: i32,
   |     ^
   |
   = note: `Point` has a derived impl for the trait `Clone`, but this is intentionally ignored during dead code analysis
   = note: `#[warn(dead_code)]` on by default

warning: `ch13_fn` (bin "ch13_fn") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/ch13_fn`
c: 3
fn 5
x 1
x 5
point x 1
1

四、函数值参数的传递、不可变借用参数的传递、可变借用参数的传递

1.函数值参数传递(move)

函数的代码本身通常是存储在可执行文件的代码段,而在调用时函数会在栈,开辟一个新的stack frame(栈空间),用于存储函数的局部变量、参数和返回地址等信息,而当函数结束后会释放该空间。

而当传入non-Copy value(Vec、String等)传入函数时实参会转移value的所有权给形参,实参会失去value的所有权而在函数结束时,value的所有权会释放。

2.不可变借用(就是C++的引用)

如果你不想失去value的所有权,你又没有修改value的需求,你可以使用不可变借用

在Rust中,你可以将不可变引用作为函数的参数,从而在函数内部访问参数值但不能修改它。这有助于确保数据的安全性,防止在多处同时对数据进行写操作,从而避免数据竞争。

如何应用不可变借用

  • Use*to deference,去获取其的值

3.可变借用

如果你有修改值的需求你可以使用可变借用,以允许在函数内部修改参数的值。这允许函数对参数进行写操作,但在同一时间内只能有一个可变引用。

需要在形参前加&mut

如何应用可变借用

  • 同样使用Use*to deference,去获取其的值

Example:

// p1有默认拷贝
// p2没有默认拷贝,所以是move
fn move_func(p1: i32, p2: String) {
    println!("p1 is {}", p1);
    println!("p2 is {}", p2);
}

// borrow引用,但是在这里没意义,对于&i32这种类型而言
fn print_value(value: &i32) {
    println!("{}", value);
}

fn string_func_borrow(s: &String) {
    println!("{}-{}", (*s).to_uppercase(), s.to_uppercase());
}

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn modify_point(point: &mut Point) {
    (*point).x += 2;
    point.y += 2; //等价于(*point).y += 2
}

fn main() {
    let n = 12;
    let s = String::from("oo");
    move_func(n, s);
    println!("n is {}", n);
    // println!("s is {}", s);
    let s = String::from("oo");
    print_value(&n);
    print_value(&n);
    string_func_borrow(&s);
    println!("s is {}", s);
    let mut p = Point { x: 0, y: 0 };
    println!("{:?}", p); //就是调用#[derive(Debug)]特质
    modify_point(&mut p);
    println!("{:?}", p);
}

编译及测试:

 cargo run
   Compiling ch14_fn_move_borrow_mut v0.1.0 (/home/wangji/installer/rust/project/ch14_fn_move_borrow_mut)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/ch14_fn_move_borrow_mut`
p1 is 12
p2 is oo
n is 12
12
12
OO-OO
s is oo
Point { x: 0, y: 0 }
Point { x: 2, y: 2 }

五、函数返回值与所有权机制

1.返回Copy与Non-Copy

都可以返回,但是要注意Non-Copy是在堆上的。

性能:

  • 在一般情况下,返回Copy类型的值通常具有更好的性能。这是因为Copy类型的值是通过复制进行返回的,而不涉及堆上内存的分配和释放,通常是在栈上分配。这样的操作比涉及堆上内存额分配和释放更为高效。

六、高阶函数:函数作为参数与返回值

参考

;