(一)Rust_通用编程概念.md


通用编程概念

前言

又开新坑了,笔记系列主要学习了 Rust 官方的文档,配合 B 站的视频效果更佳。

本章节主要介绍了一些 Rust 通用的基础概念,其实这部分对于已经有编程经验的人来说都很了解,可以快速浏览下了解 Rust 与其他语言的一些细节不同之处。

话不多说,Let’s Go.

变量与可变性

  1. 声明变量使用 let 关键字。
  2. 默认情况下,变量是不可变的。
  3. 使用 mut 标记可变变量。
let x = 5;
println!("The value of x is {}", x);
x = 6;

error[E0384]: cannot assign twice to immutable varia
ble `x`
 --> src/main.rs:6:1
  |
4 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: consider making this binding mutab
le: `mut x`
5 |     println!("The value of x is {}", x);
6 | x = 6;
  | ^^^^^ cannot assign twice to immutable variable

变量与常量

  1. 常量(constant),常量在绑定值以后也是不可变的,但是它与不可变的变量有些区别:
    • 不可以使用 mut, 常量永远都是不可变的。
    • 声明常量使用 const 关键字,它的类型必须显式的标注。
    • 常量可以在任何作用域内声明,包括全局作用域。
    • 常量只可以绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值。
  2. 在程序运行期间,常量在其声明的作用域内一直有效。
  3. 命名规范:Rust 里常量使用全大写字母,每个单词之间用下划线分隔开,例如:const MAX_POINTS: u32 = 100_000;

Shadowing 隐藏

  1. 可以使用相同的名字声明新的变量,新的变量就会 shadow 之前声明的同名变量。

    • 在后续的代码中这个变量名代码的就是新的变量。
    let x = 5;
    println!("The value of x is {}", x); // 5
    //x = 6; error
    let x = 6; //shadowing
    println!("The value of x is {}", x); // 6
    
  2. shadow 和把变量标记为 mut 是不一样的:

    • 如果不使用 let 关键字,那么重新给非 mut 变量进行赋值时会导致编译错误。
    • 而使用 let 声明的同名新变量,也是不可变的。
    • 使用 let 声明的同名新变量,它的类型可以与之前不同。
    let space = "    "; // str
    let space = space.len(); // usize
    println!("now space value is {}", space);
    

数据类型

  1. 标量和复合类型。

  2. Rust 是静态编译语言,在编译时必须知道所有变量的类型:

    • 基于使用的值,编译器通常能够推断出它的具体类型。
    • 但如果可能的类型比较多(例如把 String 转为整数 parse
      方法),就必须添加类型的显式标注,否则编译会报错。
    let guess = "42".parse().expect("Not a number");
    
    error[E0282]: type annotations needed
    --> src/main.rs:16:9
    |
    16 | let guess = "42".parse().expect("Not a numb
    er");
    | ^^^^^ consider giving `guess` a type
    
    // 需要指定类型
    let guess: u32 = "42".parse().expect("Not a number");
    

标量类型

整数类型

  1. 整数类型没有小数部分。

  2. 无符号整数类型以 u 开头。例如 u32 就是一个无符号的整数类型,占据 32 位的空间。

  3. 有符号整数类型以 i 开头。

  4. Rust 整数类型关系:

    length Signed unsigned
    8-bit i8 u8
    16-bit i16 u16
    32-bit i32 u32
    64-bit i64 u64
    128-bit i128 u128
    arch isize usize
  5. isize 与 usize 类型的位数由程序运行的计算机架构所决定:

    • 如果是 64 位计算机,那就是 64 位。
    • 32 位同理。
  6. 整数的字面值,除了 byte 类型外,所有的数值字面类型都允许使用类型后缀:

    • 不清楚应该使用那种类型,可以使用 Rust 默认类型:
    • 整数默认 i32 :速度很快针对性优化。
    Number literals Example
    Decimal 98_222
    Hex 0xff
    Octal 0077
    Binary 0b1111_0000
    Byte(u8 only) b’A’
  7. 整数溢出,在不同的编译模式下有不同效果:

    • 例如在 u8 的范围是 0-255,如果设置为 256:
      • 在 debug 模式下,Rust 会检测整数溢出,发生溢出后程序在运行时就会 panic。
    • 在 release 模式下,Rust 不会检测整数溢出,如果溢出则会进行环绕操作,即 256 变为 0。

浮点类型

  1. Rust 有两种基础的浮点类型,也就是含有小数部分的类型:

    • f32,32 位,单精度。
    • f64,64 位,双精度。
  2. Rust 的浮点类型使用 IEEE-754 标准来进行描述。

  3. f64 是默认类型,因为在现代 CPU 上 f64 和 f32 的速度出啊不多,而且精度更高。

    let x = 2.0; // f64
    let y: f32 = 3.0; // f32
    
  4. 数值操作:+ - * / %

布尔类型

  1. Rust 的布尔类型也只有两个值:true 和 false。
  2. 占用一个字节大小,符号是 bool。

字符类型

  1. Rust 语言中 char 类型被用来描述语言中最基础的单个字符。
  2. 字符类型的字面值使用单引号,占用 4 字节大小。
  3. 是 Unicode 标量值,可以表示比 ASCII 多得多的字符内容:拼音、中日韩文、零长度空白字符、emoji
    表情等。

复合类型

  1. 复合类型可以将多个值放在一个类型里。
  2. Rust 提供里两种基础的复合类型:元组(Tuple)、数组。

Tuple

  1. Tuple 可以将多个类型的多个值放在一个类型里。

  2. Tuple 的长度是固定的:一旦声明就无法改变。

    let tup: (i32, f64, u8) = (500, 6.4, 1);
    println!("{},{},{}", tup.0, tup.1, tup.2);
    
  3. 获取 tuple 的元素值:

    • 可以使用模式匹配来解构(destructure)一个 tuple 进而获取元素的值。
    • 也可以像上边例子中使用元素索引号进行获取。
let (x, y, z) = tup;
println!("x={},y={},z={}", x, y, z); // 500 6.4 1

数组

  1. 数组也可以将多个值放在一个类型里。

  2. 数组中每个元素的类型必须相同。

  3. 数组的长度也是固定的。

    let a = [1, 2, 3, 4, 5];
    
  4. 数组的用处。

    • 如果想要让数据存储在 stack 上而不是 heap,或者想保证固定的元素,这时使用数组更有好处。
    • 数组没有 Vector 灵活:Vector 与数组类似,由标准库提供而数组由预导入模块提供。
    • Vector 长度可变,当不确定使用哪种时,优先使用 Vector。
  5. 数组的类型:

    • 数组的类型表示格式:[类型;长度] ==> let a: [i32; 5] = [1,2,3,4,5];
    • 如果数组中每个元素值都相同的,那么可以使用如下初始化方法:let a = [3; 5]; 等同 let a = [3,3,3,3,3];
  6. 访问数组的元素:

    • 数组是 stack 上分配的单个块的内存。
    • 可以使用索引来访问数组的元素。
    let a = [1, 2, 3, 4, 5];
     println!("a[0] = {}, a[2] = {}", a[0], a[2]); // 1、3
    
    • 如果访问的索引超出来数组的范围,那么:
      • 编译会通过。
      • 运行时会报错(runtime 时会 panic),Rust 不允许其继续访问相应地址的内存。

函数

  1. 声明函数使用 fn 关键字。
  2. 依照惯例,针对函数和变量名,Rust 使用 snake case 命名规范:
    • 所有的字母都是小写的,单词之间使用下划线分隔开。

函数的参数

  1. parameters、arguments,即形参与实参。

  2. 在函数签名里,必须声明每个参数的类型。

    fn main() {
       another_function(5, 6); // argument
    }
    
    // 参数需要指明类型
    // parameter
    fn another_function(x: i32, y: i32) {
       println!("the value of x is: {}", x);
       println!("the value of y is: {}", y);
    }
    

函数体中的语句与表达式

  1. 函数体由一系列语句组成,可选的由一个表达式结束。
  2. Rust 是一个基于表达式的语言。
  3. 语句是执行一些动作的命令。
  4. 表达式会计算产生一个值。
  5. 函数定义也是语句。
  6. 语句不返回值,所以不可以使用 let 将一个语句赋给一个变量。
//函数声明语句
fn function() {
    let y = 5 + 6; //y = 绑定语句 , 5 + 6 不包括分号是表达式

    //let x = (let z = 7); err: 语句不能被赋值

    let x = 5;
    let y = {
        // {} 其中包含 x + 3 是块中的最后一个表达式,相当于返回值
        let x = 1;
        x + 3 // 如果加上分号使其变为语句 x+3; 则返回的知识一个空的 tuple ()
    };

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

函数的返回值

  1. 在符号 -> 后边声明函数返回值的类型,但是不可以为返回值命名。
  2. 在 Rust 里面,返回值就是函数体里面最后一个表达式的值。
  3. 若想提前返回,需要使用 return 关键字,并指定一个值。
    • 大多数函数都是默认使用最后一个表达式为返回值。
fn five() -> i32 {
    5 // 返回最后一个表达式 5
}

fn plus_five(x: i32) -> i32 {
    x + 5 // 返回表达式 x+5 的值,加上分号就变成语句了
}

控制流

if 表达式

  1. if 表达式允许根据条件来执行不同的代码分支。

    • 条件必须是 bool 类型。
  2. if 表达式中,与条件相关联的代码块叫做分支。

  3. 可选的,在后边可以加上一个 else 表达式。

  4. 使用 else if 处理多重条件。

    • 如果使用了多余一个 else if,那么最好使用 match 来重构代码。
    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");
       }
    }
    
  5. 在 let 语句中使用 if:因为 if 是一个表达式,所以可以将它放在 let
    语句中等号右边。

    fn ifvar() {
       let condition = true;
       let number = if condition { 5 } else { 6 }; // 两侧的类型需要一致
    
       println!("The value of number is : {}", number);
    }
    

循环

  1. Rust 提供了三种循环:loop、while 和 for。

loop

  1. loop 关键字告诉 Rust 反复执行一块代码,直到喊停为止。或者使用 break
    来进行中止。

     let mut count = 0;
     let result = loop {
         count = count + 1;
         println!("loop count : {}", count);
    
         if count == 10 {
             break count * 2;
         }
     };
    

while 条件循环

  1. 每次执行循环体前都要判断一次条件。

    
    let mut number = 3;
    
    while number != 0 {
        println!("{}!", number);
        number = number - 1;
    }
    
    println!("LIFTOFF!!!!");
    

for 循环遍历集合

  1. 可以使用 while 或 loop 遍历集合,但是易错且低效。
  2. 使用 for 循环更加简洁紧凑,它可以针对集合中的每个元素来执行一些代码。
let a = [10, 20, 30, 40, 50];
// iter 迭代器
for element in a.iter() {
   println!("the value is : {}", element);
}

Range

  1. 标准库提供。
  2. 指定一个开始数字和一个结束数字,Range 可以生成它们之间的数字(不包含结束)。
  3. rev 方法可以反转 Range。
fn for_range() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

文章作者: Layton
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Layton !
  目录