变量和可变性
变量默认是不可变(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);
}
- 常量可以在任何地方定义,包括全局的!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
变量遮蔽和可变变量的比较:
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 givingguess
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);
}
文章评论