Beginning_Rust:使用异构数据结构(第八章)

在本章中,您将学习如何定义和使用其他复合类型:
•元组
•结构
•元组结构
它们可用于对不同类型的对象进行分组。
在本章的最后,您将看到一些代码样式约定。

元组

数组和向量可以包含多个项目,但这些项目必须都是相同的类型。

如果您希望在单个对象中存储多个不同类型的子对象,您可以这样做:

let data = (10000000, 183.19, 'Q');
let copy_of_data = data;
print!("{}, {}, {}",
data.0, copy_of_data.1, data.2);

这将打印:“10000000,183.19,Q”。

“data”变量是一个复合对象,因为它由三个对象组成。 偶数数组是复合对象,但它们被约束为由相同类型的对象组成,而“data”变量由不同类型的对象组成:整数,浮点数和字符。

因此,我们的对象不是数组,而是“元组”。

元组的声明看起来像数组。 唯一的区别是使用圆括号而不是方括号。

元组的每个项目都被命名为“field”。

此外,元组的类型可以明确:

let data: (i32, f64, char) = (10000000, 183.19, 'Q');

该类型具有与值相同的格式,其中每个字段的值由其类型替换。

正如在第二个语句中所示,可以使用整个元组来初始化相同类型的另一个元组。

您可以使用点符号按位置访问元组的字段。 访问“arr”数组的第七项时,必须写“arr [6]”; 要访问“data”元组的第七个字段,必须编写“data.6”。

元组也可以是可变的:

let mut data = (10000000, 183.19, 'Q');
data.0 = -5;
data.2 = 'x';
print!("{}, {}, {}", data.0, data.1, data.2);

这将打印:“ - 5,183.19,x”。

与数组类似,元组也可以包含任意数量的字段,包括零。 假定元组的类型由其字段类型的序列定义,括在括号中,如果没有字段,则仅保留括号,因此其类型为“()”。 并且假定元组的值由其字段值的序列表示,括在括号中,如果没有字段,则仅保留括号,因此其值为“()”。

但是我们已经看到了这种类型和这个价值。 所以现在解释为什么它们被命名为“空元组”。

元组和数组之间的区别在于变量索引无法访问元组:

let array = [12, 13, 14];
let tuple = (12, 13, 14);
let i = 0;
print!("{}", array[i]);
print!("{}", tuple.i);

在此程序中,最后一行是非法的,并且无法使用在运行时确定的索引来获取元组字段的值。

结构

只要元组包含的项目不多,它们就很有用,但是当它们有很多字段时,很容易误认为它们,并且使用它们的代码很难被理解:

let data = (10, 'x', 12, 183.19, 'Q', false, -9);
print!("{}", data.2 + data.6);

很明显这段代码会打印3吗?

此外,任何元组的类型仅由其字段类型的序列定义,如果有许多字段,则此类型太长而无法指定,并且知之甚少:

let data1 = (10, 'x', 12, 183.19, 'Q', false, -9);
let mut data2: (u16, char, i16, f64, bool, char, i16);
data2 = data1;

此代码是非法的。 你能发现错误吗?

另外,如果在元组的开头添加了一个字段,那么这种类型的对象的所有索引必须在源代码中递增。 例如,“data.2”必须变为“data.3”。

因此,有一个特定的语句来声明一个结构的类型,给它一个名字,并标记该结构的所有字段,这非常有用:

struct SomeData {
integer: i32,
fractional: f32,
character: char,
five_bytes: [u8; 5],
}
let data = SomeData {
integer: 10_000_000,
fractional: 183.19,
character: 'Q',
five_bytes: [9, 0, 250, 60, 200],
};
print!("{}, {}, {}, {}",
data.five_bytes[3], data.integer,
data.fractional, data.character);

这将打印60,10000000,183.19,Q。

第一个语句占用六行:它以“struct”关键字开头,然后继续执行一个块。它的作用是声明“SomeData”类型。这种类型的任何对象都是四个字段的序列。对于每个字段,声明其名称和类型,用冒号分隔。字段声明列表以逗号分隔,带有可选的结尾逗号。让我们将“struct”命名为这种数据类型。

第二个语句也占据六行。它声明变量“data”,并使用刚刚声明的类型的对象初始化它。请注意,初始化语法类似于类型声明语法,其中删除了“struct”关键字,并且每个字段类型都由要分配给此类字段的值替换。让我们将“struct-object”命名为这种类型的对象,它是类型为结构的任何对象。

第三个语句使用所谓的点符号访问刚刚定义的struct-object的字段。此表示法由表示结构对象的表达式组成,后跟一个点,后跟要访问的字段的名称。

此代码类似于以下C语言程序:

#include <stdio.h>
int main() {
struct SomeData {
int integer;
float fractional;
char character;
unsigned char five_bytes[5];
};
struct SomeData data = {
10000000,
183.19,
'Q',
{9, 0, 250, 60, 200},
};

printf("%d, %d, %g, %c",
data.five_bytes[3], data.integer,
data.fractional, data.character);
return 0;
}

让我们看看这个C代码与上面的Rust代码的不同之处。

在C中,字段用分号分隔,在Rust中,它们用逗号分隔。

在Rust中,类型是在字段名称之后写的,就像在Pascal语言中一样。

在C中,您可以通过仅指定一次类型来声明多个相同类型的字段,即以这种方式:“int a,b;”。 相反,在Rust中,必须为每个字段指定一次类型:“a:i32,b:i32,”。

在C中,“数据”的初始化只需通过列出值来完成,类似于Rust元组。 相反,在Rust中,对于每个字段,您还必须指定字段的名称。

在C和Rust中,都使用了点符号。
如果将变量声明为可变,则还可以使用相同的点符号更改其字段的值:

struct SomeData {
integer: i32,
fractional: f32,
}
let mut data = SomeData {
integer: 10,
fractional: 183.19,
};
data.fractional = 8.2;
print!("{}, {}", data.fractional, data.integer);

这将打印:“8.2,10”。
与元组一样,结构也可能是空的,因此您可以声明一个不包含字段的元组。

元组结构

我们已经看到有两种结构包含不同类型的对象:

•元组,其类型没有名称,以前不会被声明,并且其字段没有名称;

•结构,其类型具有名称,必须事先声明,并且其字段具有名称。

因此,这两种结构之间存在一些差异。 然而,有时需要中途的东西:一种结构,其类型具有名称,必须事先声明,如结构,但其字段没有名称,如元组。 因为它们是元组和结构之间的混合体,所以它们被命名为“元组结构”:

struct SomeData (
i32,
f32,
char,
[u8; 5],
);
let data = SomeData (
10_000_000,
183.19,
'Q',
[9, 0, 250, 60, 200],
);
print!("{}, {}, {}, {}",
data.2, data.0, data.1, data.3[2]);

这将打印:“Q,10000000,183.19,250”。

正如示例中所示,元组结构在实例化之前定义,通过使用关键字“struct”(如结构),但将其字段括在括号中,而不指定字段的名称,如元组。 初始化以类型的名称开头,就像结构一样,但是像元组一样继续。

必要时,它的字段可以像元组一样访问,因为它们没有名称。 与元组和结构不同,不允许使用空元组结构。 实际上并不经常使用元组结构。

词汇约定

现在我们已经看到了大量不同的Rust构造(但还不是全部!),现在是思考几乎每个Rust程序员采用的一些词汇约定的好时机,因此强烈建议大家使用它们。 这些约定是如此根深蒂固,即使编译器被违反也会发出警告。 这是一个显示它们的程序:

const MAXIMUM_POWER: u16 = 600;
enum VehicleKind {
Motorcycle,
Car,
Truck,
}
struct VehicleData {
kind: VehicleKind,
registration_year: u16,
registration_month: u8,
power: u16,
}
let vehicle = VehicleData {
kind: VehicleKind::Car,
registration_year: 2003,
registration_month: 11,
power: 120,
};
if vehicle.power > MAXIMUM_POWER {
println!("Too powerful");
}

此示例中显示的约定是:

•常量名称(例如:MAXIMUM_POWER)仅包含大写字符,单词由下划线分隔。

•由应用程序代码或标准库(例如:VehicleKind和VehicleData)定义的类型名称和枚举变体名称(例如:Car)由粘在一起的单词组成,其中每个单词都有一个大写的首字母,后跟小写字母。

•任何其他名称(例如,像let这样的关键字,像u8这样的基本类型,以及像registration_year这样的feld标识符)只使用小写字母,单词用下划线分隔。

猜你喜欢

转载自blog.csdn.net/m0_37696990/article/details/82795863