0


Rust通用编程概念

文章目录

变量与可变性

变量与可变性

在Rust中,声明变量使用let关键字,并且默认情况下,声明的变量是不可变的,要使变量可变需要在声明变量时,在变量前面加上mut关键字。如下:

fnmain(){letmut x =10;println!("x = {}", x);//x = 10
    x =20;println!("x = {}", x);//x = 20}

如果将上述代码中的mut关键字去掉,那么在编译代码时就会报错,报错结果就是不能对不可变的变量进行二次赋值,也就是不能对不可变的变量进行修改。如下:

在这里插入图片描述

常量

  • 常量(constant)与不可变的变量一样,在绑定值以后也是不可变的。
  • 在程序在运行期间,常量在其声明的作用域内一直有效。

需要注意的是,Rust里常量使用全大写字母,每个单词之间用下划线分开。如下:

constMAX_POINTS:u32=100_000;

常量与不可变的变量的区别:

  • 常量不可以使用mut关键字,因为常量永远都是不可变的。
  • 声明常量使用const关键字,并且常量的类型必须被显示标注。
  • 常量可以在任何作用域内进行声明,包括全局作用域。
  • 常量只能绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值。
注:可以在数字字面值中插入下划线来增强可读性。

隐藏(Shadowing)

在Rust中,可以使用相同的名字声明新的变量,新的变量会隐藏之前声明的同名变量,在后续的代码中这个变量名代表的就是新的变量。如下:

fnmain(){let x =10;let x = x +1;println!("x = {}", x);//x = 11}

此外,使用mut关键字声明变量x也可以达到上述效果。如下:

fnmain(){letmut x =10;
    x = x +1;println!("x = {}", x);//x = 11}

但隐藏和把变量声明为mut是不同的:

  • 在第一份代码中,使用let声明的同名新变量是不可变的,而第二份代码中变量x是可变的。
  • 对于隐藏来说,使用let声明的同名新变量的类型可以与之前的类型不同。

比较常见的使用场景就是数字字符串解析,隐藏可以让解析后的整型复用之前数字字符串的变量名。如下:

fnmain(){let spaces ="    ";let spaces = spaces.len();println!("spaces = {}", spaces);//spaces = 4}

如果Rust没有隐藏这个特性,那么我们就需要重新取一个变量名来避免命名冲突。如下:

fnmain(){let spaces_str ="    ";let spaces_num = spaces_str.len();println!("spaces_num = {}", spaces_num);//spaces_num = 4}

数据类型

数据类型

  • Rust是静态编程语言,在编译时必须知道所有变量的类型,而基于变量使用的值,编译器通常能够推断出变量的具体类型。
  • 如果变量可能的类型比较多,就必须显示标注出变量的类型,否则编译器就会报错。

例如将字符串类型转换成整型的parse方法,就必须添加类型的标注,因为parse方法的解析结果可以是多种数值类型。如下:

fnmain(){let year:u32="2023".parse().expect("无法解析为整数类型!");//显示标注变量类型println!("year = {}", year);//year = 2023}

标量类型

标量类型

  • 标量类型代表一个单独的值。
  • Rust有四种基本的变量类型:整数类型、浮点类型、布尔类型、字符类型。

整数类型

整数类型

Rust中的整数类型如下:
长度有符号无符号8-biti8u816-biti16u1632-biti32u3264-biti64u64128-biti128u128archisizeusize
说明一下:

  • 无符号整数类型以u开头,有符号整数类型以i开头,u或i后面的数字表示整数的长度。
  • 有符号整数的取值范围: − 2 n − 1 -2^{n-1} −2n−1 到 2 n − 1 − 1 2^{n-1}-1 2n−1−1,无符号整数的取值范围:0 到 2 n − 1 2^n-1 2n−1 。

isize和usize类型

isize和usize类型的位数由程序运行的计算机的架构所决定:

  • 如果是64位计算机,那么isize和usize就相当于i64和u64。
  • 如果是32为计算机,那么isize和usize就相当于i32和u32。
注:isize和usize通常用于对集合进行索引操作。

整数字面值

  • 除了byte类型外,所有的字面值都允许使用类型后缀。例如57u8。
  • 如果不太清楚应该使用哪种类型,可以使用Rust相应的默认类型。
  • 整数类型的默认类型就是i32,该类型在大部分情形下都是运算速度最快的,即使是在64位系统中。

整数字面量的表示形式:
整数字面量示例Decimal(十进制)98_222Hex(十六进制)0xffOctal(八进制)0o77Binary(二进制)0b1111_0000Byte(单字节字符)(仅限于u8)b’A’
可以尝试将以上示例的值进行打印,结果如下:

fnmain(){let a =98_222;let b =0xff;let c =0o77;let d =0b1111_0000;let e =b'A';let f =12f64;println!("a = {}", a);//a = 98222println!("b = {}", b);//b = 255println!("c = {}", c);//c = 63println!("d = {}", d);//d = 240println!("e = {}", e);//e = 65}
注:可以在整数字面量中添加下划线来增强可读性。

整数溢出

  • 由于整数是保存在内存中一个固定长度的空间的,因此它能存储的最大值和最小值是固定的,如果我们尝试存储一个数,而这个数又大于该整数类型所能存储的最大值,那么就会导致整数溢出。
  • 在调试模式下编译,Rust会检查整数溢出,如果发生溢出,程序在运行时就会触发panic;在发布模式下编译,Rust不会检查可能触发panic的整数溢出,而会进行二进制补码环绕。

例如u8类型的范围是0-255,如果将u8变量的值更新为256,那么就会导致整数溢出。如下:

fnmain(){let a:u8=255;let b:u8=1;println!("a = {:?}, b = {:?}", a, b);println!("a + b = {:?}", a + b);//a + b = 0}

上述代码在debug模式和release模式下均能成功编译。如下:

在这里插入图片描述

由于该代码会导致整数溢出,因此在debug模式生成的可执行程序在运行时会触发panic,而release模式生成的可执行程序会进行二进制补码环绕,使得256变成了0。如下:

在这里插入图片描述

浮点类型

浮点类型

  • Rust中有两种基础的浮点类型,分别是f32(单精度浮点数)和f64(双精度浮点数)。
  • Rust中的浮点数类型采用IEEE-754标准来表述,其中f64是默认的浮点数类型,因为在现代CPU上f64和f32的运行效率差不多,但f64的精度更高。

例如下面声明的x变量默认是f64类型,而y变量则是我们标注的f32类型。如下:

fnmain(){let x =0.2;//f64let y:f32=0.3;//f32}

数值运算

对于所有的数值类型,Rust都支持常见的加、减、乘、除、余等数值运算。如下:

fnmain(){let sum =10+20;let dif =10.1-20.2;let mul =10*20;let div =10.1/20.2;let rem =10%20;}

布尔类型

布尔类型

Rust中的布尔类型可以是true或false,占据单个字节大小。如下:

fnmain(){let a =true;//boollet b:bool=false;//bool}

字符类型

字符类型

  • Rust语言中的char类型被用来描述语言中最基础的单个字符,字符类型的字面值使用单引号。
  • Rust中的字符类型占用4字节大小,是Unicode标量值,可以表示比ASCII更多的字符内容,比如拼音、中日韩文、emoji表情等。

例如下面的英文字母、中文汉字以及emoji表情都是字符类型。如下:

fnmain(){let x ='a';//charlet y:char='ℤ';//charlet z = '😁';//charprintln!("{} {} {}", x, y, z);}
注:Unicode标量值范围为U+0000到U+D7FF,以及U+E000到U+10FFFF。

复合类型

复合类型

  • 复合类型可以将多个不同类型的值组合为一个类型。
  • Rust提供了两种内置的复合类型:元组(tuple)、数组(array)。

元组

元组

  • 元组可以将多个不同类型的多个值组合进一个复合类型中。
  • 元组的长度是固定的,一旦声明就无法改变。

声明元组时将一系列值使用逗号分隔后,放置到一对圆括号中即可。如下:

fnmain(){let tup:(i32,f64,u8)=(32,6.4,8);}

说明一下:

  • 声明元组时也可以标注类型,标注时使用一个圆括号,在圆括号中依次标注元组中对应位置的元素类型即可。
  • 不带任何值的元组叫做单元(unit)元组,这种值以及对应的类型都写作(),表示空值或空的返回类型。(如果表达式不返回任何其他值,则会隐式返回单元值)

访问元组元素

访问元组中的元素使用点标记法,在点的后面指明元素的索引号即可。如下:

fnmain(){let tup:(i32,f64,u8)=(32,6.4,8);println!("{} {} {}", tup.0, tup.1, tup.2);//32 6.4 8}

此外,还可以使用模式匹配对元组进行解构(destructuring),从而获取元组中的元素值。如下:

fnmain(){let tup:(i32,f64,u8)=(32,6.4,8);let(x, y, z)= tup;println!("x = {}, y = {}, z = {}", x, y, z);//x = 32, y = 6.4, z = 8}

数组

数组

  • 数组也可以将多个值组合在一起,但数组中每个元素的类型必须相同。
  • 数组的长度也是固定的,一旦声明就无法改变。

声明元组时将一系列值使用逗号分隔后,放置到一对中括号中即可。如下:

fnmain(){let a =[1,2,3,4,5];}

说明一下:

  • 数组的类型表示为[type; len],比如上述代码中数组a的类型为[i32; 5]

此外,如果数组中每个元素的值相同,那么可以以

[val; len]

的方式声明数组,表示声明的数组中有len个元素,每个元素的值都是val。如下:

fnmain(){let a =[3;5];//等价于let a = [3, 3, 3, 3, 3];}

访问数组元素

访问数组中的元素使用索引的方式,在中括号中指明元素的索引号即可。如下:

fnmain(){let a =[1,2,3,4,5];let first = a[0];//1let second = a[1];//2}

访问数组元素时,指明的索引值如果超出了数组的范围。如下:

fnmain(){let a =[1,2,3,4,5];let index =6;let num = a[index];//errorprintln!("num = {}", num);}

对于上面这种简单的代码逻辑,编译时会检查出越界并产生报错。如下:

在这里插入图片描述

但如果让用户输入一个数字,作为访问数组元素时的索引值。如下:

usestd::io;fnmain(){let a =[1,2,3,4,5];println!("请输入一个索引:");letmut index =String::new();io::stdin().read_line(&mut index).expect("无法读取行!");let index:usize= index.trim().parse().expect("无法解析为整数类型!");let num = a[index];println!("num = {}", num);}

那么在编译时是不会产生报错的,因为我们不知道程序运行后用户输入的数字是多少,但在运行程序后如果发现索引值越界,那么就会触发panic。如下:

在这里插入图片描述

函数

函数声明

  • 声明函数使用fn关键字,fn关键字后面是函数名。
  • 函数名后是一个小括号,小括号里面可以声明函数的参数。
  • 用一对花括号来标识函数体的开始和结尾。

下面的代码中定义了主函数和一个名为another_function的函数,并在主函数中调用了another_function函数。如下:

fnmain(){println!("main function");another_function();}fnanother_function(){println!("another function");}

说明一下:

  • 在Rust中,函数名和变量名均使用snack case命名规范,即以小写字母命名,多个单词之间使用下划线分开。
  • another_function函数的声明不用在main函数之前,只要another_function的定义对于使用区域是可见的即可。

函数参数

  • 如果函数有参数,那么在函数签名里必须声明每个参数的类型。
  • 函数签名中声明的参数叫做parameter,调用函数时传递的参数叫做argument。

下面给another_function定义了两个参数,并在main函数中对其进行了调用。如下:

fnmain(){another_function(10,20);//argument}fnanother_function(x:i32, y:i32){//parameterprintln!("x = {}, y = {}", x, y);}

语句与表达式

  • Rust是一个基于表达式的语言,所以它将语句(statement)与表达式(expression)区别为两个不同的概念。
  • 语句是指那些执行操作但不返回值的指令,而表达式则是指会进行计算并产生一个值作为结果的指令。
  • 函数体由若干条语句组成,并可以以一个表达式作为结尾。

例如下面声明变量时使用的代码就是一条语句,甚至下面整个例子本身也是一条语句。如下:

fnmain(){let x =10;}

由于语句是不会返回值的,因此不能一条语句赋值给另一个变量。如下:

fnmain(){let y =(let x =10);//error}

实际我们应该将一个表达式赋值给其他变量,因此编译上述代码你会看到如下报错:

在这里插入图片描述

此外,Rust中的代码块也是一个表达式,表达式的值就是代码块中最后一个表达式的值。如下:

fnmain(){let y ={let x =1;
        x +3};println!("y = {}", y);//y = 4}

注意:

  • 代码中的x+3后面不能加分号,因为加了分号后x+3;就变成了一个语句,此时该代码块不会返回任何值,或者说代码块返回的是一个单元元组()

函数返回值

  • ->符号后面可以声明函数的返回值类型,但是不可以为返回值命名。
  • 在Rust中,函数的返回值就是函数体中最后一个表达式的值。
  • 可以使用return关键字,并指定一个值来提前从函数中返回。

下面声明了一个plus_five函数,该函数接收一个i32类型的参数,并将该参数的值加5后进行返回。如下:

fnplus_five(val:i32)->i32{
    val +5}fnmain(){let x =plus_five(10);println!("x = {}", x);//15}

注释

注释

Rust中可以通过

//

/**/

进行单行或多行注释。如下:

/*
这是一个多行注释
...
...
*/fnmain(){//这是一个多行注释//...//.../*这是一个单行注释*/println!("Hello World");//这是一个单行注释}
注:Rust中还有一种注释叫做文档注释,后续再介绍。

控制流

if表达式

if表达式

  • if表达式允许你根据条件来执行不同的代码分支,但注意这个条件必须是bool类型的。
  • 在if表达式后面可以追加若干个else if表达式,最后还可以追加一个else表达式。

下面的代码中通过if表达式判断一个分数的等级。如下:

fnmain(){let score =95;if score >=0&& score <60{println!("不及格");}elseif score >=60&& score <80{println!("良好");}elseif score >=80&& score <=100{println!("优秀");}else{println!("成绩出错");}}

当else if表达式过多时,为了增加代码的可读性,最好使用match表达式对代码进行重构。如下:

fnmain(){let score =95;match score {0..=59=>println!("不及格"),60..=79=>println!("良好"),80..=100=>println!("优秀"),
        _ =>println!("成绩出错"),}}

在let语句中使用if

由于if是一个表达式,因此可以将其放在等号右边用于给其他变量赋值。如下:

fnmain(){let cond =true;let number =if cond {10}else{20};println!("number = {}", number);//number = 10}

注意:

  • if表达式后面如果跟有else if表达式或else表达式,那么这些代码块的返回值类型必须相同,因为Rust在编译时必须知道接收if表达式结果的变量的类型。

循环

loop循环

  • 使用loop关键字可以让Rust反复执行某一块代码。
  • 与其他语言类似,在loop循环中也可以使用continue和break关键字。
  • loop循环可以返回值,当使用break关键字跳出循环时,在break后面指明返回值即可。

下面代码中,当counter累加到10时,将counter乘2的值作为loop循环的返回值,并用result变量接收了这个返回值。如下:

fnmain(){letmut counter =0;let result =loop{
        counter +=1;if counter ==10{break counter *2;//loop循环的返回值}};println!("result = {}", result);//result = 20}

while循环

  • while循环能够在每次执行循环体之前都判断依次条件,如果条件满足则执行循环体。
  • 与其他语言类似,在while循环中也可以使用continue和break关键字。

下面代码中,当number的值减为0时便不再执行循环体。如下:

fnmain(){letmut number =3;while number !=0{println!("{}!", number);
        number -=1;}println!("LIFTOFF!!!");}

for循环

  • 使用loop和while可以用来遍历集合,这时需要我们对索引进行控制,比较容易出错并且低效。
  • for循环更简洁紧凑,它可以针对集合中的每个元素来执行一些代码。
  • for循环的安全性和间接性,使它成为了Rust中最常用的循环结构。

下面使用for循环对数组中的元素进行了一次遍历。如下:

fnmain(){let a =[10,20,30,40,50];for element in a.iter(){println!("value: {}", element);}}

说明一下:

  • 数组的iter方法会返回该数组的只读迭代器,for循环则是依次获取数组中每个元素的迭代器,然后以借用的方式获取元素的值,使得我们可以通过element在循环体中对数组中的元素进行访问。

此外,for循环可以搭配Range来对某个数字序列进行遍历。如下:

fnmain(){for number in(1..4).rev(){println!("{}!", number);}println!("LIFTOFF!!!");}

说明一下:

  • Range是标准库提供的,用来生成从一个数字开始到另一个数字结束之前的所有数字序列。
  • Range中的rev方法可以对Range生成的数字序列进行反转。

本文转载自: https://blog.csdn.net/chenlong_cxy/article/details/133894338
版权归原作者 2021dragon 所有, 如有侵权,请联系我们删除。

“Rust通用编程概念”的评论:

还没有评论