0%

Rust 学习笔记:第3章 通用的编程概念

变量与可变性

  • 声明变量使用 let 关键字

  • 默认情况下,变量是不可变的(Immutable)

    1
    let x =10;
  • 声明变量时,在变量前面加上 mut 关键字,就可以使变量可变

    1
    let mut x = 10;

变量与常量

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

Shadowing(隐藏)

  • 可以使用相同的名字声明新的变量,新的变量就会 shadow(隐藏)之前声明的同名变量

    • 在后续代码中这个变量名代表的就是新的变量
    1
    2
    let mut guess = 10;
    let guess = 888;
  • shadow 和把变量标记为 mut 是不一样的

    • 如果不使用 let 关键字,那么重新给非 mut 的变量赋值会导致编译时错误
    • 而使用 let 声明的同名新变量,也是不可变的
    • 使用 let 声明的同名新变量,它的类型可以与之前不同

数据类型

  • 标量和复合类型

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

    • 基于使用的值,编译器通常能够推断它的具体类型

    • 但如果可能的类型比较多(例如把String转为整数的parse方法),就必须添加类型的标注,否则编译会报错

      下面例子要是不添加 u32 那么程序就会报错,因为编译器不知道把 42 字符串解析成那种整数类型

      1
      let guess: u32 = "42".parse().expect("not a number");

      标量类型

  • 一个标量类型代表一个单个的值

  • Rust 有四个主要的标量类型

    • 整数类型
    • 浮点类型
    • 布尔类型
    • 字符类型

整数类型

  • 整数类型没有小数部分

  • 例如 u32 就是一个无符号的整数类型,占据 32 位的空间

  • 无符号整数类型以 u 开头

  • 有符号整数类型以 i 开头

  • Rust 的整数类型如图所示

    Snipaste_2021-06-06_10-57-20

    • 每种都分 iu,以及固定的位数

    Snipaste_2021-06-06_10-59-55

isizeusize 类型

  • isizeusize 类型的位数由程序运行的计算机的架构所决定
    • 如果是 64 位计算机,那就是 64 位
    • 如果是 32 位计算机,那就是 32 位
  • 使用 isizeusize的主要场景是对某种集合进行索引操作

整数字类型

Number literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte(u8 only) b'A'
  • 除了 byte 类型外,所有的数值字面值都允许使用类型后缀
    • 例如 57u8(57代表u8类型的数)
  • 如果你不太清楚应该使用哪种类型,可以使用 Rust 相应的默认类型
  • 整数的默认类型就是 i32
    • 总体上来说速度很快,即使在 64 位系统中

整数溢出

  • 例如:u8 的范围是 0-255,如果你把一个 u8 变量的值设为 256,那么

    • 调试模式下编译:Rust 会检查整数溢出,如果发生溢出,程序在运行时就会 panic(恐慌)

    • 发布模式下(–release)编译:Rust 不会检查可能导致 panic 的整数溢出

      如果溢出发生,Rust 会执行 “环绕操作”:将256变成0,257变成1(所以并不会导致程序 panic)

浮点类型

  • Rust 有两种基础的浮点类型,也就是含有小数部分的类型
    • f32,32位,单精度
    • f64,64位,双精度
  • Rust 的浮点类型使用 IEEE-754 标准来表述
  • f64 是默认类型,因为在现代 CPU 上 f64 和 f32 的速度差不多,而且精度更高

数值操作

1
2
3
4
5
6
7
fn main() {
let sum = 5 + 10; // i32
let difference = 95.5 - 4.3; //f64
let producet = 4 * 30; //i32
let quotient = 56.7 / 32.2; //f64
let reminder = 54 % 5; //
}

布尔类型

  • Rust 的布尔类型也有两个值:true 和 false
  • 布尔类型占用1个字节空间
  • 符号是 bool

字符类型

  • Rust 语言中 char 类型被用来描述语言中最基础的单个字符

  • 字符类型的字面值使用单引号

  • 占用4个字节大小

  • 是 Unicode 标量值,可以表示比 ASCII 多得多的字符内容:拼音、中日韩文、零长度空白字符、emoji 表情等

    范围:U+0000 到 U+D7FF、U+E000 到 U+10FFFF

  • 但 Unicode 中并没有 “字符”的概念,所以直接上认识的字符也许与 Rust 中的概念并不相符

复合类型

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

Tuple(元组)

  • Tuple 可以将多个类型的多个值放在一个类型里
  • Tuple 的长度是固定的:一旦声明就无法改变
创建 Tuple
  • 在小括号里,将值用逗号分开
  • Tuple 中的每个位置都对应一个类型,Tuple 中各元素的类型不必相同
1
2
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("{}, {}, {}", tup.0, tup.1, tup.2);
获取 Tuple 元素值

可以使用模式匹配来解构(destructure)一个 Tuple 来获取元素的值

1
2
3
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup;
println!("{}, {}, {}", x, y, z);
访问 Tuple 的元素

在 tuple 变量使用点标记法,后接元素的索引号

1
2
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("{}, {}, {}", tup.0, tup.1, tup.2);

数组

  • 数组也可以将多个值放在一个类型里
  • 数组中每个元素的类型必须相同
  • 数组的长度也是固定的
数组的用处
  • 如果想让你的数据存放在 stack(栈)上而不是 heap(堆)上,或者想保证有固定数量的元素,这是使用数组更有好处
  • 数组没有 Vector 灵活
    • Vector 和数组类似,它是由标准库提供
    • Vector 的长度可以改变
    • 如果你不确定应该用数组还是 Vector,那么估计你应该用 Vector
声明一个数组
  • 在中括号里,各值用逗号分开

    1
    let a = [1, 2, 3, 4, 5];
  • 如果数组的每个元素值都相同,那么可以在:

    1. 在中括号指定初始值
    2. 然后是一个 ;
    3. 最后是数组的长度
    1
    let a = [3; 5]; 相当于 let a = [3, 3, 3, 3, 3];
    数组的类型
  • 数组的类型以这种形式表示:[类型;长度]

    • 例如:let a:[i32; 5] = [1, 2, 3, 4, 5];
访问数组的元素
  • 数组是 Stack 上分配的单个块的内存

  • 可以使用索引来访问数组的元素

  • 如果访问的索引超出了数组的范围,那么:

    • 编译会通过

      编译会通过不是绝对的,Rust 还是会对索引进行一个简单的检查

    • 运行会报错(runtime 时会 panic)

      rust 不会允许其继续访问相应地址的内存;但是在 C/C++ 中是可以访问的,但是不知道里面是什么东西

函数

  • 声明函数使用 fn 关键字
  • 依照惯例,针对函数和变量名,Rust 使用 snake case 命令规范
    • 所有的字母都是小写的,单词之间使用下划线分开
1
2
3
4
5
6
7
8
fn main() {
println!("hello world");
another_function();
}

fn another_function() {
println!("Annother function");
}

函数的参数

  • parameters、arguments
  • 在函数签名里,必须声明每个参数的类型
1
2
3
4
5
6
7
fn main() {
another_function(5); //argument
}

fn another_function(x: i32) { //parameter
println!("the value of x is: {}", x);
}

函数体中的语句与表达式

  • 函数体由一系列语句组成,可选的由一个表达式结束
  • Rust 是一个基于表达式的语言
  • 语句是执行一些动作的指令
  • 表达式会计算产生一个值
  • 函数的定义也是语句
  • 语句不返回,所以不可以使用 let 将一个语句赋给一个变量
1
2
3
4
5
6
7
8
fn main() {
let x = 5;
let y = {
let x = 1;
x + 3 //加上;就是一个语句,就不能返回数值了
}
println!("The value of y is: {}", y);
}

函数的返回值

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

fn main() {
let x = plus_five(6);
println!("The value of x is:{}", x);
}

注释

  • 单行注释

    1
    // This is a function
  • 多行注释

    1
    2
    3
    4
    /*
    This is a function
    This is a function
    */
  • 文档注释

控制流

if 表达式

  • if 表达式允许你根据条件来执行不同的代码分支
    • 这个条件必须是 bool 类型
  • if 表达式中,与条件相关联的代码块就叫分支(arm)
  • 可选的,在后面可以加上一个 else 表达式
1
2
3
4
5
6
7
8
9
fn main() {
let number = 2;

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

使用 else if 处理多重条件,但如果使用多个 else if,那么最好使用 match 来重构代码。

在 let 语句中使用 if,因为 if 是一个表达式,所以可以将它放在 let 语句中等号的右边。

Rust 是一个强类型的语言,同时也是一个特别安全的语言,所以在编译期间就要知道变量的类型是什么;

如果将下面的 6 改成字符串 6 就会报错,那么无法在编译期间推导变量的类型

1
2
3
4
5
fn main() {
let condition = true;
let number = if condition {5} else {6};
println!("The value of number is: {}", number);
}

循环

  • Rust 提供了 3 循环:loop、while、for
1
2
3
4
5
fn main() {
loop {
println!("again!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
fn main {
let mut counter = 0;

let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};

println!("The result is: {}", result);
}
1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut number = 3;

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

number = number - 1;
}

prinltn!("LIFIOFF!!!");
}

使用 for 循环遍历集合

  • 可以使用 while 或 loop 来遍历集合,但是易错且低效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
    println!("The value is: {}", a[index]);
    index = index + 1;
    }
    }
  • 使用 for 循环更简洁紧凑,它可以针对集合中的每个元素来执行一些代码

    1
    2
    3
    4
    5
    6
    fn main() {
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
    println!("The value is: {}", element);
    }
    }
  • 由于 for 循环的安全、简洁性、所以它在 Rust 里用的最多

Range

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

println!("LIF")
}