要么我说了算,要么我什么也不说 -- 拿破仑
今天,我们继续Rust学习笔记的探索。我们来谈谈关于基础概念的相关知识点。
如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。
变量与可变性 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
数据类型 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
Rust中函数 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
流程控制 推荐阅读指数 ⭐️⭐️⭐️⭐️
好了,天不早了,干点正事哇。
在Rust中变量默认是不可变的。
当一个变量是不可变的时,一旦它被绑定到某个值上面,这个值就再也无法被改变。
fn main(){
let x =7;
x = 8;
}
保存并通过命令cargo run来运行代码,会提示如下错误:

这里提示我们cannot assign twice to immutable variable x(不能对不可变量x进行二次赋值)
变量默认是不可变的,但你可以通过在声明的变量名称前添加mut关键字来使其可变。
fn main() {
let mut x =7;
println!("x的值是:{}",x);
x = 8;
println!("x的值是:{}",x);
}
保存并通过命令cargo run来运行代码,输出结果如下:
x的值是 7x的值是 8设计一个变量的可变性还需要考量许多因素。 当你在使用某些重型数据结构时,适当地使用可变性去修改一个实例,可能比赋值和重新返回一个新分配的实例更有效率 当数据结构较为轻量的时候,采用更偏向函数式的风格,通过创建新变量来进行赋值,可能会使代码更加易于理解。
变量的不可变性可能会让你联想到另外一个常见的编程概念:常量。
但是,常量和变量之间还存在着一些细微的差别
不能用mut关键字来修饰一个常量。 常量不仅是默认不可变的,它还总是不可变的 使用const关键字而不是let关键字来声明一个常量 在声明的同时,必须显示地标注值的类型 常量可以被声明在任何作用域中,甚至包括全局作用域。 这在一个值需要被不同部分的代码共同引用时十分有用 只能将常量绑定到一个常量表达式上,而无法将一个函数的返回值或其他需要在运行时计算的值绑定在常量上。
下面是声明常量的例子,数值100被绑定到了常量MAX_AGE上。在Rust中,约定俗成地使用下划线分隔的全大写字母来命令一个常量
fn main() {
const MAX_AGE:u32 = 100;
}
在Rust中,一个新的声明变量可以覆盖掉旧的同名变量,我们把这一个现象描述为:第一个变量被第二个变量{遮蔽|Shadow}了。这意味着随后使用这个名称时,它指向的将会是第二个变量。
fn main() {
let x =5;
let x = x + 1;
let x = x * 2;
println!("x的值为:{}",x)
}
x绑定到值为5上。let x =语句遮蔽了第一个x变量,并将第一个x变量值加上1的运行结果绑定到新的变量x上,此时x的值是6。let语句同样遮蔽了第二个x变量,并将第二个x变量值乘以2的结果12绑定到第三个x变量上。通过使用let,可以将对这个值执行一系列的变换操作,并允许这个变量在操作完成后保持自己的不可变性。
遮蔽机制与mut的一个区别在于:由于重复使用let关键字会创建出新的变量,所以可以在复用变量名称的同时改变它的类型。
fn main() {
let spaces:&str = "abc";
let spaces:usize= spaces.len();
}
第一个 spaces 变量是一个字符串类型,第二个 spaces 变量是一个数字类型。
Rust中每一个值都有其特定的数据类型,Rust会根据数据的类型来决定应该如何处理它们。
我们来介绍两种不同的数据类型子集:{标量类型|Scalar}和{复合类型|Compound}。
Rust是一门静态类型语言,这意味着它在编译程序的过程中需要知道所有变量的具体类型。
在大部分情况下,编译器都可以根据我们如何绑定、使用变量的值来自动推导出变量的类型。但是,在某些时候,当发生数据类型的转换时候,就需要显示地添加一个类型标注。
下面的test变量是将String类型转换为数值类型。
let test:u32 = "42".parse().expect("非数值类型")
标量类型是单个值类型的统称。
在Rust中内建了4种基础的标量类型:
整数是指那些没有小数部分的数字。在Rust中存在如下内建整数类型,每一个长度不同的值都存在有符号和无符号两种变体。
| 长度 | 有符号 | 无符号 |
|---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32(Rust默认) | u32 |
64-bit | i64 | u64 |
arch | isize | usize |
每一个整数类型的变体都会标明自身是否存在符号,并且拥有一个明确的大小。有符号和无符号代表了一个整数类型是否拥有描述负数的能力。
换句话说,
对于一个位数为n的有符号整数类型,它可以存储从-(2n-1)到(2n-1-1)范围内的所有整数。 而对于无符号整数类型而言,则可以存储从0到(2n-1)范围内的所有整数。
除了指明位数的类型,还有isize和usize两种特殊的整数类型,它们的长度取决于程序运行的目标平台。
64位架构上,它们就是64位的32位架构上,它们就是32位的Rust对于整数字面量的默认推导类型i32通常就是一个很好的选择:它在大部分情形下都是运算速度最快的那一个。
当Rust发生整数溢出时候,会执行二进制补码环绕。也就是说,任何超出类型最大值的整数都会被环绕为类型最小值。
Rust还提供了两种基础的浮点数类型,浮点数也就是带小数点的数字。这两种类型是f32和f64,它们分别占用了32位和64位空间。
在Rust中,默认会将浮点数字面量的类型推导为f64。
Rust的浮点数使用了IEEE-754标准来进行表述,f32和f64类型分别对应这标准中的单精度和双精度浮点数。
Rust的布尔类型只拥有两个可能的值true和false,它只会占据单个字节的空间大小。使用bool来表示一个布尔类型。
fn main(){
let t = true;
let f:bool = false;
}
在Rust中,char类型被用于描述语言中最基础的单个字符。
fn main(){
let c = 'a';
}
char类型使用单引号指定,字符串使用双引号指定。
在Rust中char类型占4字节,是一个Unicode标量值,这意味着它可以表示比ASCII多的字符内容。
{复合类型|Compound}可以将多个不同类型的值组合为一个类型。在Rust提供了两个内置的基础复合类型:{元组|Tuple}和{数组|Array}
元组可以将其他不同类型的多个值组合进一个复合类型中。元组还拥有一个固定的长度:你无法在声明结束后增加或减少其中的元素数量。
为了创建元组,需要把一系列的值使用逗号分隔后放置到一对圆括号中。元组每个位置都有一个类型,这些类型不需要是相同的。
fn main(){
let tup:(i32,f64,u8) = (500,7.8,1);
}
由于一个元组也被视为一个单独的复合元素,所以这里的变量tup被绑定到了整个元组上。为了从元组中获得单个的值,可以使用模式匹配来{解构|Destructuring}元组
fn main(){
let tup:(i32,f64,u8) = (500,7.8,1);
let (x,y,z) = tup;
}
除了解构,还可以通过索引并使用点号(.)来访问元组中的值。
fn main(){
let tup:(i32,f64,u8) = (500,7.8,1);
let firstValue = x.0;
let secondValue = x.1;
}
我们同样可以在数组中存储多个值的集合。与元组不同,数组中每一个元素都必须是相同类型。 Rust中数组拥有固定的长度,一旦声明就再也不能随意更改大小。
fn main(){
let a = [1,2,3,4,5];
}
当然,Rust标准库也提供了一个更加灵活的{动态数组|Vector}:它是一个类似于数组的集合结构,但它允许用户自由的调整数组的长度。这个我们后面的章节会有详细介绍。
为了写出数组的类型,你可以使用一对方括号,并在方括号中填写数组内所有元素的类型,一个分号及数组内元素的数量。
fn main(){
let a:[i32;5] = [1,2,3,4,5];
}
另外还有一种更简便的初始化数组的方式。在方括号中指定元素的值并接着填入一个分号及数组的长度。
fn main(){
let a =[3;5];
}
以a命令的数组将会拥有5个元素,而这些元素全部拥有相同的初始值3。
数组是一整块分配在栈上的内存组成,可以通过索引来访问一个数组中所有元素。
fn main(){
let a =[1,2,3,4,5];
let frist = a[0];
let second = a[1];
}
存在如下代码
fn main() {
let a = [1,2,3,4,5];
let index = 10;
let item = a[index];
}
使用cargo run运行这段代码,会发现程序顺利的通过编译,会在运行时因为错误而奔溃退出:

实际上,每次通过索引来访问一个元素时,Rust都会检查这个索引是否小于当前数组的长度。假如索引超出了当前数组的长度,Rust就会发生panic。
Rust代码使用{蛇形命名法|Snake Case} 来作为规范函数和变量名称的风格。蛇形命名法只使用小写的字母进行命名,并以下画线分隔单词。
fn main() {
another_function()
}
fn another_function(){
println!("函数调用")
}
在Rust中,函数定义以fn关键字开始并紧随函数名称与一对圆括号,还有一对花括号用于标识函数体开始和结尾的地方。
可以使用函数名加圆括号的方式来调用函数。Rust不关心在何处定义函数,只要这些定义对于使用区域是可见的既可。
还可以在函数声明中定义{参数|Argument},它们是一种特殊的变量,并被视作函数签名的一部分。当函数存在参数时,你需要在调用函数时为这些变量提供具体的值。
fn main() {
another_function(5)
}
fn another_function(x:i32){
println!("传入函数的变量为:{}",x)
}
在函数签名中,你必须显示地声明每个参数的类型。
函数体由若干语句组成,并可以以一个表达式作为结尾。由于Rust是一门基于表达式的语言,所以它将{语句|Statement}和{表达式|Expression}区别为两个不同的概念。
执行操作但不返回值的指令进行计算并产生一个值作为结果的指令使用let关键字创建变量并绑定值时使用的指令是一条语句。
fn main(){
let y = 6;
}
这里的函数定义同样是语句,甚至上面整个例子本身也是一条语句。
语句不会返回值
因此,在Rust中,不能将一条let语句赋值给另一个变量。
如下代码会产生编译时错误。
fn main(){
let x = (let y =6);
}
与语句不同,表达式会计算出某个值来作为结果。另外,表达式也可以作为语句的一部分。
调用函数是表达式调用宏是表达式{})同样也是表达式fn main(){
let x =5;
①let y = {②
let x =3;
③ x + 1
};
}
表达式②是一个代码块,它会计算出4作为结果。而这个结果会作为let语句①的一部分被绑定到变量y上。
函数可以向调用它的代码返回值。需要在箭头符号(->)的后面声明它的类型。
在Rust中,函数的返回值等同于函数体的最后一个表达式。
return关键字并指定一个值来提前从函数中返回fn five() ->i32{
5
}
fn main() {
let x = five();
println!("子函数返回的值为:{}",x)
}
如上的代码中,five函数的返回值类型通过-> i32被指定了。five函数中的5就是函数的输出值,这也就是它的返回类型会被声明为i32的原因。
在Rust中用来控制程序执行流的结构主要是if表达式和循环表达式。
if表达式允许根据条件执行不同的代码分支。
fn main() {
let number = 3;
if number <5 {
println!("满足条件")
}else{
println!("不满足条件")
}
}
所有的if表达式都会使用if关键字来开头,并紧随一个判断条件。其后的花括号中放置了条件为真时需要执行的代码片段。if表达式中与条件相关联的代码块被称为{分支|Arm}
条件表达式必须产生一个bool类型的值,否则会触发编译错误
在Rust中不会自动尝试将非布尔类型的值转换为布尔类型。必须显示地在if表达式中提供一个布尔类型作为条件。
由于if是一个表达式,所以可以在let语句的右侧使用它来生成一个值。
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("number的值为:{}",number)
}
代码块输出的值就是其中最后一个表达式的值。另外,数字本身也可以作为一个表达式使用。
上面的例子中,整个if表达式的值取决于究竟哪一个代码块得到执行。
所有if分支可能返回的值都必须是一种类型。
Rust提供了多种{循环|Loop}工具。一个循环会执行循环体中的代码直到结尾,并紧接着回到开头继续执行。
Rust提供了3种循环
loopwhilefor可以使用loop关键字来指示Rust反复执行某一块代码,直到显示地声明退出为止。
fn main() {
loop {
println!("重复执行")
}
}
运行这段程序时,除非手动强制退出程序,否则重复执行字样会被反复输出到屏幕中。
loop循环可以被用来反复尝试一些可能会失败的操作,有时候也需要将操作的结果传递给余下的代码。我们可以将需要返回的值添加到break表达式后面,也就是用来终止循环表达式后面。
fn main() {
let mut counter = 0;
let result = loop {
counter +=1;
if counter ==10 {
break counter *2;
}
};
println!("result的值为:{}",result)
}
上面的代码中,当counter值为10时候,就会走break语句,返回counter *2。并将对应的值返回给result。
另外一种常见的循环模式是在每次执行循环体之前都判断一次条件,假如条件为真则执行代码片段,假如条件为假或执行过程中碰到break就退出当前循环。
fn main() {
let mut counter = 3;
while counter!=0{
println!("{}",counter);
counter = counter -1;
}
}
fn main() {
let a = [1,2,3,4,5];
for element in a.iter() {
println!("当前的值为{}",element)
}
}
for循环的安全性和简洁性使它成为Rust中最为常用的循环结构。
阅读量:1646
点赞量:0
收藏量:0