mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-23 21:20:42 +08:00
437 lines
17 KiB
Markdown
437 lines
17 KiB
Markdown
[#]: subject: "Rust Basics Series #2: Using Variables and Constants in Rust Programs"
|
||
[#]: via: "https://itsfoss.com/rust-variables/"
|
||
[#]: author: "Pratham Patel https://itsfoss.com/author/pratham/"
|
||
[#]: collector: "lkxed"
|
||
[#]: translator: "Cubik65536"
|
||
[#]: reviewer: "wxy"
|
||
[#]: publisher: "wxy"
|
||
[#]: url: "https://linux.cn/article-15771-1.html"
|
||
|
||
Rust 基础系列 #2: 在 Rust 程序中使用变量和常量
|
||
======
|
||
|
||
![][0]
|
||
|
||
> 推进你的 Rust 学习,熟悉 Rust 程序的变量和常量。
|
||
|
||
在 [该系列的第一章][2]中,我讲述了为什么 Rust 是一门越来越流行的编程语言。我还展示了如何 [在 Rust 中编写 Hello World 程序][2]。
|
||
|
||
让我们继续 Rust 之旅。在本文中,我将向你介绍 Rust 编程语言中的变量和常量。
|
||
|
||
此外,我还将讲解一个称为“<ruby>遮蔽<rt>shadowing</rt></ruby>”的新编程概念。
|
||
|
||
### Rust 变量的独特之处
|
||
|
||
在编程语言中,变量是指 _存储某些数据的内存地址的一个别名_ 。
|
||
|
||
对 Rust 语言来讲也是如此。但是 Rust 有一个独特的“特性”。每个你声明的变量都是 **默认 <ruby>不可变的<rt>immutable</rt></ruby>** 。这意味着一旦给变量赋值,就不能再改变它的值。
|
||
|
||
这个决定是为了确保默认情况下,你不需要使用 <ruby>自旋锁<rt>spin lock</rt></ruby> 或 <ruby>互斥锁<rt>mutex</rt></ruby> 等特殊机制来引入多线程。Rust **会保证** 安全的并发。由于所有变量(默认情况下)都是不可变的,因此你不需要担心线程会无意中更改变量值。
|
||
|
||
这并不是在说 Rust 中的变量就像常量一样,因为它们确实不是常量。变量可以被显式地定义为可变的。这样的变量称为 **可变变量** 。
|
||
|
||
这是在 Rust 中声明变量的语法:
|
||
|
||
```
|
||
// 默认情况下不可变
|
||
// 初始化值是**唯一**的值
|
||
let variable_name = value;
|
||
|
||
// 使用 'mut' 关键字定义可变变量
|
||
// 初始化值可以被改变
|
||
let mut variable_name = value;
|
||
```
|
||
|
||
> 🚧 尽管你可以改变可变变量的值,但你不能将另一种数据类型的值赋值给它。
|
||
>
|
||
> 这意味着,如果你有一个可变的浮点型变量,你不能在后面将一个字符赋值给它。
|
||
|
||
### Rust 数据类型概观
|
||
|
||
在上一篇文章中,你可能注意到了我提到 Rust 是一种强类型语言。但是在定义变量时,你不需要指定数据类型,而是使用一个通用的关键字 `let`。
|
||
|
||
Rust 编译器可以根据赋值给变量的值推断出变量的数据类型。但是如果你仍然希望明确指定数据类型并希望注释类型,那么可以这样做。以下是语法:
|
||
|
||
```
|
||
let variable_name: data_type = value;
|
||
```
|
||
|
||
下面是 Rust 编程语言中一些常见的数据类型:
|
||
|
||
- **整数类型**:分别用于有符号和无符号的 32 位整数的 `i32` 和 `u32`
|
||
- **浮点类型**:分别用于 32 位和 64 位浮点数的 `f32` 和 `f64`
|
||
- **布尔类型**:`bool`
|
||
- **字符类型**:`char`
|
||
|
||
我会在下一篇文章中更详细地介绍 Rust 的数据类型。现在,这应该足够了。
|
||
|
||
> 🚧 Rust 并不支持隐式类型转换。因此,如果你将值 `8` 赋给一个浮点型变量,你将会遇到编译时错误。你应该赋的值是 `8.` 或 `8.0`。
|
||
|
||
Rust 还强制要求在读取存储在其中的值之前初始化变量。
|
||
|
||
```
|
||
{ // 该代码块不会被编译
|
||
let a;
|
||
println!("{}", a); // 本行报错
|
||
// 读取一个**未初始化**变量的值是一个编译时错误
|
||
}
|
||
|
||
{ // 该代码块会被编译
|
||
let a;
|
||
a = 128;
|
||
println!("{}", a); // 本行不会报错
|
||
// 变量 'a' 有一个初始值
|
||
}
|
||
```
|
||
|
||
如果你在不初始化的情况下声明一个变量,并在给它赋值之前使用它,Rust 编译器将会抛出一个 **编译时错误** 。
|
||
|
||
虽然错误很烦人,但在这种情况下,Rust 编译器强制你不要犯写代码时常见的错误之一:未初始化的变量。
|
||
|
||
### Rust 编译器的错误信息
|
||
|
||
来写几个程序,你将
|
||
|
||
- 通过执行“正常”的任务来理解 Rust 的设计,这些任务实际上是内存相关问题的主要原因
|
||
- 阅读和理解 Rust 编译器的错误/警告信息
|
||
|
||
#### 测试变量的不可变性
|
||
|
||
让我们故意写一个试图修改不可变变量的程序,看看接下来会发生什么。
|
||
|
||
```
|
||
fn main() {
|
||
let mut a = 172;
|
||
let b = 273;
|
||
println!("a: {a}, b: {b}");
|
||
|
||
a = 380;
|
||
b = 420;
|
||
println!("a: {}, b: {}", a, b);
|
||
}
|
||
```
|
||
|
||
直到第 4 行看起来都是一个简单的程序。但是在第 7 行,变量 `b` —— 一个不可变变量 —— 的值被修改了。
|
||
|
||
注意打印 Rust 变量值的两种方法。在第 4 行,我将变量括在花括号中,以便打印它们的值。在第 8 行,我保持括号为空,并使用 C 的风格将变量作为参数。这两种方法都是有效的。(除了修改不可变变量的值,这个程序中的所有内容都是正确的。)
|
||
|
||
来编译一下!如果你按照上一章的步骤做了,你已经知道该怎么做了。
|
||
|
||
```
|
||
$ rustc main.rs
|
||
error[E0384]: cannot assign twice to immutable variable `b`
|
||
--> main.rs:7:5
|
||
|
|
||
3 | let b = 273;
|
||
| -
|
||
| |
|
||
| first assignment to `b`
|
||
| help: consider making this binding mutable: `mut b`
|
||
...
|
||
7 | b = 420;
|
||
| ^^^^^^^ cannot assign twice to immutable variable
|
||
|
||
error: aborting due to previous error
|
||
|
||
For more information about this error, try `rustc --explain E0384`.
|
||
```
|
||
|
||
> 📋 “binding” 一词是指变量名。但这只是一个简单的解释。
|
||
|
||
这很好的展示了 Rust 强大的错误检查和信息丰富的错误信息。第一行展示了阻止上述代码编译的错误信息:
|
||
|
||
```
|
||
error[E0384]: cannot assign twice to immutable variable b
|
||
```
|
||
|
||
这意味着,Rust 编译器注意到我试图给变量 `b` 重新赋值,但变量 `b` 是一个不可变变量。所以这就是导致这个错误的原因。
|
||
|
||
编译器甚至可以识别出错误发生的确切行和列号。
|
||
|
||
在显示 `first assignment to b` 的行下面,是提供帮助的行。因为我正在改变不可变变量 `b` 的值,所以我被告知使用 `mut` 关键字将变量 `b` 声明为可变变量。
|
||
|
||
> 🖥️ 自己实现一个修复来更好地理解手头的问题。
|
||
|
||
#### 使用未初始化的变量
|
||
|
||
现在,让我们看看当我们尝试读取未初始化变量的值时,Rust 编译器会做什么。
|
||
|
||
```
|
||
fn main() {
|
||
let a: i32;
|
||
a = 123;
|
||
println!("a: {a}");
|
||
|
||
let b: i32;
|
||
println!("b: {b}");
|
||
b = 123;
|
||
}
|
||
```
|
||
|
||
这里,我有两个不可变变量 `a` 和 `b`,在声明时都没有初始化。变量 `a` 在其值被读取之前被赋予了一个值。但是变量 `b` 的值在被赋予初始值之前被读取了。
|
||
|
||
来编译一下,看看结果。
|
||
|
||
```
|
||
$ rustc main.rs
|
||
warning: value assigned to `b` is never read
|
||
--> main.rs:8:5
|
||
|
|
||
8 | b = 123;
|
||
| ^
|
||
|
|
||
= help: maybe it is overwritten before being read?
|
||
= note: `#[warn(unused_assignments)]` on by default
|
||
|
||
error[E0381]: used binding `b` is possibly-uninitialized
|
||
--> main.rs:7:19
|
||
|
|
||
6 | let b: i32;
|
||
| - binding declared here but left uninitialized
|
||
7 | println!("b: {b}");
|
||
| ^ `b` used here but it is possibly-uninitialized
|
||
|
|
||
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||
|
||
error: aborting due to previous error; 1 warning emitted
|
||
|
||
For more information about this error, try `rustc --explain E0381`.
|
||
```
|
||
|
||
这里,Rust 编译器抛出了一个编译时错误和一个警告。警告说变量 `b` 的值从来没有被读取过。
|
||
|
||
但是这是荒谬的!变量 `b` 的值在第 7 行被访问了。但是仔细看;警告是关于第 8 行的。这很令人困惑;让我们暂时跳过这个警告,继续看错误。
|
||
|
||
这个错误信息说 `used binding b is possibly-uninitialized`。和之前的例子一样,Rust 编译器指出错误是由于尝试在第 7 行读取变量 `b` 的值而引起的。读取变量 `b` 的值是错误的原因是它的值没有初始化。在 Rust 编程语言中,这是非法的。因此编译时错误出现。
|
||
|
||
> 🖥️ 这个错误可以很容易地通过交换第 7 和第 8 行的代码来解决。试一下,看看错误是否消失了。
|
||
|
||
### 示例程序:交换数字
|
||
|
||
现在你已经熟悉了常见的变量相关问题,让我们来看一个交换两个变量值的程序。
|
||
|
||
```
|
||
fn main() {
|
||
let mut a = 7186932;
|
||
let mut b = 1276561;
|
||
|
||
println!("a: {a}, b: {b}");
|
||
|
||
// 交换变量值
|
||
let temp = a;
|
||
a = b;
|
||
b = temp;
|
||
|
||
println!("a: {}, b: {}", a, b);
|
||
}
|
||
```
|
||
|
||
我在这里声明了两个变量 `a` 和 `b`。这两个变量都是可变的,因为我希望在后面改变它们的值。我赋予了一些随机值。最初,我打印了这些变量的值。
|
||
|
||
然后,在第 8 行,我创建了一个名为 `temp` 的不可变变量,并将存储在 `a` 中的值赋给它。之所以这个变量是不可变的,是因为 `temp` 的值不会改变。
|
||
|
||
要交换值,我将变量 `b` 的值赋给变量 `a`,在下一行,我将 `temp` 的值(它包含 `a` 的值)赋给变量 `b`。现在值已经交换了,我打印了变量 `a` 和 `b` 的值。
|
||
|
||
在编译并执行上面的代码后,我得到了以下输出:
|
||
|
||
```
|
||
a: 7186932, b: 1276561
|
||
a: 1276561, b: 7186932
|
||
```
|
||
|
||
正如你所见,值已经交换了。完美。
|
||
|
||
### 使用未使用的变量
|
||
|
||
当你声明了一些变量,打算在后面使用它们,但是还没有使用它们,然后编译你的 Rust 代码来检查一些东西时,Rust 编译器会警告你。
|
||
|
||
原因是显而易见的。不会被使用的变量占用了不必要的初始化时间(CPU 周期)和内存空间。如果不会被使用,为什么要在程序写上它呢?尽管编译器确实会优化这一点。但是它仍然是一个问题,因为它会以多余的代码的形式影响可读性。
|
||
|
||
但是,有的时候,你可能会面对这样的情况:创建一个变量与否不在你的控制之下。比如说,当一个函数返回多个值,而你只需要其中的一些值时。在这种情况下,你不能要求库维护者根据你的需要调整他们的函数。
|
||
|
||
所以,在这种情况下,你可以写一个以下划线开头的变量,Rust 编译器将不再显示这样的警告。如果你真的不需要使用存储在该未使用变量中的值,你可以简单地将其命名为 `_`(下划线),Rust 编译器也会忽略它!
|
||
|
||
接下来的程序不仅不会生成任何输出,而且也不会生成任何警告和/或错误消息:
|
||
|
||
```
|
||
fn main() {
|
||
let _unnecessary_var = 0; // 没有警告
|
||
let _ = 0.0; // 完全忽略
|
||
}
|
||
```
|
||
|
||
### 算术运算
|
||
|
||
数学就是数学,Rust 并没有在这方面创新。你可以使用在其他编程语言(如 C、C++ 和/或 Java)中使用过的所有算术运算符。
|
||
|
||
包含可以在 Rust 编程语言中使用的所有运算符和它们的含义的完整列表可以在 [这里][3] 找到。
|
||
|
||
#### 示例程序:一个生锈的温度计
|
||
|
||
(LCTT 译注:这里的温度计“生锈”了是因为它是使用 Rust(生锈)编写的,原作者在这里玩了一个双关。)
|
||
|
||
接下来是一个典型的程序,它将华氏度转换为摄氏度,反之亦然。
|
||
|
||
```
|
||
fn main() {
|
||
let boiling_water_f: f64 = 212.0;
|
||
let frozen_water_c: f64 = 0.0;
|
||
|
||
let boiling_water_c = (boiling_water_f - 32.0) * (5.0 / 9.0);
|
||
let frozen_water_f = (frozen_water_c * (9.0 / 5.0)) + 32.0;
|
||
|
||
println!(
|
||
"Water starts boiling at {}°C (or {}°F).",
|
||
boiling_water_c, boiling_water_f
|
||
);
|
||
println!(
|
||
"Water starts freezing at {}°C (or {}°F).",
|
||
frozen_water_c, frozen_water_f
|
||
);
|
||
}
|
||
```
|
||
|
||
没什么大不了的……华氏温度转换为摄氏温度,反之亦然。
|
||
|
||
正如你在这里看到的,由于 Rust 不允许自动类型转换,我不得不在整数 32、9 和 5 后放一个小数点。除此之外,这与你在 C、C++ 和/或 Java 中所做的类似。
|
||
|
||
作为练习,尝试编写一个程序,找出给定数中有多少位数字。
|
||
|
||
### 常量
|
||
|
||
如果你有一些编程知识,你可能知道这意味着什么。常量是一种特殊类型的变量,它的值**永远不会改变**。_它保持不变_。
|
||
|
||
在 Rust 编程语言中,使用以下语法声明常量:
|
||
|
||
```
|
||
const CONSTANT_NAME: data_type = value;
|
||
```
|
||
|
||
如你所见,声明常量的语法与我们在 Rust 中看到的变量声明非常相似。但是有两个不同之处:
|
||
|
||
- 常量的名字需要像 `SCREAMING_SNAKE_CASE` 这样。所有的大写字母和单词之间用下划线分隔。
|
||
- 常量的数据类型**必须**被显性定义。
|
||
|
||
#### 变量与常量的对比
|
||
|
||
你可能在想,既然变量默认是不可变的,为什么语言还要包含常量呢?
|
||
|
||
接下来这个表格应该可以帮助你消除疑虑。(如果你好奇并且想更好地理解这些区别,你可以看看[我的博客][4],它详细地展示了这些区别。)
|
||
|
||
![一个展示 Rust 编程语言中变量和常量之间区别的表格][5]
|
||
|
||
#### 使用常量的示例程序:计算圆的面积
|
||
|
||
这是一个很直接的关于 Rust 中常量的简单程序。它计算圆的面积和周长。
|
||
|
||
```
|
||
fn main() {
|
||
const PI: f64 = 3.14;
|
||
let radius: f64 = 50.0;
|
||
|
||
let circle_area = PI * (radius * radius);
|
||
let circle_perimeter = 2.0 * PI * radius;
|
||
|
||
println!("有一个周长为 {radius} 厘米的圆");
|
||
println!("它的面积是 {} 平方厘米", circle_area);
|
||
println!(
|
||
"以及它的周长是 {} 厘米",
|
||
circle_perimeter
|
||
);
|
||
}
|
||
```
|
||
|
||
如果运行代码,将产生以下输出:
|
||
|
||
```
|
||
有一个周长为 50 厘米的圆
|
||
它的面积是 7850 平方厘米
|
||
以及它的周长是 314 厘米
|
||
```
|
||
|
||
### Rust 中的变量遮蔽
|
||
|
||
如果你是一个 C++ 程序员,你可能已经知道我在说什么了。当程序员**声明**一个与已经声明的变量同名的新变量时,这就是变量遮蔽。
|
||
|
||
与 C++ 不同,Rust 允许你在同一作用域中执行变量遮蔽!
|
||
|
||
> 💡 当程序员遮蔽一个已经存在的变量时,新变量会被分配一个新的内存地址,但是使用与现有变量相同的名称引用。
|
||
|
||
来看看它在 Rust 中是如何工作的。
|
||
|
||
```
|
||
fn main() {
|
||
let a = 108;
|
||
println!("a 的地址: {:p}, a 的值 {a}", &a);
|
||
let a = 56;
|
||
println!("a 的地址: {:p}, a 的值: {a} // 遮蔽后", &a);
|
||
|
||
let mut b = 82;
|
||
println!("\nb 的地址: {:p}, b 的值: {b}", &b);
|
||
let mut b = 120;
|
||
println!("b的地址: {:p}, b的值: {b} // 遮蔽后", &b);
|
||
|
||
let mut c = 18;
|
||
println!("\nc 的地址: {:p}, c的值: {c}", &c);
|
||
c = 29;
|
||
println!("c 的地址: {:p}, c的值: {c} // 遮蔽后", &c);
|
||
}
|
||
```
|
||
|
||
`println` 语句中花括号内的 `:p` 与 C 中的 `%p` 类似。它指定值的格式为内存地址(指针)。
|
||
|
||
我在这里使用了 3 个变量。变量 `a` 是不可变的,并且在第 4 行被遮蔽。变量 `b` 是可变的,并且在第 9 行也被遮蔽。变量 `c` 是可变的,但是在第 14 行,只有它的值被改变了。它没有被遮蔽。
|
||
|
||
现在,让我们看看输出。
|
||
|
||
```
|
||
a 的地址: 0x7ffe954bf614, a 的值 108
|
||
a 的地址: 0x7ffe954bf674, a 的值: 56 // 遮蔽后
|
||
|
||
b 的地址: 0x7ffe954bf6d4, b 的值: 82
|
||
b 的地址: 0x7ffe954bf734, b 的值: 120 // 遮蔽后
|
||
|
||
c 的地址: 0x7ffe954bf734, c 的值: 18
|
||
c 的地址: 0x7ffe954bf734, c 的值: 29 // 遮蔽后
|
||
```
|
||
|
||
来看看输出,你会发现不仅所有三个变量的值都改变了,而且被遮蔽的变量的地址也不同(检查十六进制的最后几个字符)。
|
||
|
||
变量 `a` 和 `b` 的内存地址改变了。这意味着变量的可变性或不可变性并不是遮蔽变量的限制。
|
||
|
||
### 总结
|
||
|
||
本文介绍了 Rust 编程语言中的变量和常量。还介绍了算术运算。
|
||
|
||
做个总结:
|
||
|
||
- Rust 中的变量默认是不可变的,但是可以引入可变性。
|
||
- 程序员需要显式地指定变量的可变性。
|
||
- 常量总是不可变的,无论如何都需要类型注释。
|
||
- 变量遮蔽是指使用与现有变量相同的名称声明一个 _新_ 变量。
|
||
|
||
很好!我相信和 Rust 一起的进展不错。在下一章中,我将讨论 Rust 中的数据类型。敬请关注。
|
||
|
||
与此同时,如果你有任何问题,请告诉我。
|
||
|
||
*(题图:MJ/7c5366b8-f926-487e-9153-0a877145ca5)*
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
via: https://itsfoss.com/rust-variables/
|
||
|
||
作者:[Pratham Patel][a]
|
||
选题:[lkxed][b]
|
||
译者:[Cubik](https://github.com/Cubik65536)
|
||
校对:[wxy](https://github.com/wxy)
|
||
|
||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||
|
||
[a]: https://itsfoss.com/author/pratham/
|
||
[b]: https://github.com/lkxed/
|
||
[1]: https://itsfoss.com/content/images/2023/03/linux-mega-packt.webp
|
||
[2]: https://linux.cn/article-15709-1.html
|
||
[3]: https://doc.rust-lang.org/book/appendix-02-operators.html?ref=itsfoss.com#operators
|
||
[4]: https://blog.thefossguy.com/posts/immutable-vars-vs-constants-rs.md?ref=itsfoss.com
|
||
[5]: https://itsfoss.com/content/images/2023/02/image.png
|
||
[0]: https://img.linux.net.cn/data/attachment/album/202305/01/144948gp13zdindx50ll0p.png |