《Rust权威指南》读书笔记3 - General Programming Concepts

Rust - General Programming Concepts

  本文对应《Rust权威指南》的第3章,主要介绍Rust中的通用编程概念,如变量、数据类型、函数、程序控制流等。

Variables & Mutability 变量与可变性

Mutability 可变性

注意,此处的可变性(Mutability)与Python变量中的可变变量所指相同,而不是与常量相对的变化性。

Rust中的变量默认不可变, 使用mut关键字可以创建一个可变变量。

let x = 123;		// immutable
let mut x = 123; 	// mutable

PS: 关于这样设计的原因,笔者看到了一个编码习惯方面的解释:**在实现同样功能的情况下,程序员倾向于使用更短的语法。**在Rust中,大多数情况下,不可变变量可以胜任工作。如果可变变量的声明比不可变变量短,那么程序员倾向直接使用可变变量,**而这产生了不安全性。**将不可变设计得比可变短,体现了语言鼓励不可变变量。

  • 如果试图改变不可变变量的值,则会编译时报错 “cannot assign twice to immutable variable
  • Rust编译器保证声明为不可变的值一定不会改变
  • 当数据结构较为清凉的时候,采用偏向函数式的风格,通过创建新变量来赋值,可能使代码更加易于理解

Const 常量

常量与不可变变量的概念也不相同。我们使用const关键字修饰一个常量

const MAX_POINTS: u32 = 100;
  • 必须显式标注常量类型
  • 常量可以声明在任何作用域中,包括全局作用域
  • 常量只能绑定到一个常量表达式上
  • 常量类似C/C++中的常量宏

Shadow 隐藏

Rust提供隐藏机制允许用户使用同一个变量名绑定不同的变量

使用let声明的新变量可以**覆盖旧的同名变量。这被称为旧变量被新变量隐藏(shadow)**了。我们可以不停使用let同一变量名来隐藏变量。

  • 隐藏保持了变量的可变性(不可变变量被不可变变量隐藏后还是不可变)
  • 隐藏允许绑定不同类型的变量,而不能直接改变可变变量的类型
  • 需要注意的是,隐藏仅限于变量作用域中,两个同名变量的作用域交叠,当一个变量离开作用域,另一个变量仍可用。隐藏不等同于替换

Data Types 数据类型

Rust 中,数据类型可以分为两类:标量类型(Scalar), 复合类型(Compound)

注意:Rust是静态类型语言,所有变量的具体类型都需要在编译过程中确定。(这和变量类型是否被显式声明无关,使用隐式声明,编译器也能在一定条件下推导变量类型)

Scalar 标量类型

标量类型是单个值类型的同城,Rust 内建(builtin)了4中基础标量类型:

  • 整数 (integer)
  • 浮点数 (float)
  • 布尔型 (bool)
  • 字符型 (char)
Integer

整型下,根据长度和有无符号位,分为以下几种细分类型

SizeSignedUnsigned
8bi8u8
16bi16u16
32bi32u32
64bi64u64
128bi128u128
arch-dependisizeusize
  • 每一个类型都明确表明其大小和有无符号
  • 一般使用默认推导类型的i32,运行效率最高

整型溢出

  • 在debug模式中,编译器默认会提供溢出检查,溢出时发生panic
  • 在release模式中,不会进行检查和panic,替换为补码环绕(类似C)
  • 可以使用标准库类型Wrapping手动启用环绕
Float

共有两种浮点数类型,f32单精度和f64双精度,两者效率接近。Rust默认将浮点数推导为f64

Numerical Operations 数值运算

Rust支持基本数学运算符。但是,Rust不支持不同类型的运算(整型内部也不可),需要将字面量指定成对应类型(在后面添加即可)

let a = 5 / 2;		// correct, a == 2
let a = 5.0 / 2;	// error
let a = 5.0 / 2.0;	// correct, a == 2.5, f64
let a = 5f64 / 2f32;// error, type mismatch
let a = 5f32 / 2.0;	// correct, a == 2.5, f32
bool

布尔型只有两个值,true & false,占用1Byte

char

用于表示最基础的单个字符,使用单引号指定''(与C类似)

Attention: char类型占4字节,使用unicode编码,提供了外文、表情支持

Compound 复合类型

Compound Type 将多个不同类型的值组合为一个类型。Rust提供两种Builtin基础复合类型

  • tuple 元组
  • array 数组
tuple

元组可以将不同类型的值组合进一个符合类型。元组拥有一个固定长度,长度无法在声明结束后修改。

元组使用圆括号()包含,逗号,分隔

let tup = (500, 6.4, 'a', true);
  • destructuring 解包:类似Python语法,使用let语句,用多个变量承接元组变量的值
let (x, y, z, a) = tup;
  • index 索引,使用点号 .后跟索引,使用对应位置上的值
array

Rust数组拥有固定长度,声明后无法改变。使用方括号[]来声明。数组的每个元素必须是相同类型。另外,可以用类型标注,指定元素类型

let a = [1, 2, 3, 4];
let a: [u64; 4] = [1, 2, 3, 4];
  • 如果需要长度可变,可使用vector

数组批量声明方法

使用批量声明,可以快速创建含有相同元素的数组

let a: [u64; 3] = [1; 3];		// assert_eq!(a, [1, 1, 1]);
let a = [1u64; 3];				// same as line 1

Accessing array elements

使用方括号[]与索引访问元素。

let a = [1, 2, 3, 4];
assert_eq!(a[0], 1);
  • 数组越界:如果在运行过程中发生数组越界,程序会触发panic: index out of bounds

Functions 函数

  • 使用fn关键字声明函数。

  • Rust采用snake case(蛇形命名法)规范函数命名,所有字母小写,单词采用下划线连接

  • Rust不关心函数定义位置,只关心该函数是否在范围内可见

fn test_function() {
    println!("This is a test function");
}

函数参数

在英文中区分了函数的形参(Parameter)和实参 (Argument),但在中文中常常混淆为“参数”。应当区分。函数参数在括号中填充

fn test1(x: i32) {
    println!("the value of x is {}", x);
}
  • 在函数签名中,必须显式声明参数类型。这经过了Rust设计者们的慎重考虑。这样可以明确地进行类型指定,而不需要其他代码的类型推导。

语句和表达式

语句不会返回值,表达式会。Rust中的大部分代码都是表达式

  • 普通赋值语句不会返回值,这不同于C

  • 表达式本身作为语句的一部分

  • 以下都是表达式

    • 调用函数

    • 调用宏

    • 花括号{} 除了能新建作用域,也能作为表达式使用。最后一行语句如果没有分号;,则被认为是{}表达式的返回值

      let y = {
          let x = 3;
          x + 1		// no semi-colon
      }
      
      • 如果后面加上分号,则返回空 ()

函数返回值

Return Value 是函数向调用它的代码返回的值。需要在箭头->后声明其类型

同样,使用表达式(最后一行)隐式传递返回值,或使用return关键字提前返回。

fn five -> i32 {
    5		// return 5 implicitly
}
fn three -> i32 {
    return 3;	// return explicitly
}

Control Flow 控制流

除了之前提到的match匹配,Rust同样提供了正常的控制流,如分支结构if和循环结构等

if statement

if表达式的判断条件(condition)不需要使用括号包含。但是所有代码必须用大括号包装为代码块

Condition必须产生一个bool类型的值,这与其他语言中非零即true的设计不相同

let number = 3;
if number {			// error
	todo!();
}
if number != 0 {	// correct
	todo!();
}

代码格式规范:

if y == 1 {
    todo!()
} else if y == 2 {
    todo!()
} else {
    todo!()
}
  • 由于todo!宏返回空,所以加不加分号不影响

  • if表达式独立出现,代码块必须只能返回空,否则编译错误

  • 过多的if-else导致代码杂乱无章,可以用之前介绍的match分支结构(在后面介绍)

  • if 可作为表达式使用(返回特定类型的值)

    let x = if y == 1 {
        1
    } else if y == 2 {
        2
    } else {
        'a'		// error, type mismatch
    };
    
    • 注意:各代码块类型必须相同(或与类型标注一致)
    • 注意:各分支必须全部覆盖(最后一定有else),否则该arm返回空,导致赋值失败
  • 我们可以使用if let语句精简代码,将赋值和匹配连在一起

Repetition Structure 循环结构

loop

使用loop语句实现循环(死循环),通过break关键字退出

loop {
    todo!();
    if condition {
        break;
    }
}
  • 通过在break后加参数以返回值(用于表达式)

    let mut counter = 0;
    let mut sum = 0;
    let result = loop {
        counter += 1;
        sum += counter;
        if counter == 10 {
            break sum * 2;
        }
    }
    
  • 跳出多重循环:使用标签以跳出多重循环,默认跳出最内层循环

    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}");
    }
    
while condition loops
while condition {
    todo!()
}

Looping through a collection with for

使用for语句来遍历一个集合

let a = [10, 20, 30];
for ele in a {		// element, ele: i32
    println!("Element is: {}", ele);
}
for ele in a.iter() {	// iterator, ele: &i32
    println!("Element is: {}", ele);
}
  • 第一种方式(直接使用集合)传递集合中元素的
  • 第二种方式使用迭代器,直接传递元素的引用

使用Range生成循环

for number in 1..4 {
    println!("Number is: {}", number);
}
for number if (1..4).rev() {	// revert the sequence
    println!("Number is: {}", number);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值