Rust程序设计学习笔记(2)--通用编程概念

adtxl
2024-12-09 / 0 评论 / 6 阅读 / 正在检测是否收录...

仅是一些笔记,完整的rust学习文档详见https://rustwiki.org/zh-CN/book/title-page.html

1. 变量和可变性

1.1 变量

在rust中,变量默认都是不可变的,可在变量名前加上mut使变量可变

1.2 常量(constant)

常量是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异。

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
  1. 常量绝不可以加mut,而且常量使用const关键字,而不是let关键字来声明,并且值的类型必须注明
  2. 常量可以在任意作用域内声明,包括全局作用域
  3. 常量只能设置为常量表达式,而不能是函数调用的结果或是只能在运行时计算得到的值。
  4. Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词。
  5. 在声明的作用域内,常量在程序运行的整个过程中都有效。对于应用程序域中程序的多个部分可能都需要知道的值的时候,常量是一个很有用的选择。
  6. 将整个程序中用到的硬编码(hardcode)值命名为常量,对于将该值的含义传达给代码的未来维护者很有用。如果将来需要更改硬编码的值,则只需要在代码中改动一处就可以了。

1.3 遮蔽(shadow)

在Rust中,可以声明和前面变量具有相同的变量名,其中第一个变量被第二个变量遮蔽,这意味着当我们使用变量时我们看到的会是第二个变量的值。可以通过使用相同的变量名并重复使用let关键字来遮蔽变量。

fn main() {
    let x = 5;

    let x = x + 1;    // x=6

    {
        let x = x * 2;        // x=12
        println!("The value of x in the inner scope is: {}", x);
    }

    println!("The value of x is: {}", x);    // x=6
}

请注意,遮蔽就是又创建了一个新变量,和使用mut声明一个可变变量是有本质不同的。所有遮蔽时可以使用let关键字改变变量的类型。

2. 数据类型

Rust 的每个值都有确切的数据类型(data type),该类型告诉 Rust 数据是被指定成哪类数据,从而让 Rust 知道如何使用该数据。在本节中,我们将介绍两种数据类型:标量类型和复合类型。

2.1 标量类型

标量(scalar)类型表示单个值。Rust 有 4 个基本的标量类型:整型、浮点型、布尔型和字符。

2.1.1 整数类型

Rust中内置的整数类型如下,
isize 和 usize 类型取决于程序运行的计算机体系结构,在表中表示为“arch”:若使用 64 位架构系统则为 64 位,若使用 32 位架构系统则为 32 位。

长度有符号类型无符号类型
8 位i8u8
16 位i16u16
32 位i32u32
64 位i64u64
128 位i128u128
archisizeusize

注:在Rust中如果以release的形式进行构建,不会检测整型溢出的问题,此变量的值将不是期望的值。

2.1.2 浮点类型

Rust 的浮点型是 f32 和 f64,它们的大小分别为 32 位和 64 位。默认浮点类型是 f64,因为在现代的 CPU 中它的速度与 f32 的几乎相同,但精度更高。所有浮点型都是有符号的。

2.1.3 数字运算

Rust 的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取模运算。整数除法会向下取整。

2.1.4 布尔类型

和大多数编程语言一样,Rust 中的布尔类型也有两个可能的值:true 和 false。布尔值的大小为 1 个字节。Rust 中的布尔类型使用 bool 声明。

2.1.5 字符类型

Rust 的 char(字符)类型是该语言最基本的字母类型,
char 字面量采用单引号括起来,这与字符串字面量不同,字符串字面量是用双引号括起来。Rust 的字符类型大小为 4 个字节,表示的是一个 Unicode 标量值,这意味着它可以表示的远远不止是 ASCII。标音字母,中文/日文/韩文的文字,emoji,还有零宽空格(zero width space)在 Rust 中都是合法的字符类型。Unicode 值的范围为 U+0000 ~ U+D7FF 和 U+E000~U+10FFFF。

2.2 复合类型

复合类型(compound type)可以将多个值组合成一个类型。Rust 有两种基本的复合类型:元组(tuple)和数组(array)。

2.2.1 元组类型

元组是将多种类型的多个值组合到一个复合类型中的一种基本方式。元组的长度是固定的:声明后,他们将无法增大或缩小。
我们通过在小括号内写入以逗号分隔的值列表来创建一个元组。

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}

该程序首先创建一个元组并将其绑定到变量 tup 上。 然后它借助 let 来使用一个模式匹配 tup,并将它分解成三个单独的变量 x、y 和 z。 这过程称为解构(destructuring),因为它将单个元组分为三部分。最后,程序打印出 y 值,为 6.4。
除了通过模式匹配进行解构外,我们还可以使用一个句点(.)连上要访问的值的索引来直接访问元组元素。例如:

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

没有任何值的元组 () 是一种特殊的类型,只有一个值,也写成 ()。该类型被称为单元类型(unit type),该值被称为单元值(unit value)。如果表达式不返回任何其他值,就隐式地返回单元值。

2.2.2 数组类型

元组不同,数组的每个元素必须具有相同的类型。与某些其他语言中的数组不同,Rust 中的数组具有固定长度。

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

使用方括号编写数组的类型,其中包含每个元素的类型、分号,然后是数组中的元素数,如下所示:

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

以这种方式编写数组的类型看起来类似于初始化数组的另一种语法:如果要为每个元素创建包含相同值的数组,可以指定初始值,后跟分号,然后在方括号中指定数组的长度,如下所示:

let a = [3; 5];        // 等同于let a = [3, 3, 3, 3, 3];

注:如果运行时访问数组越界,Rust可以检测出来,并报panic

3. 函数

Rust 代码中的函数和变量名使用下划线命名法(snake case,直译为蛇形命名法)规范风格。在下划线命名法中,所有字母都是小写并使用下划线分隔单词。

3.1 语句和表达式

函数体由一系列语句组成,也可选择以表达式结尾。目前为止,我们介绍的函数还没有包含结尾表达式,不过你已经看到了表达式作为语句的一部分。因为 Rust 是一门基于表达式(expression-based)的语言,所以这是一个需要理解的重要区别。其他语言没有这样的区别,所以让我们看看语句和表达式分别是什么,以及它们的区别如何影响函数体。

语句(statement)是执行一些操作但不返回值的指令。表达式(expression)计算并产生一个值。

fn main() {
    let y = 6;    //  语句
    let x = (let y = 6);    // 表达式,这里是错误的,因为语句不返回值
}

函数定义也是语句,上面整个例子本身就是一个语句。用来创建新作用域的大括号(代码块) {} 也是一个表达式,例如:

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {}", y);    // y的值为4
}

这个表达式

{
    let x = 3;
    x + 1
}

是一个代码块,在这个例子中计算结果是 4。这个值作为 let 语句的一部分被绑定到 y 上。注意,x + 1 行的末尾没有分号,这与你目前见过的大部分代码行不同。表达式的结尾没有分号。如果在表达式的末尾加上分号,那么它就转换为语句,而语句不会返回值。在接下来探讨函数返回值和表达式时,请记住这一点。

3.2 带有返回值的函数

函数可以向调用它的代码返回值。我们并不会对返回值命名,但要在箭头(->)后声明它的类型。在Rust中,函数的返回值等同于函数体最后一个表达式的值。使用return关键字和指定值,可以从函数中提前返回;但大部分函数隐式返回最后一个表达式。

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {}", x);        // x=6
}

fn plus_one(x: i32) -> i32 {
    x + 1        // 没有分号,这里是一个表达式
}

4. 控制流

Rust 代码中最常见的用来控制执行流的结构是 if 表达式和循环。

4.1 if表达式

一个例子如下

fn main() {
    let number = 3;

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

所有的 if 表达式都以 if 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 number 的值是否小于 5。在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。if 表达式中与条件关联的代码块有时被叫做分支(arm)

fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}

上面的代码将在编译时报错,因为rust不能像大多语言那样,会自动地将非布尔值转换为布尔值。必须自始至终地使用布尔值作为if的条件。

Rust也可以使用else if处理多重条件。

因为if是个表达式,所以也可以在let语句中使用if

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {}", number);        // number=5
}

4.2 循环

Rust 有三种循环:loop、while 和 for。

4.2.1 使用loop重复执行代码

loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。
可以使用 break 关键字来告诉程序何时停止循环。
循环中的 continue 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。
如果存在嵌套循环,break 和 continue 应用于此时最内层的循环。你可以选择在一个循环上指定一个循环标签(loop label),然后将标签与 break 或 continue 一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。下面是一个包含两个嵌套循环的示例:

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("remaining = {}", remaining);
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {}", count);
}

外层循环有一个标签 counting_up,它将从 0 数到 2。没有标签的内部循环从 10 向下数到 9。第一个没有指定标签的 break 将只退出内层循环。break 'counting_up; 语句将退出外层循环。这个代码打印:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2

loop 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果从循环中传递给其它的代码。为此,你可以在用于停止循环的 break 表达式添加你想要返回的值;该值将从循环中返回,以便您可以使用它,如下所示:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

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

当相等时,使用 break 关键字返回值 counter * 2。循环之后,我们通过分号结束赋值给 result 的语句。

4.2.2 while条件循环

在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 break 停止循环。

fn main() {
    let mut number = 3;

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

        number -= 1;
    }

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

4.2.3 使用for遍历集合

使用 for 循环来对一个集合的每个元素执行一些代码。

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

    for element in a {
        println!("the value is: {}", element);
    }
}

for 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如示例 3-3 中使用 while 循环的倒计时例子,大部分 Rustacean 也会使用 for 循环。这么做的方式是使用 Range,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。

下面是一个使用 for 循环来倒计时的例子,它还使用了一个我们还未讲到的方法,rev,用来反转区间(range):

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}
0

评论 (0)

取消