mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2025-02-22 01:10:07 +08:00
Refining Ch03.
This commit is contained in:
parent
4965f88387
commit
1e2d49aeb6
@ -1,7 +1,4 @@
|
||||
fn main() {
|
||||
let x = 5;
|
||||
println! ("x 的值为:{x}");
|
||||
|
||||
x = 6;
|
||||
println! ("x 的值为:{x}");
|
||||
let mut spaces = " ";
|
||||
spaces = spaces.len();
|
||||
}
|
||||
|
@ -46,14 +46,16 @@ error: could not compile `variables` due to previous error
|
||||
|
||||
咱们之所以收到了错误消息 `cannot assing twice to immutable variable 'x'`,是因为咱们试图将第二个值,赋值给那个不可变的变量 `x`。
|
||||
|
||||
在尝试修改某个被指定为不可变的值时,由于这种情况会导致程序错误,因此这个时候收到编译时错误尤为重要。代码一部分的运作,是建立在值将绝不会改变这种假定上,而代码另一部分却修改了那个值,那么就有可能代码的第一部分未有完成他预计要完成的工作了。此类程序错误的原因,就难于追踪到真相,尤其是在代码第二部分仅 *有的时候* 才对那个值进行修改时。Rust 编译器保证了在表明某个值不会变化时,那么那个值就真的不会变化,如此就不必亲自去紧盯着那个变量了。代码也由此而更易于推演。
|
||||
重要的是,当我们试图更改某个被指定为不可变的值时,会出现编译时错误,因为这种情况可能会导致错误。如果我们代码的一部分假设某个值永远不会改变,而代码的另一部分却改变了该值,那么代码的前一部分就有可能无法完成其设计目标。这种错误的原因可能很难事后追踪,尤其是当第二部分代码只是 *偶尔* 改变值时。Rust 编译器保证,当咱们声明了某个值不会改变时,他确实不会改变,因此咱们不必自己跟踪它。这样,咱们的代码就更容易推理了。
|
||||
|
||||
然而可变则可能会非常有用,并能令到代码更便于编写。变量仅在默认情况下是不可变的;就如同在第 2 章中所做的那样,可通过在变量名字前添加 `mut` 关键字,让变量成为可变。`mut` 的添加,也向将来代码的读者传达了某种意图,表示代码的其他部分,会对这个变量的值进行修改。
|
||||
但可变会非常有用,可以让代码编写更方便。虽然变量在默认情况下是不可变的,但也可以像 [第 2 章](../Ch02_Programming_a_Guessing_Game.md#使用变量存储值) 中咱们曾做的那样,在变量名前添加 `mut`,使其成为可变变量。添加 `mut` 还可以向代码的未来读者,传达某种意图,即代码的其他部分,将改变该变量的值。
|
||||
|
||||
比如,来将 `src/main.rs` 修改为下面这样:
|
||||
比如,咱们来将 `src/main.rs` 修改为下面这样:
|
||||
|
||||
文件名:`src/main.rs`
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
println! ("x 的值为:{}", x);
|
||||
|
||||
@ -62,44 +64,86 @@ error: could not compile `variables` due to previous error
|
||||
}
|
||||
```
|
||||
|
||||
在此时运行这个程序时,就会得到这样的输出:
|
||||
|
||||
当我们现在运行这个程序时,会得到这样的结果:
|
||||
|
||||
|
||||
```rust
|
||||
$ cargo run 101 ✘
|
||||
Compiling variables v0.1.0 (/home/peng/rust-lang/projects/variables)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.46s
|
||||
Running `target/debug/variables`
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.26s
|
||||
Running `target\debug\variables.exe`
|
||||
x 的值为:5
|
||||
x 的值为:6
|
||||
```
|
||||
|
||||
在使用了 `mut` 关键字时,就被允许将绑定到 `x` 的值从 `5` 修改到 `6` 了。除了防止程序错误之外,还要考虑多种权衡。比如,在使用着大型数据结构时,就地修改其的一个实例,就会比拷贝并返回新近分配的实例要快一些(for example, in cases where you're using large data structures, mutating an instance in place may be faster than copying and returning newly allocated instances)。而对于较小的数据结构,创建其新实例,并以更具函数式编程风格来编写代码,则可能更易于理解,那么由此带来的性能下降,相对所取得的思路清晰,也会是值得的。
|
||||
在使用了 `mut` 时,我们就可以将绑定到 `x` 的值,从 `5` 改为 `6`。最终,是否使用可变,取决于咱们自己,取决于咱们认为在特定情况下,什么是最清晰的。
|
||||
|
||||
|
||||
## 常量
|
||||
|
||||
与不可变变量类似, *常量(constants)* 是一些绑定到名字且不允许修改的值,但常量与变量之间,有些差异。
|
||||
**Constants**
|
||||
|
||||
首先,是不允许在常量上使用 `mut` 关键字的。常量不光是默认不可变的 -- 他们一直都是不可变的。常量的声明用的是 `const` 关键字,而不是 `let` 关键字,同时值的类型 *必须* 被注解(be annotated)。在下一小节,[数据类型](#数据类型),就会讲到类型和类型注解了,因此现在不要关心细节。只要明白必须始终对类型进行注解。
|
||||
|
||||
可在任何作用域,包括全局作用域中声明常量。而当在全局作用域中声明常量时,则会让那些代码中许多部分都需要知悉的值的常量,变得有用起来。
|
||||
与不可变的变量一样,*常量,constants* 也是绑定到名字,且不允许更改的值,但常量和变量之间有些区别。
|
||||
|
||||
常量与不可变变量的最后一个区别,就是常量只能被设置到一个常量表达式,而不能被设置为只能在运行时计算出结果的值。
|
||||
首先,不允许将 `mut` 与常量一起使用。常量不仅默认是不可变的,而且始终不可变。咱们要使用 `const` 关键字,而不是 `let` 关键字,声明常量,而且 *必须* 注解值的类型。我们将在下一小节 [“数据类型”](data_types.md) 中,介绍类型和类型注解,所以现在不用担心这些细节。只需知道咱们必须始终注解类型即可。
|
||||
|
||||
下面是一个常量声明的示例:
|
||||
常量可以在任何作用域(包括全局作用域)中声明,因此对于代码的多个部分都需要知道的那些值来说,常量非常有用。
|
||||
|
||||
常量与不可变变量的最后一个区别,是常量只能设置为一个常量表达式,而不是只能在运行时计算出的值的结果,constants may be set only to a constant expression, not the result of a value that could only be computed at runtime。
|
||||
|
||||
> **译注**:这句话的意思,常量的初始化赋值表达式不能包含变量。如下面的代码:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let a = 3;
|
||||
|
||||
const THREE_HOURS_IN_SECONDS: u32 = a * 60 * 60;
|
||||
|
||||
println! ("三个小时的秒数:{THREE_HOURS_IN_SECONDS}");
|
||||
}
|
||||
```
|
||||
|
||||
> 将报错:
|
||||
|
||||
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
|
||||
error[E0435]: attempt to use a non-constant value in a constant
|
||||
--> src\main.rs:4:41
|
||||
|
|
||||
4 | const THREE_HOURS_IN_SECONDS: u32 = a * 60 * 60;
|
||||
| ---------------------------- ^ non-constant value
|
||||
| |
|
||||
| help: consider using `let` instead of `const`: `let THREE_HOURS_IN_SECONDS`
|
||||
|
||||
For more information about this error, try `rustc --explain E0435`.
|
||||
error: could not compile `variables` (bin "variables") due to previous error
|
||||
```
|
||||
|
||||
|
||||
下面是个常量声明的示例:
|
||||
|
||||
```rust
|
||||
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
|
||||
```
|
||||
|
||||
该常量的名字为 `THREE_HOURS_IN_SECONDS`,而他的值就被设置为了 `60` (即一分钟的秒数)乘以 `60` (即一小时的分钟数)乘以 `3` (此程序中要计数的小时数)。Rust 关于常量命名的约定,即为全部使用大写,在词汇之间用下划线隔开。编译器在运行时,能够执行一套受限的运算,这样就可以选择将常量值,以这种更易于理解和验证的方式写出来,而不是将该常量设置为值 `10,800`。请参阅 [Rust 参考手册有关常量求值的小节](https://doc.rust-lang.org/reference/const_eval.html),了解更多有关在声明常量时可使用那些运算的信息。
|
||||
这个常量的名字是 `THREE_HOURS_IN_SECONDS`,其值被设置为 60(一分钟的秒数)乘以 60(一小时的分钟数)再乘以 3(本程序中要计算的小时数)的结果。Rust 的常量命名约定,是使用全大写字母,单词之间使用下划线。编译器可以在编译时,计算一个有限的运算集,这让我们可以选择,以一种更容易理解和验证的方式写出这个值,而不是将这个常量设置为值 10,800。有关在声明常量时,可以使用哪些运算的更多信息,请参阅 [Rust 参考手册中,关于常量求值的小节](https://doc.rust-lang.org/reference/const_eval.html)。
|
||||
|
||||
常量在程序运行的全部时间、在其被声明的作用域内部,都是有效的。常量的这个属性,令到常量对于应用域内的那些、程序多个部分都需要知悉的值来说,变得有用起来,比如某个游戏全部玩家所允许赚到的最大点数,或光速常量。
|
||||
常量在程序运行的整个过程中,在其声明的范围内都有效。这一属性使得常量对于程序域中,多个部分可能需要了解的值,例如游戏中允许任何玩家获得的最大点数或光速,会非常有用。
|
||||
|
||||
对那些整个程序都要用到的、作为常量的硬编码值进行取名,对于向代码将来的维护者们传达那些值的意义,是相当有用的。对于未来需要更新硬编码值来说,对常量命名就让那些需要修改的代码只有一处要改,而对此带来帮助。
|
||||
将整个程序中要用到的一些值,命名为常量,有助于向未来的代码维护者,传达该值的含义。此外,如果将来需要更新硬编码值,只需更改咱们代码中的一处即可。
|
||||
|
||||
## 遮蔽(shadowing)
|
||||
|
||||
如同在第 2 章中的猜数游戏里看到的那样,可声明一个与先前变量同名的新变量。Rust 公民们表示,那第一个变量是被第二个给 *遮蔽* 掉了,这就意味着在用到这个变量是,程序所看到的,会是第二个变量的值。通过使用一样的变量名字,以及重复使用 `let` 关键字,就可对某个变量进行遮蔽,如下所示:
|
||||
## 遮蔽
|
||||
|
||||
**Shadowing**
|
||||
|
||||
|
||||
如同咱们曾在 [第 2 章](../Ch02_Programming_a_Guessing_Game.md#将猜数与秘数相比较) 的猜数游戏教程中,所看到的,咱们可以声明出一个,与先前的某个变量同名的新变量。Rustaceans 会说,第一个变量是被第二个 *遮蔽了,shadowd*,这意味着当你使用这个变量的名字时,编译器将看到的,是第二个变量。实际上,第二个变量覆盖了第一个,将变量名的任何使用,都带到自己身上,直到他自己被遮蔽,或作用域结束。像下面这样,通过使用相同的变量名,和重复使用 `let` 关键字,咱们便可以遮蔽某个变量:
|
||||
|
||||
|
||||
文件名:`src/main.rs`
|
||||
|
||||
@ -118,6 +162,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```console
|
||||
内部作用域中 x 的值为:12
|
||||
x 的值为:6
|
||||
@ -125,49 +170,60 @@ x 的值为:6
|
||||
|
||||
> 注意:遮蔽特性的使用,不需要 `mut` 关键字。
|
||||
|
||||
这个程序首先将 `x` 绑定到值 `5`。随后通过重复 `let x =`,取原来的值并加上 `1`,而对 `x` 做了遮蔽操作,因此 `x` 的值此时就为 `6` 了。之后,在一个内部作用域内,第三个 `let` 语句也对 `x` 进行了遮蔽,将先前的值乘以 `2`,就给到 `x` 一个值 `12`。在那个内部作用域完毕时,那个内部遮蔽就结束了,同时 `x` 回到仍为 `6`。在运行这个程序时,他将输出下面的内容:
|
||||
这个程序首先将 `x`,绑定到 5 的值。然后通过重复 `let x =`,创建出一个新变量 `x`,将原来的值加上 `1`,这样 `x` 的值就是 `6`。然后,在用大括号创建的一个内层作用域中,第三个 `let` 语句也对 `x` 进行了遮蔽处理,并创建了一个新变量,将先前的值乘以 `2`,使 `x` 的值为 `12`。当这个作用域结束时,内部的遮蔽结束,`x` 返回到 `6`。当我们运行这个程序时,他将输出如下内容:
|
||||
|
||||
|
||||
```console
|
||||
$ cargo run ✔
|
||||
Compiling variables v0.1.0 (/home/peng/rust-lang/projects/variables)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.47s
|
||||
Running `target/debug/variables`
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.03s
|
||||
Running `target\debug\variables.exe`
|
||||
内部作用域中 x 的值为:12
|
||||
x 的值为:6
|
||||
```
|
||||
遮蔽操作不同于将变量构造为 `mut`,因为如果我们不小心在没有使用 `let` 关键字的情况下,将变量重新赋值,咱们就会得到一个编译时报错。而通过使用 `let` 关键字,我们可以对某个值执行一些变换,但在这些变换完成后,该变量将不可变。
|
||||
|
||||
由于在不小心而尝试在不带 `let` 关键字而重新赋值给该变量时,会收到编译时错误,因此遮蔽不同于构造一个`mut` 的变量。通过使用 `let` 关键字,就可以在值上执行少量的转换操作,而在这些转换操作完成后又将该变量置入到不可变。
|
||||
`mut` 与遮蔽的另一个区别是,当我们再次使用 `let` 关键字时,我们实际上是创建了一个新变量,因此我们可以改变该值的类型,而可以重复使用这个同样的名字。例如,假设咱们的程序要求用户,通过输入一些空格字符,来给出他们想要的某些文本之间多少个空格,然后我们打算将这个输入,存储为一个数字:
|
||||
|
||||
`mut` 与遮蔽的另一不同之处,则是由于再次使用`let`关键字时,有效地创建出了一个新变量,因此就可以改变那个值的类型,而仍然重用那同样的变量名字。比如说程序要通过用户输入若干空格字符,来询问用户希望在一些文本之间留多少个空格,而此时又要将用户输入的若干个空格,保存为一个数字:
|
||||
|
||||
```rust
|
||||
let spaces = " ";
|
||||
let spaces = spaces.len();
|
||||
```
|
||||
|
||||
第一个 `spaces` 变量是字符串类型,而第二个 `spaces` 变量则是数字类型。遮蔽因此而免于不得不苦苦思索不同的变量名字,诸如 `spaces_str` 及 `spaces_num`;相反,是可以重新较简单的 `spaces` 名称。然而,若尝试对这个变量使用 `mut` 关键字,就会收到一个编译时错误,如下所示:
|
||||
|
||||
第一个 `spaces` 变量,属于字符串类型,而第二个 `spaces` 变量,则是数字类型。遮蔽就这样,让我们不必使用不同的名称,如 `spaces_str` 与 `spaces_num`;相反,我们可以重复使用这个更简单的 `spaces` 名称。然而,如果我们尝试使用 `mut` 来实现这一点,如下所示,咱们就会收到一个编译时报错:
|
||||
|
||||
|
||||
```rust
|
||||
let mut spaces = " ";
|
||||
spaces = spaces.len();
|
||||
```
|
||||
|
||||
错误是说不允许转变变量类型:
|
||||
|
||||
该报错表明,我们不允许改变变量的类型:
|
||||
|
||||
|
||||
```console
|
||||
$ cargo run ✔
|
||||
Compiling variables v0.1.0 (/home/peng/rust-lang/projects/variables)
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.rs:14:14
|
||||
|
|
||||
13 | let mut spaces = " ";
|
||||
| ------ expected due to this value
|
||||
14 | spaces = spaces.len();
|
||||
| ^^^^^^^^^^^^ expected `&str`, found `usize`
|
||||
--> src\main.rs:3:14
|
||||
|
|
||||
2 | let mut spaces = " ";
|
||||
| ------ expected due to this value
|
||||
3 | spaces = spaces.len();
|
||||
| ^^^^^^^^^^^^ expected `&str`, found `usize`
|
||||
|
|
||||
help: try removing the method call
|
||||
|
|
||||
3 - spaces = spaces.len();
|
||||
3 + spaces = spaces;
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
error: could not compile `variables` due to previous error
|
||||
error: could not compile `variables` (bin "variables") due to previous error
|
||||
```
|
||||
|
||||
现在已经完成变量运行机制的探讨,接卸来就要看看这些变量可以有的那些其余数据类型了。
|
||||
|
||||
现在我们已经探讨了变量的工作原理,我们来看看,他们可以拥有的更多数据类型。
|
||||
|
Loading…
Reference in New Issue
Block a user