一门赋予每个人构建可靠且高效软件能力的语言。

第三章 通用编程概念

  • 变量与可变性
  • 数据类型
    • 标量类型
    • 符合类型
  • 函数
  • 注释
  • 控制流

3.1 变量与可变性

3.1.1 变量

  • 申明变量使用 let 关键字
  • 默认情况下,变量是不可变的(immutable
fn main() {
    println!("Hello, world!");

    let x: i32=5;
    println!("the value of x is {}",x);
}
  • 申明变量时,在变量前面加上 mut,就可以使变量可变
fn main() {
    println!("Hello, world!");

    let mut x: i32=5;
    x=8;
    println!("the value of x is {}",x);
}

3.1.2 常量

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

fn main() {
    println!("Hello, world!");
    println!("the number is {}",MAX_POINTS)
}

3.1.3 Shadowing(隐藏)

  • 可以使用相同的名字申明新的变量,新的变量就会 Shadow(隐藏)之前申明的同名变量
    • 在后续的代码中这个变量名代表的就是新的变量
const MAX_POINTS:u32 = 100_000;

fn main() {
    println!("Hello, world!");

    println!("the number is {}",MAX_POINTS)
}
  • Shadow 和把变量标记为 mut 是不一样的:
    • 如果不使用 let 关键字,那么重新给非 mut 的变量赋值会导致编译错误
    • 而使用 let 申明的同名新变量,也是不可变的
    • 使用 let 申明的同名新变量,他的类型可以与之前不同
fn main() {
    let spaces = "    ";
    let spaces=spaces.len();
    
    println!("the number is {}", spaces)  // 4
}

3.2 数据类型

  • 标量和复合类型
  • Rust 是静态编译语言,在编译时必须知道所有变量的类型
    • 基于使用的值,编译器通常能够推断出它的具体类型
    • 但如果可能的类型较多(如将 String 转为整数的 parse 方法),就必须添加类型的标注,否则编译会报错
fn main() {
    let guess:i32 = "42".parse().expect("not a number");

    println!("number is {}",guess);
}

3.2.1 标量类型

  • 一个标量类型代表一个单个的值
  • Rust 有四个主要的标量类型:
    • 整数类型
    • 浮点类型
    • 布尔类型
    • 字符类型

3.2.2 整数类型

3.2.2.1 基础介绍
  • 整数类型没有小数部分

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

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

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

  • Rust 的整数类型列表如图:

    • 每种都分为 i 和 u ,以及固定位数
    • 有符号的范围: -2(n-1)~2(n-1)-1
    • 无符号的范围:0~2^n-1
3.2.2.2 isizeusize
  • isizeusize 类型的位数由程序运行的计算机的架构所决定的:
  • 如果是 64 位计算机,那就是 64 位的
  • 使用 isizeusize 的主要场景是对某种集合进行索引操作
3.2.2.3 整数字面值
  • 除了 byte 类型外,所有的数值字面值都允许使用类型后缀
    • 例如 57u8
  • 如果不太清楚应该使用那种类型,可以使用 Rust 相应的默认类型
  • 整数的默认类型就是 i32
    • 总体上来说速冻更快,即使在 64 位系统中
3.2.2.4 整数溢出
  • 例如:u8 的范围是 0-255,如果你把一个 u8 变量的值设为 256 ,那么:
    • 调试模式下编译:Rust 会检查整数溢出,如果发生溢出,程序在运行时就会 panic
    • 发布模式下(--release)编译:Rust 不会检查可能导致 panic 的整数溢出
      • 如果溢出发生:Rust 会执行“环绕”操作:
        • 256 变成 0,257 变成 1 ...

3.2.3 浮点类型

3.2.3.1 基础介绍
  • Rust 有两种基础的浮点类型,也就是含有小数部分的类型
    • f32,32位,单精度
    • f64,64为,双精度
  • Rust 的浮点类型使用了 IEEE-754 标准来表达
  • f64 是默认类型,因为在现代 CPU 上 f64f32 的速度差不多,而且精度更高
3.2.3.2 数值操作
  • 加减乘除余等
fn main() {
    let sum = 5 + 10;

    let difference = 95.5 - 4.3;

    let product = 4 * 30;

    let quotient = 56.7 / 32.2;

    let reminder = 54 % 5;
}

3.2.4 布尔类型

  • Rust 的布尔类型也有两个值:true 和 false
  • 一个字节大小
  • 符号是 bool
fn main() {
    let t = true;
    let m: bool = false;
}

3.2.5 字符类型

  • Rust 语言中 char 类型被用来描述语言中最基础的单个字符。
  • 字符类型的字面值使用单引号
  • 占用 4 个字节大小
  • 是 Unicode 标量值,可以表示比 ASCII 多得多的字符内容:拼音、中日韩文、零长度空白字符、emoji表情等。
    • U+0000U+D7FF
    • U+E000U+10FFFF
  • 但 Unicode 中并没有 “字符” 的概念,所以直觉上认为的字符也许与 Rust 中的概念并不相符
fn main() {
    let x: &str = "z";
    let y: char='z';
    let z: char='😂';
}

3.3 复合类型

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

3.3.1 元组(Tuple)

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

    let (x,y,z) =tup;
    println!("{},{},{}",x,y,z);
}
3.3.1.3 访问 Tuple 的元素
  • 在 Tuple 变量使用点标记法,后接元素的索引号
fn main() {
    let tup: (i32, f64, u8) =(500,6.4,1);
    println!("{},{},{}",tup.0,tup.1,tup.2);
}

3.3.2 数组

  • 数组也可以将多个值放在同一个类型里
  • 数组中每个元素的类型必须相同
  • 数组的长度也是固定的
3.3.2.1 申明数组
  • 在中括号里,各值用逗号分开
fn main() {
    let a = [1,2,3,4,5];
}
3.3.2.2 数组的用处
  • 如果想让数据存放在 stack(栈)上而不是 heap(堆)上,或者想保证有固定数量的元素,这时使用数组更有好处
  • 数组没有 Vector 灵活
    • Vector 和数组类似,它由标准库提供
    • Vector 的长度可以改变
    • 如果你不确定应该使用数组还是 Vector ,那么估计你应该使用 Vector
3.3.2.3 数组的类型
  • 数组的类型表示形式:[类型;长度]
    • 例如:let a:[i32;5] = [1,2,3,4,5];
3.3.2.4 另一种申明数组的方式
  • 如果数组的每个元素都相同,那么可以:
    • 在中括号中指定初始值
    • 然后是一个 ";"
    • 最后是数组的长度
    • 例如:let a=[3;5]; 相当于 let a = [3,3,3,3,3];
3.3.2.5 访问数组的元素
  • 数组是在 stack 上分配的单个块的内存
  • 可以使用索引来访问数组的元素
  • 如果访问的索引超出了数组的范围,那么
    • 编译会通过
    • 运行会报错(runtime 时会 panic )
      • Rust 不会允许其继续访问相应地址的内存
fn main() {
    let a = [1,2,3,4,5];

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

3.4 函数

  • 申明函数使用 fn 关键字
  • 按照惯例,针对函数与变量名,Rust 使用 snake case 命名规范:
    • 所有的字母都是小写,单词之间使用下划线分开
fn main() {
    println!("hello world!");
    another_function();
}

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

3.4.1 函数的参数

  • parametersarguments
  • 在函数签名里,必须包含每个参数的类型
fn main() {
    println!("hello world!");
    another_function(5);    // argument
}

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

3.4.2 函数体中的语句与表达式

  • 函数体由一系列语句组成,可选的由一个表达式结束
  • Rust 是一个基于表达式的语言
  • 语句是执行一些动作的指令
  • 表达式会计算产生一个值
  • 函数定义也是语句
  • 语句不返回值,所以不可以使用 let 将一个语句赋给一个变量

3.4.3 函数的返回值

  • -> 符号后边申明函数返回值的类型,但是不可以为返回值命名
  • 在 Rust 中,返回值就是函数体里面最后一个表达式的值
  • 若想提前返回。需使用 return 关键字,并指定一个值
fn main() {
    let x=five(1);
    println!("the number is {}",x); // 6
}

fn five(x:i32)->i32{
    x+5
}

3.5 控制流 :if else

3.5.1 if 表达式

  • if 表达式允许您根据条件来执行不同的代码分支
    • 这个条件必须是bool类型
  • if 表达式中,与条件相关联的代码块就叫做分支(arm)
  • 可选的,在后边可以加上一个 else 表达式
fn main() {
    let num=3;
    if num<5{
        println!("true");
    }else {
        println!("false");
    }
}

3.5.2 使用 else if 处理多重条件

  • 如果使用了多于一个 else if ,那么最好使用 match 来重构代码

3.5.3 在 let 语句中使用 if

  • 因为 if 是一个表达式,所以可以将它放在 let 语句中等号的右边
fn main() {
    let condition = true;

    let num = if condition { 5 } else { 6 };
    
    println!("the number is {}",num);    // 5
}

3.6 控制流: Rust 的循环

  • Rust 提供了 3 中循环:loop,while 和 for

3.6.1 loop 循环

  • loop 关键字告诉 Rust 反复的执行一块代码,直到喊停
  • 可以在 loop 循环中使用 break 关键字来告诉程序何时停止循环
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

    println!("the result is {}", result);    // 20
}

3.6.2 while 条件循环

  • 另外一种常见的循环模式是每次执行循环体之前都判断一次条件
  • while条件循环为这种模式而生
fn main() {
    let mut counter = 3;

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

3.6.3 使用 for 循环遍历集合

  • 可是使用 while 或 loop 来遍历集合,但是易错且低效
fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("{}", a[index]);

        index += 1;
    }
}
  • 使用 for 循环更简洁紧凑,它可以针对集合中的每个元素来执行一些代码
fn main() {
    let a = [10, 20, 30, 40, 50];
    for element in a.iter(){
        println!("{}",element);
    }
}
  • 由于 for 循环的安全、简洁性,所以它在 Rust 里用的最多

3.6.4 Range

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