Rust的一般概念

2021-11-04 51点热度 0人点赞 0条评论

变量和可变性

变量默认是不可变(immutalbe)的。刚开始学习Rust的人可能不太习惯,但是变量默认不可变能够提升程序的安全性且更容易做到并发。为什么Rust鼓励你使用不可变的变量呢?

当一个变量是不可变的时候,一旦某个值绑定到这个变量了,你就不能再改变这个值了。我们新建一个工程来测试一下:

cargo new varibales --bin

main.rs内容为:

fn main() {
    let x = 5;
    println!("x = {}", x);
    x = 6; // 报错!error[E0384]
    println!("x = {}", x);
}

运行cargo run试编译一下,在 x = 6; 那一行会报错!

error[E0384]: cannot assign twice to immutable variable x

意思是你不能对一个不可变的变量x进行二次赋值!我们在let x = 5;的时候并没有指定x是immutable的,为什么Rust会这么提示呢?因为Rust默认会给变量添加不可变的属性,只要你没有给变量添加可变(mut)修饰,它就是不可变的。

如果要让变量变成可变的,需要在let后面添加mut关键字。

fn main() {
    let mut x = 5;
    println!("x = {}", x);
    x = 6;
    println!("x = {}", x);
}

变量和常量

既然有变量就会有常量,常量和immutable的变量很像,但有一些不同:
1. 你不能用mut来修饰常量,常量不仅仅叫做默认不可变,它是永远不能变!
2. 常量用const关键字来定义,而不是let,常量定义时必须指明类型

fn main() {
    let mut x = 5;
    println!("x = {}", x);
    x = 6;
    println!("x = {}", x);

    let y = 7;
    println!("y = {}", y);
    const HUNDRED: u32 = 100;
   println!("HUNDRED = {}", HUNDRED);
}
  1. 常量可以在任何地方定义,包括全局的!let只能在函数中使用,如果我们将let y = 7移动到main函数外面的,编译会报错!
let y = 7; // 报错!error: expected item, found keyword `let`
fn main() {
    ...
  1. 常量不能通过函数调用赋值,只能使用常量表达式赋值;
const HUNDRED: u32 = 100 + 6; // 正确
fn main() {
    ...

变量遮蔽(Shadowing)

当你定义了和前面变量一样的名字的时候,新的变量会将之前旧的变量名盖住。

fn main() {
    let x = 1;
    let x = x + 1;
    let x = x * 3;

    println!("x = {}", x);
}

运行结果:x = 6

变量遮蔽和可变变量的比较:
1. 变量遮蔽每次都需要关键字let,是新建一个变量!可变变量只使用过一次let,并需要用mut来修饰变量,第二次赋值不是新建变量;
2. 变量遮蔽可以改变值的类型

let spaces = " ";
let spaces = spaces.len();

第一个spaces的类型是字符串类型,第二个spaces是整数类型。也可以这样理解,++第二个整数类型的变量spaces刚好用了和第一个字符串类型的变量相同的名字++!

而如果我们用mut来修饰变量,然后赋予不同类型的值的话,就会报类型不匹配的错误!

let mut spaces = " ";
spaces = spaces.len(); // error[E0308]: mismatched types
                       // expected `&str`, found `usize`

数据类型

在Rust中每一个值都有一个确定的数据类型!数据类型包含有2个大的子集:数值型和复合类型。

Rust是一门静态类型语言,静态类型语言意味着Rust必须在编译期间就确定所有的变量类型!我们在很多地方都会碰到将字符串转换成数值的需求,因为数值类型有很多种类,如果编译器在编译期间无法确定最终数值的类型的话,就会报错,它要求开发者指定好!比如:

let guess = "42".trim().parse().expect("请输入一个整型数!");

报错:error[E0282]: type annotations needed;
consider giving guess a type 就是要给guess指定一个类型,比如 guess: u32

数值类型(Scalar Types)

Rust有4种基本的数值类型:整型,浮点型,布尔型和字符型。每个数值类型代表一个值。我们稍微看看。

整型

整型没有小数部分,它可以分为有符号数和无符号数。有符号数的整型用i开头,无符号数的整型用u开头。
Rust内建支持的整型类型

长度 有符号 无符号
8位 i8 u8
16位 i16 u16
32位 i32 u32
64位 i64 u64
架构相关 isize usize

isize和usize是与运行程序的CPU架构相关的:在32位机器上是64位长度,在64位机器上是64位长度。如果你不确定用哪一个整型类型,那么就用Rust默认的,为i32,++使用这个类型进行运算时是最快的,即使在64位计算机上也是++。

在Rust中有多种整型字面常量的书写方式。其中下划线_仅用来便于开发者阅读,不对数字的大小构成影响!

数字字面常量 举例
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
字节 b'A'

注意:
b‘A'会打印成65,’A'会打印成A。
0x表示的是十六进制,0o表示的是八进制,0b表示的是二进制,在数字0后面分别是小写字母x、o和b,不能是大写的

测试一下:

fn main() {
    let x = 20_0000;
    println!("x is {}", x);
    let x = 0x20_0000;
    println!("x is {}", x);
    let x = 0o77;
    println!("x is {}", x);
    let x = 0b1111_0000;
    println!("x is {}", x);
    let x = b'A';
    println!("x is {}", x);
    let x = 'A';
    println!("x is {}", x);
}

运行结果:

x is 200000
x is 2097152
x is 63
x is 240
x is 65
x is A

浮点类型

Rust支持2种基本的浮点类型:单精度f32和双精度f64。默认的浮点类型是f64,因为现代的计算机性能都很强劲,计算64位的速度与32位差不多,前者的的精度更高,适用范围更广。

let x = 2.0;    // f64
let y: f32 = 3.0; // f32

布尔类型

fn main() {
    let t = true;
    let f: bool = false; // with explicit type annotation
}

字符类型

字符用单引号包围起来,字符串用双引号

fn main() {
    let c = 'z';
    let z = 'Ƶ';
    let heart_eyed_cat = '😻';
    let zhong = '中';
    println!("heart_eyed_cat is {}", heart_eyed_cat);
    println!("zhong is {}", zhong);
  }

输出:

heart_eyed_cat is 😻
zhong is 中

Rust中的字符类型不是单纯的ASCII字符编码,而是Unicode编码!所以我们可以看到上面能够打印出中文和Emoji表情。Unicode编码值的范围是U+0000 ~ U+D7FF,以及U+E000 ~ U+10FFFF。

因为Rust的字符类型与我们通常了解的很不一样,要特别注意!

复合类型

复合类型可以将多个值组织到一个类型中,Rust支持2种基本的复合类型:元组(tuples)和数组(arrays)

元组类型(元/圆括号())

元组可以将多个不同类型的值放在一个集合里。
创建元组时需要用圆括号括起来,里面的每个值用逗号隔开。你可以为每个元素指定好类型,也可以由Rust帮你推断。

let tup: (i32, f64, u8) = (2, 3.0, 250); // 正确
let tup = (2, 3.0, 250); // 正确,自动推断类型
let tup: (i32, f64) = (2, 3.0, 250); // 错误 error[E0308]: mismatched types:
            // expected a tuple with 2 elements, found one with 3 elements

前两句表达式都创建了tup元组,元组中有三个值,分别为2, 3.0, 250,前者指定了值类型,后者由Rust自动推断三个值的类型。

如何读取元组中的数据呢?

fn main() {
    let tup: (i32, f64, u8) = (2, 3.0, 250);
    let mut tup = (2, 4.0, 250);

    let (x, mut y, z) = tup; // 解元组:将一个元组分解成几个部分
    println!("y is {}", y);
    println!("tup.0 is {}", tup.0); // 点运算
    tup.2 = 200;
    y = 5.0;
    println!("y is {}", y);
    println!("z is {}", z);
}

运行结果:

y is 4
tup.0 is 2
y is 5
z is 250

读取元组数据的方法:
1. 将元组赋值给另外一个由变量组成的元组,然后用元组中的变量获得对应的值!
2. 使用.(点)运算符和序号的方式获取,序号从0开始。

数组类型(方括号[])

另一个将多个值放在一个集合里的方法是数组,和元组不同,数组中的元素只能一种!Rust的数组的长度是固定的,一旦定义就不能再修改了!创建数组时需要用方括号括起来,里面的每个值用逗号隔开

let a = [1, 2, 3, 4, 5];

如何访问数组元素?方括号中加下标,下标从0开始。

    let a = [1, 2, 3, 4, 5];
    println!("a[0] is {}", a[0]);

如果我们访问不存在的序号呢?我们知道Rust的数组中的长度是固定的,所以猜想Rust会在编译时报错吧?是的!

println!("a[0] is {}", a[5]); // 报错 index out of bounds: 
                              // the length is 5 but the index is 5

Rust在访问元素的时候会看序号是否超出范围了,超出的话就会果断panic退出了。这样的处理让程序变得更加安全,对比一些语言不做比较的话就会访问到不可用的内存,那么程序当时可能没有错误退出,在过一段时间之后才表现出来,问题追查的时候就比较难以定位到原因。

++标准库中提供的vector和array具有相似的功能++,都是同一种类型,但是vector不固定长度,灵活性要比array大得多,如果你不知道在这两者间如何选择,就选择vector使用。
下面这种情况用array很合适,如月份的定义、星期的定义,都是固定长度和同一种类型的。

let months = ["January", "February", "March", "April", "May", "June", "July", 
"August", "September", "October", "November", "December"];

函数

函数在Rust中非常常见,像main函数就是其中一个非常重要的函数,它是整个应用程序的入口。Rust中的函数代码采用蛇形命名法编写,蛇形命名法是由全小写的字母和_下划线连接单词的方式。

fn main() {
    println!("Hello, world!");
    another_function();
}
fn another_function() {
    println!("Another function.");
}

函数以fn开头,后面跟着的是函数名。看上面的main函数可以调用到定义在其后面的another_function函数,说明在Rust中,被调用函数another_function的定义与调用者main函数书写先后顺序无关。

参数

函数的签名中,参数的类型是必须指定的!

fn main() {
    another_function(5, 6);
}
fn another_function(x: i32, y: i32) {
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
}

函数中的语句Statement和表达式Expression

Rust中的函数应该由若干条语句和末尾的一条表达式组成(也可以没有表达式!)。和其它语言不一样,在Rust中对这两个概念做了区分。

语句是用来执行的指令,没有结果值。表达式更像是一个实物,则有一个实际的结果用于赋值。

let y = 6; // 语句
let x = (let y = 6); // 报错,因为let y = 6是语句,是没有返回值的

语句没有返回值,所以你不能用语句作为一个值赋值给另外一个变量

let x = y = 6; // 在Rust中也是不成立的

再看看下面的语句和表达式:

fn add(x: i32, y: i32) -> i32 {
    return x + y; // 是一条语句
}

fn minus(x: i32, y: i32) -> i32 {
    x + y + 100 // 没有分号!为表达式,x+y+100的结果用于minus的返回值
}

fn main() {
    println!("result   add: {}", add(1, 2)); // 语句
    println!("result minus: {}", minus(1, 2)) // 表达式,在函数的末尾了,这样写也可以正常编译
}

控制流

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

会报错!因为if 是一个语句,所以它有返回值。但是两个分支的返回值不一样就不行!Rust是静态编译语言,无法处理这样类型不确定的情况!

循环

loop

嵌套的loop循环

fn main() {
    'outer: loop {
        println!("Entered the outer loop");

        'inner: loop {
            println!("Entered the inner loop");

            // This would break only the inner loop
            //break;

            // This breaks the outer loop
            break 'outer;
        }

        println!("This point will never be reached");
    }

    println!("Exited the outer loop");
}

loop的返回值:

    let mut counter = 0;

    let result = loop {
        counter += 1;

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

while

    while n > 0 {
        println!("n is {}", n);
        n -= 1;
    }

for

数据的iter方法

fn main() {
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("the value is: {}", element);
    }
}
fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

for i in 1..=10 {
    println!("i(include) is {}", i);
}

教头Lily

铁汉柔情

文章评论

您需要 登录 之后才可以评论