Rust的一般概念(rust是解释型语言吗)
变量和可变性
变量默认是不可变(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的变量很像,但有一些不同:
你不能用mut来修饰常量,常量不仅仅叫做默认不可变,它是永远不能变!
常量用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); } 复制代码
常量可以在任何地方定义,包括全局的!let只能在函数中使用,如果我们将
let y = 7
移动到main函数外面的,编译会报错!
let y = 7; // 报错!error: expected item, found keyword `let` fn main() { ... 复制代码
常量不能通过函数调用赋值,只能使用常量表达式赋值;
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
变量遮蔽和可变变量的比较:
变量遮蔽每次都需要关键字let,是新建一个变量!可变变量只使用过一次let,并需要用mut来修饰变量,第二次赋值不是新建变量;
变量遮蔽可以改变值的类型
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 复制代码
读取元组数据的方法:
将元组赋值给另外一个由变量组成的元组,然后用元组中的变量获得对应的值!
使用.(点)运算符和序号的方式获取,序号从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
链接:https://juejin.cn/post/7046761118101930021