You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/src/ch03-02-data-types.md

297 lines
14 KiB

8 years ago
## 数据类型
6 years ago
> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/master/src/ch03-02-data-types.md)
8 years ago
> <br>
5 years ago
> commit 6598d3abac05ed1d0c45db92466ea49346d05e40
8 years ago
6 years ago
在 Rust 中,每一个值都属于某一个 **数据类型***data type*),这告诉 Rust 它被指定为何种数据以便明确数据处理方式。我们将看到两类数据类型子集标量scalar和复合compound
8 years ago
5 years ago
记住Rust 是 **静态类型***statically typed*)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,比如第二章的 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 使用 `parse``String` 转换为数字时,必须增加类型注解,像这样:
8 years ago
```rust
8 years ago
let guess: u32 = "42".parse().expect("Not a number!");
8 years ago
```
这里如果不添加类型注解Rust 会显示如下错误,这说明编译器需要我们提供更多信息,来了解我们想要的类型:
8 years ago
7 years ago
```text
7 years ago
error[E0282]: type annotations needed
--> src/main.rs:2:9
8 years ago
|
2 | let guess = "42".parse().expect("Not a number!");
7 years ago
| ^^^^^
| |
| cannot infer type for `_`
| consider giving `guess` a type
8 years ago
```
你会看到其它数据类型的各种类型注解。
8 years ago
### 标量类型
**标量***scalar*类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过它们。让我们深入了解它们在 Rust 中是如何工作的。
8 years ago
#### 整型
**整数** 是一个没有小数部分的数字。我们在第二章使用过 `u32` 整数类型。该类型声明表明,它关联的值应该是一个占据 32 比特位的无符号整数(有符号整数类型以 `i` 开头而不是 `u`)。表格 3-1 展示了 Rust 内建的整数类型。在有符号列和无符号列中的每一个变体(例如,`i16`)都可以用来声明整数值的类型。
8 years ago
7 years ago
<span class="caption">表格 3-1: Rust 中的整型</span>
8 years ago
| 长度 | 有符号 | 无符号 |
5 years ago
|---------|---------|----------|
| 8-bit | `i8` | `u8` |
| 16-bit | `i16` | `u16` |
| 32-bit | `i32` | `u32` |
| 64-bit | `i64` | `u64` |
| 128-bit | `i128` | `u128` |
| arch | `isize` | `usize` |
8 years ago
5 years ago
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,数字是否需要有一个符号(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[补码形式twos complement representation](https://en.wikipedia.org/wiki/Two%27s_complement) 存储。
8 years ago
每一个有符号的变体可以储存包含从 -(2<sup>n - 1</sup>) 到 2<sup>n - 1</sup> - 1 在内的数字,这里 *n* 是变体使用的位数。所以 `i8` 可以储存从 -(2<sup>7</sup>) 到 2<sup>7</sup> - 1 在内的数字,也就是从 -128 到 127。无符号的变体可以储存从 0 到 2<sup>n</sup> - 1 的数字,所以 `u8` 可以储存从 0 到 2<sup>8</sup> - 1 的数字,也就是从 0 到 255。
8 years ago
另外,`isize` 和 `usize` 类型依赖运行程序的计算机架构64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。
8 years ago
可以使用表格 3-2 中的任何一种形式编写数字字面值。注意除 byte 以外的所有数字字面值允许使用类型后缀,例如 `57u8`,同时也允许使用 `_` 做为分隔符以方便读数,例如`1_000`。
8 years ago
<span class="caption">表格 3-2: Rust 中的整型字面值</span>
8 years ago
| 数字字面值 | 例子 |
8 years ago
|------------------|---------------|
| Decimal (十进制) | `98_222` |
| Hex (十六进制) | `0xff` |
| Octal (八进制) | `0o77` |
| Binary (二进制) | `0b1111_0000` |
| Byte (单字节字符)(仅限于`u8`) | `b'A'` |
8 years ago
7 years ago
那么该使用哪种类型的数字呢如果拿不定主意Rust 的默认类型通常就很好,数字类型默认是 `i32`:它通常是最快的,甚至在 64 位系统上也是。`isize` 或 `usize` 主要作为某些集合的索引。
8 years ago
5 years ago
> ##### 整型溢出
>
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256` 时会发生什么呢?这被称为 “整型溢出”“integer overflow” ),关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时Rust 检查这类问题并使程序 *panic*,这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
>
> 在 release 构建中Rust 不检测溢出,相反会进行一种被称为二进制补码包装(*twos complement wrapping*)的操作。简而言之,`256` 变成 `0``257` 变成 `1`,依此类推。依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,[`Wrapping`][wrapping]。
6 years ago
8 years ago
#### 浮点型
Rust 也有两个原生的 **浮点数***floating-point numbers*类型它们是带小数点的数字。Rust 的浮点数类型是 `f32``f64`,分别占 32 位和 64 位。默认类型是 `f64`,因为在现代 CPU 中,它与 `f32` 速度几乎一样,不过精度更高。
8 years ago
这是一个展示浮点数的实例:
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
```
7 years ago
浮点数采用 IEEE-754 标准表示。`f32` 是单精度浮点数,`f64` 是双精度浮点数。
8 years ago
#### 数值运算
8 years ago
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。下面的代码展示了如何在 `let` 语句中使用它们:
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
// 加法
8 years ago
let sum = 5 + 10;
// 减法
8 years ago
let difference = 95.5 - 4.3;
// 乘法
8 years ago
let product = 4 * 30;
// 除法
8 years ago
let quotient = 56.7 / 32.2;
// 取余
8 years ago
let remainder = 43 % 5;
}
```
这些语句中的每个表达式使用了一个数学运算符并计算出了一个值,然后绑定给一个变量。附录 B 包含 Rust 提供的所有运算符的列表。
8 years ago
#### 布尔型
7 years ago
正如其他大部分编程语言一样Rust 中的布尔类型有两个可能的值:`true` 和 `false`。Rust 中的布尔类型使用 `bool` 表示。例如:
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let t = true;
let f: bool = false; // 显式指定类型注解
8 years ago
}
```
5 years ago
使用布尔值的主要场景是条件表达式,例如 `if` 表达式。在 [“控制流”“Control Flow”][control-flow] 部分将介绍 `if` 表达式在 Rust 中如何工作。
8 years ago
#### 字符类型
目前为止只使用到了数字,不过 Rust 也支持字母。Rust 的 `char` 类型是语言中最原生的字母类型,如下代码展示了如何使用它。(注意 `char` 由单引号指定,不同于字符串使用双引号。)
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let c = 'z';
let z = '';
let heart_eyed_cat = '😻';
8 years ago
}
```
5 years ago
Rust 的 `char` 类型的大小为四个字节(four bytes),并代表了一个 Unicode 标量值Unicode Scalar Value这意味着它可以比 ASCII 表示更多内容。在 Rust 中拼音字母Accented letters中文、日文、韩文等字符emoji绘文字以及零长度的空白字符都是有效的 `char` 值。Unicode 标量值包含从 `U+0000``U+D7FF``U+E000``U+10FFFF` 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 `char` 并不符合。第八章的 [“使用字符串存储 UTF-8 编码的文本”][strings] 中将详细讨论这个主题。
8 years ago
### 复合类型
**复合类型***Compound types*可以将多个值组合成一个类型。Rust 有两个原生的复合类型元组tuple和数组array
8 years ago
#### 元组类型
8 years ago
5 years ago
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
8 years ago
我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
```
`tup` 变量绑定到整个元组上因为元组是一个单独的复合元素。为了从元组中获取单个值可以使用模式匹配pattern matching来解构destructure元组值像这样
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
```
程序首先创建了一个元组并绑定到 `tup` 变量上。接着使用了 `let` 和一个模式将 `tup` 分成了三个不同的变量,`x`、`y` 和 `z`。这叫做 **解构***destructuring*),因为它将一个元组拆成了三个部分。最后,程序打印出了 `y` 的值,也就是 `6.4`
8 years ago
除了使用模式匹配解构外,也可以使用点号(`.`)后跟值的索引来直接访问它们。例如:
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
```
这个程序创建了一个元组,`x`,并接着使用索引为每个元素创建新变量。跟大多数编程语言一样,元组的第一个索引值是 0。
#### 数组类型
8 years ago
另一个包含多个值的方式是 **数组***array*。与元组不同数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。
8 years ago
Rust 中,数组中的值位于中括号内的逗号分隔的列表中:
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let a = [1, 2, 3, 4, 5];
}
```
当你想要在栈stack而不是在堆heap上为数据分配空间第四章将讨论栈与堆的更多内容或者是想要确保总是有固定数量的元素时数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。第八章会详细讨论 vector。
8 years ago
一个你可能想要使用数组而不是 vector 的例子是,当程序需要知道一年中月份的名字时。程序不大可能会去增加或减少月份。这时你可以使用数组,因为我们知道它总是包含 12 个元素:
8 years ago
```rust
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
```
5 years ago
可以像这样编写数组的类型:在方括号中包含每个元素的类型,后跟分号,再后跟数组元素的数量。
6 years ago
```rust
let a: [i32; 5] = [1, 2, 3, 4, 5];
```
5 years ago
这里,`i32` 是每个元素的类型。分号之后,数字 `5` 表明该数组包含五个元素。
这样编写数组的类型类似于另一个初始化数组的语法:如果你希望创建一个每个元素都相同的数组,可以在中括号内指定其初始值,后跟分号,再后跟数组的长度,如下所示:
```rust
let a = [3; 5];
```
6 years ago
8 years ago
##### 访问数组元素
数组是一整块分配在栈上的内存。可以使用索引来访问数组的元素,像这样:
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
```rust
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
```
7 years ago
在这个例子中,叫做 `first` 的变量的值是 `1`,因为它是数组索引 `[0]` 的值。变量 `second` 将会是数组索引 `[1]` 的值 `2`
8 years ago
##### 无效的数组元素访问
如果我们访问数组结尾之后的元素会发生什么呢?比如你将上面的例子改成下面这样,这可以编译不过在运行时会因错误而退出:
8 years ago
7 years ago
<span class="filename">文件名: src/main.rs</span>
8 years ago
5 years ago
```rust,ignore,panics
8 years ago
fn main() {
let a = [1, 2, 3, 4, 5];
7 years ago
let index = 10;
8 years ago
7 years ago
let element = a[index];
8 years ago
println!("The value of element is: {}", element);
}
```
7 years ago
使用 `cargo run` 运行代码后会产生如下结果:
8 years ago
7 years ago
```text
8 years ago
$ cargo run
Compiling arrays v0.1.0 (file:///projects/arrays)
7 years ago
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
8 years ago
Running `target/debug/arrays`
5 years ago
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is
10', src/main.rs:5:19
8 years ago
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
编译并没有产生任何错误,不过程序会出现一个 **运行时***runtime*错误并且不会成功退出。当尝试用索引访问一个元素时Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度Rust 会 *panic*,这是 Rust 术语,它用于程序因为错误而退出的情况。
8 years ago
这是第一个在实战中遇到的 Rust 安全原则的例子。在很多底层语言中并没有进行这类检查这样当提供了一个不正确的索引时就会访问无效的内存。通过立即退出而不是允许内存访问并继续执行Rust 让你避开此类错误。第九章会讨论更多 Rust 的错误处理。
5 years ago
[comparing-the-guess-to-the-secret-number]:
ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number
[control-flow]: ch03-05-control-flow.html#control-flow
[strings]: ch08-02-strings.html#storing-utf-8-encoded-text-with-strings
[unrecoverable-errors-with-panic]: ch09-01-unrecoverable-errors-with-panic.html
[wrapping]: ../std/num/struct.Wrapping.html