Rust基础


Rust基础

基础

两个冒号调用静态方法

声明变量的关键字 let,rust里的的变量默认是不可变的(immutable),不可变变量;让变量可修改就加上个 mut

let a = 1
let mut a = 1

  • 传参
// 传一个字符串
read_line(guess)

// 传一个可变字符串
read_line(mut guess)

// 方法的参数是通过引用传递的。
// 传一个可变字符串的引用。&是取地址符,表示这个参数是个引用,代表可以在不同地方访问同一个参数。
read_line(&mut guess)

Prelude Trait

Prelude
在rust的标准库中,有一个prelude的子模块,这个模块包含了默认导入的所有符号。

  • std库是默认导入的,std库中也有prelude的模块,里面的东西也是默认导入的。
  • **默认导入的意思就是不用手动在 use something::something; **

trait
一种抽象接口(可以理解为一个抽象类),这个抽象接口可以被继承,并且trait只能由3部分组成:function, types, constants

流程基础

变量与可变性

  • 声明变量使用let关键字
  • 默认情况下,变量是不可变的(Immutable),加上mut关键字就可以改变了。

变量与常量

常量绑定值以后也是不可变的,和不可变变量的区别如下:

  • 不可以使用mut,常量永远是不可变的。
  • 声明常量使用const关键字,它的类型必须被标注
  • 常量可以在任何作用域内进行声明,包括全局作用域。
  • 常量只可以绑定到常量表达式,无法绑定到函数的调用结果只能在运行时才计算出的值

在运行期间,常量在其声明的作用域内一直生效。常量的值可以加下划线增加可读性。
const MAX_POINTS: u32 = 100_000;

重影(隐藏) Shadowing

可以使用相同的名字声明新的变量,新的变量就会shadow之前声明的同名变量。重影过后,原来的变量的值就会变换成新的值(在后续的代码中,这个变量名代表的就是新的变量)。

// 不合法
let x = 5;
x = x + 1;

// 合法,重影
let x = 5;
let x = x + 1;

shadow 和把变量标记为mut是不一样的:

  • shadow不使用let关键字,给非mut变量赋值会报错。
  • let声明的同名新变量也是不可变的,但是可以和以前的类型不同。

理解:可变变量重新赋值的时候不需要重影,只有不可变变量重新赋值的时候要重影,重影要带let。

数据类型

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

  • 基于使用的值,编译器一般能推断出它的具体类型。
  • 类型比较多(比如Sting转整数的parse方法),就必须添加标注,否则编译会报错。

指明变量 let guess: u32 = guess.trim().parse().expect("Not u32 type.")

标量类型

整数类型

整数类型没有小数部分。

  • u32 无符号的帧数类型,占据32位的空间。
  • 无符号(非负)由 u 开头。(u8/u16/u32/u64/u128/arch(usize))
  • 有符号(有正有负))由 i 开头。(i8/i16/i32/i64/i128/arch(isize))

isize和usize
由架构序所决定:x86, x64

整数字面值

使用不同进制的数字时怎么表示。可以用下划线增加可阅读性。

  • 10进制 98222
  • 16进制 0xff
  • 8进制 0o877
  • 2进制 0b11_00
  • Byte(u8 only) b'A'

除了byte类型外,所有的数值字面值都允许使用类型后缀。

  • 57u8 u8类型的数值57
  • let mut guess = 57u8;

Rust默认类型

  • 整数默认 i32
整数溢出

超出了范围。比如u8类型给了个256,那么在调试模式下编译会panic,发布模式下不检查。

发生了溢出,rust会进行“环绕”操作(清零),256变成0,257变成1。

浮点类型

  • f32 32位,单精度
  • f64 64位,双精度

Rust默认类型

  • f64

布尔类型

bool -> true and false。

字符类型

  • char 最基础单个字符。
  • 字符类型的字面值使用单引号。

复合类型

复合类型可以将多个值放在一个类型里。

元组(tuple)

多个类型的多个值放在一个类型里。长度是固定的,一旦声明之后就无法修改了。

创建tuple

在小括号里,将值用逗号分开。tuple中的每个位置都对应一个类型,各个类型元素不必相同。在tuple里也可以指明每个值的类型。
使用时下标用点标记法,从0开始。

创建: let foo: (i32, f64, u32) = (-1, 6.5, 8);
使用: println!("{} {} {}", foo.0, foo.1, foo.2);

解构赋值

let (x, y, z) = foo;

数组

数组可以将多个值放在一个类型里,每个元素的类型必须相同,长度也是固定的。在中括号里,用逗号分开。
数组类型以这种形式标识:[类型; 长度]
创建 let foo: [i32; 3] = [1, 2, 3]

特殊声明,仅用于数组里的所有值都一样。
创建 let a = [3;5]
let a = [3, 3, 3, 3, 3]

存放在栈内存上,并能保证有固定的数量,此时应该用数组。

EG: rust里可以用Vector,这个类型由标准库提供,长度可以改变(类似python中的list),一般用Vector。

访问数组使用中括号内放下标。Foo[0]

如果访问时超出了数组的范围,此时会出现数组越界。编译会通过,运行会报错。

rust 不允许其继续访问相应地址的内存。(C/C++会允许)

函数

使用fn关键字,使用snake case命名规范。只要函数声明了,并且够得着就行。

形参和实参

和python或其他语言也差不多,但是必须声明每个参数的类型
fn getAccount(account: i32, password: u64){}

函数体中的语句和表达式

函数体由一系列语句组成。函数和函数的定义也是语句。
{}块语句由一个表达式结束,最后一个语句如果是表达式不加分号会直接返回该值,加了分号就是返回tuple (),因为变成了语句。

简单理解
表达式:有返回值,会计算产生一个值。
语句:没有返回值,一些动作的指令。所以不可以使用let将一个语句赋给一个变量,有返回值才能赋给一个变量

函数的返回值

  • 在 -> 符号后面声明函数的返回值类型,但是不可以为返回值命名。
  • 在rust里返回值就是函数体里面最后一个表达式的值。
  • 若想提前返回,使用retrun并指定一个值。(大多数函数都是默认使用最后一个表达式作为返回值)
fn main(){
    // 5
    let x = returnFive()

    // tuple
    let y = returnPlusFive(6)
}

fn getAccount(account: i32, password: u64){}

fn returnFive() -> i32{
    // 返回5
    5
}

fn returnPlusFive(arg: i32) -> (){
    // 这里是语句,加了; 会返回一个tuple ()
    arg + 5;
}

关于语句和表达式的一点整理

  1. 表达式属于语句,语句分为声明语句和表达式语句。
  2. 声明语句:声明各种语言项。
  3. 表达式语句:如果此表达式语句用了;结尾,那么求值结果将被舍弃,并总是返回 单元类型 tuple (),也代表无返回值,不需要在函数签名中指定返回类型。
  4. 当遇到函数的时候,会将函数体的花括号识别为块表达式,块表达式总是返回最后一个表达式的值。

总结:在Rust中,如果一个语句不以分号结尾,那么他就是一个表达式(expression),有返回值;如果一个语句有分号,那么它就是一个语句(statement),没有返回值。

if/else/else if

if x != 5{
    println!("5");
} else if x % 3 == 0 {
    println!("!=5")
} else {
    println!("kkk")
}

if 的重构

用match来重构。

if 表达式

if 因为是表达式,所以可以被变量接收。(和python if简写一样,要多写个花括号)
let number = if x == "hi" {6} else {5};

if else不允许拥有不兼容的类型,返回的类型必须是一样的。接收的变量的值的类型一定不能是不确定的。rust要求if else表达式里面每个可能成为结果的分支返回的类型必须是一样的。

循环

3种循环:loop, while, for

loop

反复执行,直到主动停止。使用 break 关键字停止循环。

loop 可以有返回值,接在break后面。
break 2*2

while

条件循环。每次执行一次循环体之前都判断一次条件。
while number != 0{}

配合数组使用的时候容易出现数组越界。while速度比较慢。

for

遍历集合建议使用for。

let a = [1, 2, 3];
for i in a.iter(){
  // 这个i是个引用,并没有复制出来。
  println!("{}", i)
}

for 不会数组越界。安全,简洁。

Range

和python的一样。指定一个开始数字和结束数字(不含结束)。
(1..4) // 1到3,不包含4

rev

rev可以翻转Range。
for i in (1..4).rev(){}