• Rust学习日记(二)变量的使用--结合--温度换算/斐波那契数列--实例


    前言:
    这是一个系列的学习笔记,会将笔者学习Rust语言的心得记录。
    当然,这并非是流水账似的记录,而是结合实际程序项目的记录,如果你也对Rust感兴趣,那么我们可以一起交流探讨,使用Rust来构建程序。

    注:本文中使用Rust都是在windows环境下,如果是macOS或者linux,其指令或有不同,请注意。

    系列第一篇:Rust学习日记(一)Cargo的使用

    概述:

    这是Rust学习笔记的第二篇,主要说一下Rust中的变量,本文将结合两个实例来说明,这两个实例分别是:
    1、在华氏度和摄氏度之间转换温度
    2、生成指定个数(n)的斐波那契数列
    如果你看过rust的官方手册,那你会熟悉,这两个例子事实上就是官方留的“课后习题”

    在进入实例之前,还是要先来简单看看变量的使用。

    变量的申明

    1、关键词let
    在rust中,申明变量的关键词就是let。如果你使用过其他语言,那么你可能对rust的变量申明有疑问,为什么用let?
    我觉得不用纠结,这就是一个定义,使用什么单词来作为关键词并不重要,重要的是,这个关键词的作用。

    fn main() {
        let x=5;
        println!("x is {x}");
    }
    
    • 1
    • 2
    • 3
    • 4

    上面的示例程序段,是在主函数中创建了变量x,并打印,在终端输入cargo run,输出如下:

    PS D:\008 rustpro\var> cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.02s
         Running `target\debug\var.exe`
    x is 5
    
    • 1
    • 2
    • 3
    • 4

    2、变量的可变与不可变
    变量,顾名思义,就是可变化的量,不可变的是变量吗?不可变的应该是常量吧?
    在rust中,默认情况下,变量是不可变的。
    这是rust的设计,它的目的,当然是为了安全。“安全”是贯穿rust整个体系的,后面会有更多更复杂更高级的功能,都会和它有关。

    fn main() {
        let x=5;
        println!("x is {x}");
        x=x+1;
        println!("x is {x}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对前面的函数稍作修改,给变量x再次赋值,再看输出:

    PS D:\008 rustpro\var> cargo run
       Compiling var v0.1.0 (D:\008 rustpro\var)
    error[E0384]: cannot assign twice to immutable variable `x`
     --> src\main.rs:4:5
      |
    2 |     let x=5;
      |         -
      |         |
      |         first assignment to `x`
      |         help: consider making this binding mutable: `mut x`
    3 |     println!("x is {x}");
    4 |     x=x+1;
      |     ^^^^^ cannot assign twice to immutable variable
    
    For more information about this error, try `rustc --explain E0384`.
    error: could not compile `var` (bin "var") due to previous error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    错误提示,cannot assign twice to immutable variable,不能给“immutable variable”分配两次,什么叫immutable variable?就是不可变变量。
    所以,现在你就知道,rust是比较奇葩的了。
    但是,这就是rust的设计,如果你定义变量的时候,只是简单的let x=5;那么这个x是不可变的。那如果要可变呢:

    let mut x=5
    • 1

    再加一个关键词mut,mut其实就是mutable的简写,可变的。

    fn main() {
        let mut x=5;
        println!("x is {x}");
        x=x+1;
        println!("x is {x}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    cargo run:

    PS D:\008 rustpro\var> cargo run
       Compiling var v0.1.0 (D:\008 rustpro\var)
        Finished dev [unoptimized + debuginfo] target(s) in 0.43s
         Running `target\debug\var.exe`
    x is 5
    x is 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以发现,“变量x”的值可以多次分配,也就是真正的可变了。
    看到这里你发现什么?
    你会发现,在rust里,你对一个对象,比如变量的定义,必须要十分清楚,可变就可变,不可变就不可变,
    这样一来,rust编译器就能非常清楚地知道,你定义这个变量的目的,它会编译过程对你的意图全程跟踪,
    不可变变量如果不小心让它变化,编译器就会报错,提示你,对你来说,这就是安全。
    你不用等程序都运行了,然后偶尔崩溃了一次,然后去找bug,找来找去无论怎么编译就是没问题,但一运行就报错,这是令人崩溃的。

    你已经了解到rust中变量的奇葩性了,我们接着再看几个,然后再进入实例。

    3、常量
    rust中常量用const来申明,这个const应该是比较熟悉的单词了,很多编程语言申明常量都是用它吧。
    const也就是Constants的简写。
    看一下申明的格式吧:

    const C_1=5
    • 1

    如果你真的这么写,cargo check一下,会报错:

    PS D:\008 rustpro\var> cargo check
        Checking var v0.1.0 (D:\008 rustpro\var)
    error: missing type for `const` item
     --> src\main.rs:6:14
      |
    6 |     const C_1=5;
      |              ^ help: provide a type for the constant: `: i32`
    
    error: could not compile `var` (bin "var") due to previous error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    rust编译器提示你,需要给常量提供一个数据类型,并且给了你一个选项:i32
    rust中的数据类型----整数型:
    在这里插入图片描述
    关于数据类型,其实我认为不用多说,照着手册看看了解一下即可,因为一般来说,对编程有所了解的,对数据类型一般都不陌生,可以说,几乎所有语言中的数据类型,都大同小异。
    常见的比如说整数、浮点数、布尔量、字符串等,还有一些复杂的,比如结构体(复合体)、数组、列表、元组等。

    我们接着说常量,上面说这样写const C_1=5;会报错,原因是未给常量提供数据类型,所以我们应该这么来申明常量

    const C_1:i32=5
    • 1

    rust就是这样,它要求你明确,哪怕是常量,它不会为你隐式转换,它要求你定义常量时,明确常量的数据类型。
    到此,你会发现,rust似乎很“麻烦”,这也要明确,那也要明确,为什么不能像其他语言一样,直接a=5;或者const a=5;
    我只能说,rust它就是这样一门语言,如果你对此实在不能接受,那么放弃是好的,如果无所谓,那么就按照rust的规则,暂时先走下去。

    4、变量的覆盖
    我们在本文前面说过,rust中的变量有可变和不可变的区别,直接申明变量,默认不可变,如果要可变,需加关键词mut。
    但现在我们来看另一种使用:

    fn main() {
      let x=5;
      println!("x is {x}");
      let x:char='a';
      println!("x is {x}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们在第一行定义x变量,且赋值为5,但没有用mut使其可变,所以x是不可变的,但是我们在第三行再次使用x变量,而且给它赋值为字符‘a’。
    我们先cargo run一下:

    PS D:\008 rustpro\var> cargo run
       Compiling var v0.1.0 (D:\008 rustpro\var)
        Finished dev [unoptimized + debuginfo] target(s) in 0.63s
         Running `target\debug\var.exe`
    x is 5
    x is a
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到,rust正常编译,且结果x的值被改变了。你应该看到区别,就是第三行再次使用x时,重新定义了一次。
    rust中,用let来定义变量,是可以重名的,并且后一个可以覆盖前面的,所谓“shadow”,即旧的变量活在新的变量的阴影里
    你可能会觉得,这样是不是会造成困扰,如果不小心使用了一样的变量名,覆盖之前的变量数据,岂不是会影响程序。但这个问题容易解决,只需要你在命名变量时有自己的规律即可,但是rust中这样的机制会带来好处。
    我们看到,同样的变量x,在第一次和第二次的定义中,不仅值可以不同,连数据类型也可以不同。
    什么场景会这样使用,比如你要输入一个值,是字符类型,但程序接收字符后,要以整数型来运算,但是这两个其实一个东西,如果是其他语言,你可能会这样定义变量,x_str和x_int,但是rust中你可以只定义一个x,

    let x:char='a';
    let x:i32=5;
    
    • 1
    • 2

    好了,关于变量就先说到这,基本上差不多了,当然这并非是说把变量的所有方面都涉及到了,只是说基本的概念都已经了解了。
    下面将开始实例:

    一、温度转换

    1、新建一个temp项目

    PS D:\008 rustpro> cargo new temp
         Created binary (application) `temp` package
    PS D:\008 rustpro> cd temp
    PS D:\008 rustpro\temp> cargo build
       Compiling temp v0.1.0 (D:\008 rustpro\temp)
        Finished dev [unoptimized + debuginfo] target(s) in 0.49s
    PS D:\008 rustpro\temp> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    新建项目后,rust会自动生成一个main.rs主函数。
    这个主函数里默认的是打印“hello world”,我们要修改它,以达到温度转换的功能。
    华氏度和摄氏度的转换关系如下:

    1 摄氏度=33.8 华氏度

    所以,如果我们输入一个摄氏度值为x,那么转换为华氏度应该是:f(x)=33.8*x
    数学关系很清楚,是很简单的一元一次函数。
    先看一下main.rs的结构:

    fn main() {
        println!("Hello, world!");
    }
    
    • 1
    • 2
    • 3

    fnlet一样,是关键词,定义函数,所以fn后面的main就是函数,只不过它是主函数,所以地位特殊,但是,main这个名字,想必大家也不陌生,无论是C还是python,都有主函数,地位也差不多,谁让它是main呢!
    但我们先不修改main,我们先新建一个函数,就叫temp_convert:

    fn temp_convert(){
        
    }
    
    • 1
    • 2
    • 3

    添加逻辑代码:

    fn temp_convert(t:f32)->f32{
        let t2=t*33.8;
        return t2;
    }
    
    • 1
    • 2
    • 3
    • 4

    可以看到,我们在新建的函数基础上,添加了一个形式参数t,把它的数据类型定义为f32,即浮点数。
    定义了一个函数返回值类型:->f32,即函数返回值也是f32类型。
    然后在函数体内,定义了变量t2,其赋值为t*33.8,
    最后将计算后的t2的值返回。
    这个函数中形式参数t即输入的摄氏度,返回的t2即转换的华氏度。
    接下来,就是在主程序里调用了:
    修改一下main函数:

    fn main() {
        let t1=temp_convert(3.0);
        println!("t_f is {t1}");
    }
    
    • 1
    • 2
    • 3
    • 4

    因为函数temp_convert有返回值,所以我们定义一个变量t1来储存它。
    到此,简单的温度转换就实现了,只要给函数的参数赋予不同的值(摄氏度),函数就可以返回华氏度:

    PS D:\008 rustpro\temp> cargo run
       Compiling temp v0.1.0 (D:\008 rustpro\temp)
        Finished dev [unoptimized + debuginfo] target(s) in 0.42s
         Running `target\debug\temp.exe`
    t_f is 101.399994
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但这样肯定不算是一个完整的程序,我们设想应该是这样,程序启动,会输出一个文字提示,让我们输入一个摄氏度,我们输入后,程序输出一个华氏度。然后能循环,这样才算是完整的转换程序。
    我们分两部分来说,一是如何接受用户输入,二是如何循环。

    1 用户输入

    用户输入,需要用到IO接口,在rust中即std::io
    std::io,这是一个程序包crate,类似于其他程序的模块。在使用它之前,需要先导入它。

    use std::io;
    
    • 1

    然后使用它:

    use std::io;
    fn main() {
        let mut t_in=String::new();
        println!("请输入摄氏度值:");
        io::stdin()
            .read_line(&mut t_in)
            .expect("error");
        let t_in:f32=t_in.trim().parse().expect("error");
        let t1=temp_convert(t_in);
        println!("华氏度是 {t1}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面的主函数main变化较大,我们一一来说一下。我们使用use关键词导入了std::io,这是用来接受用户输入的模块。
    然后在主函数体内添加程序:

    let mut t_in=String::new();
    
    • 1

    这行是定义了一个可变变量t_in且类型为string

      io::stdin()
            .read_line(&mut t_in)
            .expect("error");
    
    • 1
    • 2
    • 3

    这行就是io接口的调用,用于读取用户的输入,read_line即是std::io模块的函数。&mut t_in是将我们定义的变量t_in来接受read_line的输入值。
    其实上面这行直接写如下:

    io::stdin().read_line(&mut t_in).expect("error");
    
    • 1

    之所以写成多行样式,是为了便于阅读。
    这里的expect是对潜在错误的处理,关于错误的处理不在本文的范围,后续会有专门叙述。此处只要知道这样用就可以了。
    我们现在已经从用户处接受到输入值,即t_in,接下来要对输入值进行处理:

    let t_in:f32=t_in.trim().parse().expect("error");
    
    • 1

    此处我们使用前面介绍的变量覆盖,先定义了t_in为用户输入,且数据类型为string类型,接着此行用同名变量t_in定义为f32类型,覆盖之前的字符串类型。
    其中的trim是消除一个字符串的开头和结尾的多余空格,parse是将字符串转为另一种数据类型,在此处就是转为f32类型。expect我们已经说过,是对返回值的判断时可能发生的错误处理。

    `let t1=temp_convert(t_in);
    
    • 1

    在顺利获得用户输入的值且转为浮点类型后,我们调用写好的temp_convert转换函数,其形式参数t在调用时传入实际参数t_in。将temp_convert函数的返回值,传给变量t1。
    完整程序:

    use std::io;
    fn main() {
        let mut t_in=String::new();
        println!("请输入摄氏度值:");
        io::stdin()
            .read_line(&mut t_in)
            .expect("error");
        let t_in:f32=t_in.trim().parse().expect("error");
        let t1=temp_convert(t_in);
        println!("华氏度是 {t1}");
    }
    fn temp_convert(t:f32)->f32{
        let t2=t*33.8;
        return t2;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在终端运行cargo run:

    PS D:\008 rustpro\temp> cargo run
       Compiling temp v0.1.0 (D:\008 rustpro\temp)
        Finished dev [unoptimized + debuginfo] target(s) in 0.74s
         Running `target\debug\temp.exe`
    请输入摄氏度值:
    2
    华氏度是 67.6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    好了,第一部分完成了,现在我们能成功根据输入的摄氏度转为华氏度且输出,但缺点是每次运行只能进行一次,所以需要循环。

    循环

    想让程序循环起来,也很简单,使用loop指令,它会循环执行主程序的逻辑,直到触发停止。本文中未添加明确的停止程序,因为这不在我们的学习范围,后续会有说明,此文不赘述。

    fn main() {
        loop{
            let mut t_in=String::new();
            println!("请输入摄氏度值:");
            io::stdin()
                .read_line(&mut t_in)
                .expect("error");
            let t_in:f32=t_in.trim().parse().expect("error");
            let t1=temp_convert(t_in);
            println!("华氏度是 {t1}");
        }
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们可以看到,只需要将main中的原程序添加进loop的{}中即可。
    让我们运行一下:

    PS D:\008 rustpro\temp> cargo run
       Compiling temp v0.1.0 (D:\008 rustpro\temp)
        Finished dev [unoptimized + debuginfo] target(s) in 0.46s
         Running `target\debug\temp.exe`
    请输入摄氏度值:
    2
    华氏度是 67.6
    请输入摄氏度值:
    3
    华氏度是 101.399994
    请输入摄氏度值:
    4
    华氏度是 135.2
    请输入摄氏度值:
    1
    华氏度是 33.8
    请输入摄氏度值:
    2
    华氏度是 67.6
    请输入摄氏度值:
    r
    thread 'main' panicked at src\main.rs:10:42:
    error: ParseFloatError { kind: Invalid }
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    error: process didn't exit successfully: `target\debug\temp.exe` (exit code: 101)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    可以看到,程序在不停地执行,如果用户一直输入正确的值得话,但你会发现在终端的记录中,最后输入r时,rust报错且退出了,这是因为程序中没有对非法输入的应对措施,当输入非法值时,只能报错退出。
    但我不准备在本文中完善它,我相信随着我们学习rust的深入,这个问题会迎刃而解。
    如果你看到这里,我相信你对rust中变量、函数的使用,有了初步的印象了。
    下面我们说一下第二个实例,即实现斐波那契数列。

    斐波那契数列

    斐波那契数列应该是比较经典的练手题目了,你在学习其他语言如C、python、java中都可以通过编写斐波那契数列来加深对语言的理解。
    rust也不例外,我在文章一开头就说过,此文所列举的两个实例,都是rust官方手册中给读者留的课后习题。
    其实官方在手册的第二章给了一个很好的实例即猜谜游戏,说的非常详细,但我们作为学习者,在学习了官方手册后,自己要学会应用,所以我选择用课后习题来作为示例。

    斐波那契数列是这样的数列:

    1,1,2,3,5,8,13,21,34,55,89…

    这个数列的规律是,从第三项开始,每一项的值都是它前面两项的和。用公式表示为:

    f(n)=f(n-1)+f(n-2);n>2

    所以,如果要用函数来实现斐波那契数列,函数肯定是递归的。

    rust实现,首先是创建新项目,这个不用多说了。
    在这里插入图片描述
    接着创建新的函数,命名为fibonacci

    fn fibonacci(num:i32)->i32{
       if num<3{
        return 1;
       }
       else{
        return fibonacci(num-1)+fibonacci(num-2);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个fibonacci函数有个形式参数num,数据类型为i32,函数的返回值类型由’->'符号指出,也是i32
    函数体内的逻辑,是对num进行判断,如果num小于3,也就是fibonacci数列的前2项,那么函数返回值固定为1,如果num大于等于3,那么返回值就等于前2项之和。
    此处的判断,使用了if。。else语句。if else应该不用多说了吧,这应该是学习编程的人,都会了解的。
    几乎你学习任何编程语言,都会遇到if语句。
    所以,本文也不会赘述if。。else语句。
    接下来看main函数:

    use std::io;
    fn main() {
        loop{
            println!("please enter fibonacci number:");
            let mut number=String::new();
            io::stdin()
                .read_line(&mut number)
                .expect("err");
            let number:i32=number.trim().parse().expect("err");
            println!("number is {number}");
            for y in 1..number{
                let x=fibonacci(y);
                println!("fibo({y}) is {x}");
            }
        }  
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以看到,几乎和前面的实例一样,一点区别在于子函数fibonacci的调用处,这里用了for语句:
    之所以使用for语句,因为斐波那契数列是一个数列,当我们给定一个斐波那契数列的项索引(n)时,我们实际上希望程序能够列出至n项的斐波那契数列所有的项。

     for y in 1..number{
                let x=fibonacci(y);
                println!("fibo({y}) is {x}");
            }
    
    • 1
    • 2
    • 3
    • 4

    同理,for循环也是常见的,和if语句一样,我们会在后续的文章里统一说明,此文不赘述。
    我们还是看一下实际运行效果吧:

    PS D:\008 rustpro\fibonacci> cargo run
       Compiling fibonacci v0.1.0 (D:\008 rustpro\fibonacci)
        Finished dev [unoptimized + debuginfo] target(s) in 0.71s
         Running `target\debug\fibonacci.exe`
    please enter fibonacci number:
    2
    number is 2
    fibo(1) is 1
    please enter fibonacci number:
    5
    number is 5
    fibo(1) is 1
    fibo(2) is 1
    fibo(3) is 2
    fibo(4) is 3
    please enter fibonacci number:
    10
    number is 10
    fibo(1) is 1
    fibo(2) is 1
    fibo(3) is 2
    fibo(4) is 3
    fibo(5) is 5
    fibo(6) is 8
    fibo(7) is 13
    fibo(8) is 21
    fibo(9) is 34
    please enter fibonacci number:
    r
    thread 'main' panicked at src\main.rs:10:46:
    err: ParseIntError { kind: InvalidDigit }
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    error: process didn't exit successfully: `target\debug\fibonacci.exe` (exit code: 101)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    总结一下,rust变量:

    let x=5;//x不可变
    
    • 1
    let mut x=5;//x可变
    
    • 1
    let x=5;
    let x='a';//x依旧不可变,但可以被重名者覆盖,且可不同数据类型
    
    • 1
    • 2
    const x:i32=5;//常量需指定数据类型
    
    • 1
  • 相关阅读:
    .NET Worker Service 作为 Windows 服务运行及优雅退出改进
    安全和便捷:如何将运营商二要素API应用于实名制管理中
    岭回归自动调参
    利用maven的dependency插件分析工程的依赖
    存储服务器特征是什么
    python到底是不是数据分析最好的语言
    如何学习能减少对现有工作的依赖?
    花木兰荣耀典藏皮肤特效一览 花木兰九霄神辉值得入手吗
    8月8本程序员新书:Rust、软技能、Python、量子...
    工控机上安装服务器级虚拟机vmware ESXi6.7
  • 原文地址:https://blog.csdn.net/normer123456/article/details/134199205