Beginng_Rust(译):枚举案例(第七章)(完+1)

在本章中,您将学习:

•枚举如何帮助定义只能从有限的一组案例中获取值的变量

•枚举如何用于实现歧视联合类型

•如何使用匹配模式匹配构造来处理枚举

•如何使用匹配构造来处理其他数据类型,如整数,字符串和单个字符

•如何使用布尔警卫来概括匹配构造的模式匹配

枚举

而不是编写以下代码:

const EUROPE: u8 = 0;
const ASIA: u8 = 1;
const AFRICA: u8 = 2;
const AMERICA: u8 = 3;
const OCEANIA: u8 = 4;
let continent = ASIA;
if continent == EUROPE { print!("E"); }
else if continent == ASIA { print!("As"); }
else if continent == AFRICA { print!("Af"); }
else if continent == AMERICA { print!("Am"); }
else if continent == OCEANIA { print!("O"); }

最好编写以下等效代码:

enum Continent {
Europe,
Asia,
Africa,
America,
Oceania,
}
let contin = Continent::Asia;
match contin {
Continent::Europe => print!("E"),
Continent::Asia => print!("As"),
Continent::Africa => print!("Af"),
Continent::America => print!("Am"),
Continent::Oceania => print!("O"),
}

“enum”关键字引入了新的Continent类型,在其后面指定。 这种类型称为“枚举”,因为它列出了一组项目,在内部将唯一编号与每个项目相关联。 在该示例中,类型Continent的允许值是Europe, Asia, Africa, America, 和Oceania, ,它们分别在内部由值0u8,1u8,2u8,3u8和4u8表示。

在最简单的情况下,如上所示,这种类型类似于C语言的同名结构。 在定义了枚举类型之后,可以创建这种类型的对象,名为“枚举”或“枚举”用于缩写。 在该示例中,已定义Continent类型的连续枚举变量。

枚举的值只能是其类型定义中列出的项目之一。 这些物品被命名为“变体”。

扫描二维码关注公众号,回复: 3455299 查看本文章

请注意,变量的使用必须通过其类型名称进行限定,例如Continent :: Asia。

以下代码:

enum T {A, B, C, D};
let n: i32 = T::D;
let e: T = 1;

在第二行生成编译错误,在第三行生成另一个编译错误,两种类型不匹配的类型。 第一个错误描述为预期的i32,发现枚举main :: T; 相反,第二个错误被描述为预期的枚举main :: T,找到了整数变量。 因此,枚举不能隐式转换为数字,并且数字不能隐式转换为枚举。

match构造

在第一个示例的最后一部分中,刚创建的枚举由一种新的构造使用,以“match”关键字开头。

match语句是使用枚举的基本Rust工具,类似于C语言中的switch语句,即使它们在很多方面有所不同。

首先,请注意match关键字后面的表达式不必括在括号中。

然后,各种情况,也称为“手臂”,由图案组成,后跟符号“=>”,后跟表达式。 这些武器用逗号分隔。

在枚举类型的声明和“匹配”语句中,在最后一个项之后,可以选择放入另一个逗号。 通常,如果您将每个项目放在不同的行中,则会写入这样的逗号,以便包含项目的每一行都以逗号结尾; 相反,在闭括号之前省略逗号,如:

enum CardinalPoint { North, South, West, East };

我们的匹配语句的行为如下。

首先,评估匹配后的语句,因此获取一个值,在我们的例子中是Continent :: Asia。 然后,按照它们出现的顺序将这样的值与(五个)模式中的每一个进行比较,并且一旦模式匹配,就评估其臂的右侧,并且结束语句。

请注意,每个手臂的右侧必须是单个表达式。 到目前为止,我们总是使用打印! 好像它是一个陈述,但实际上它是一个表达。

实际上,如果在其后添加分号字符,则任何表达式都将成为有效语句:

let a = 7.2;
12;
true;
4 > 7;
5.7 + 5. * a;

这段代码是有效的,当然,它什么都不做。

鉴于print!的调用! 当我们添加“;”时,宏是一个有效的表达式 我们可以将它用作声明。

但是,请注意,有些语句不是有效的表达式。 例如,“let a = 3;” 并且“fn empty(){}”是即使没有分号字符也不是有效表达式的语句。 如果我们写道:

match contin {
Continent::Europe => let a = 7;,
Continent::Asia => let a = 7,
Continent::Africa => fn aaa() {},
Continent::America => print!("Am"),
Continent::Oceania => print!("O"),
}

我们在前三种情况中都会出现错误,如“=>”符号右侧,没有有效的表达式。 如果你想求值手臂右侧的几个表达式怎么办? 还是一个不是表达的语句? 在这种情况下,您可以使用块:

enum Continent {
Europe,
Asia,
Africa,
America,
Oceania,
}
let mut contin = Continent::Asia;

match contin {
Continent::Europe => {
contin = Continent::Asia;
print!("E");
},
Continent::Asia => { let a = 7; },
Continent::Africa => print!("Af"),
Continent::America => print!("Am"),
Continent::Oceania => print!("O"),
}

在这里,连续被定义为可变,然后,如果它的值是 Europe,它将被改为 Asia,并且将打印字母E. 相反,如果其值为亚洲,则会声明,初始化并立即销毁另一个变量。

这样的两个臂有一个块作为它的右侧,并且,因为任何块都是表达式,所以这种语法是有效的。

关系运算符和枚举

使用“==”运算符无法比较枚举。 实际上,以下程序是非法的:

enum CardinalPoint { North, South, West, East };
let direction = CardinalPoint::South;
if direction == CardinalPoint::North { }

编译器为最后一个语句生成消息“binary operation==不能应用于类型main :: CardinalPoint”。 因此,要检查枚举的值,您需要使用匹配语句。

枚举很重要,因为它们在标准库的许多地方使用,也在其他Rust库中使用。 匹配构造很重要,因为它需要使用枚举,即使它经常被封装在其他构造中。 使用枚举,不仅禁止“==”运算符,还禁止其他关系运算符。 因此,以下代码也会生成编译错误:

enum CardinalPoint { North, South, West, East };
if CardinalPoint::South < CardinalPoint::North { }

处理所有案件

如果您尝试编译以下程序

enum CardinalPoint { North, South, West, East };
let direction = CardinalPoint::South;
match direction {
CardinalPoint::North => print!("NORTH"),
CardinalPoint::South => print!("SOUTH"),
}

你得到的错误是“非详尽的模式:WestEast not covered”。 编译器抱怨表达方向的允许值中,只考虑其中两个,并且不考虑表达式的值为West或East的情况。 这发生在Rust要求match语句明确处理每个可能的情况时。

相反,以下程序是有效的,因为它考虑匹配参数的所有可能值:

enum CardinalPoint { North, South, West, East };
let direction = CardinalPoint::South;
match direction {
CardinalPoint::North => print!("NORTH"),
CardinalPoint::South => print!("SOUTH"),
CardinalPoint::East => {},
CardinalPoint::West => {},
}

然而,这里最后两个变种(东方和西方)什么都不做,无论如何列出它们都很烦人。 为避免必须列出所有不执行任何操作的变体,可以使用下划线符号,方法如下:

enum CardinalPoint { North, South, West, East };
let direction = CardinalPoint::South;
match direction {
CardinalPoint::North => print!("NORTH"),
CardinalPoint::South => print!("SOUTH"),
_ => {},
}

下划线符号始终与任何值匹配,因此它避免了编译错误,因为这样就可以处理所有情况。 当然,这样一个“包罗万象”的案例必须是最后一个案例,以避免捕获应该以不同方式处理的案件:

enum CardinalPoint { North, South, West, East };
let direction = CardinalPoint::South;
match direction {
CardinalPoint::North => print!("NORTH"),
_ => {},
CardinalPoint::South => print!("SOUTH"),
}

此程序不会打印任何内容,因为永远不会到达匹配的CardinalPoint :: South案例。

“_”模式对应于C语言的“默认”情况。

使用与数字匹配

除了需要枚举之外,匹配构造也可用于其他数据类型:

match "value" {
"val" => print!("value "),
_ => print!("other "),
}
match 3 {
3 => print!("three "),
4 => print!("four "),
5 => print!("five "),
_ => print!("other "),
}
match '.' {
':' => print!("colon "),
'.' => print!("point "),
_ => print!("other "),
}

这将打印:“其他三点”。

第一个匹配语句有一个字符串作为其参数,因此它期望字符串作为其左侧的左侧。 特别是,没有arm完全匹配,因此采用默认情况。

第二个匹配语句有一个整数作为参数,因此它希望整数作为其左侧的左侧。 特别是,第一臂的图案匹配,因此它被采用。

第三个匹配语句有一个字符作为其参数,因此它希望单个字符作为其左侧的左侧。 特别是,第二臂的图案匹配,因此它被采用。

对于具有非枚举参数的匹配语句,还需要处理所有可能的情况。 但是,除了枚举和布尔人之外,指定所有单个案例是不可行的; 因此,需要使用下划线“catch-all”案例。

数据枚举

Rust枚举并不总是像上面那样简单:

enum Result {
Success(f64),
Failure(u16, char),
Uncertainty,
}
// let outcome = Result::Success(23.67);
let outcome = Result::Failure(1200, 'X');
match outcome {
Result::Success(value) =>
print!("Result: {}", value),
Result::Failure(error_code, module) =>
print!("Error n. {} in module {}",
error_code, module),
Result::Uncertainty => {},
}

这将打印:“Error n. 1200 in module X”。

相反,如果更改注释以重新激活注释掉的行并注释掉下一行,程序将打印:“结果:23.67”。

在此代码中,Result枚举类型的定义具有第一个变量,其数据类型用括号括起来(f64),第二个变量有两种类型(u16和char),第三个变量没有类型(没有括号) 。

这种声明的效果是每个具有此类结果类型的对象(如示例中的变量结果)可以具有以下值:其值可以是Result :: Success,此外它还包含类型为f64的对象;或者它的值是Result :: Failure,另外它还包含一个类型为u16的对象和一个char类型的对象;或者它的值是Result :: Uncertainty,它不包含其他数据。没有其他可能性。

因此,对于C语言,Rust枚举类型将枚举特征与联合特征组合在一起。

与此示例对应的C语言程序是:

#include <stdio.h>
int main() {
enum eResult {
Success,
Failure,
Uncertainty
};
struct sResult {
enum eResult r;
union {
double value;
struct {
unsigned short error_code;
char module;
} s;
} u;
} outcome;

/*
outcome.r = Success;
outcome.u.value = 23.67;
*/
outcome.r = Failure;
outcome.u.s.error_code = 1200;
outcome.u.s.module = 'X';
switch (outcome.r) {
case Success:
printf("Result: %g", outcome.u.value);
break;
case Failure:
printf("Error n. %d in module %c",
outcome.u.s.error_code,
outcome.u.s.module);
break;
case Uncertainty:
break;
}
return 0;
}

回到上面的Rust代码,您可以看到为结果变量赋值,指定了变量的名称(Result :: Success或Result :: Failure),后面跟着一些逗号分隔的值, 括在括号中,如在函数调用中(第一种情况下为(23.67),第二种情况下为(1200,‘X’))。

为此类型的枚举赋值时,必须在括号中指定具有枚举类型请求的类型的参数。 在示例中,在Success情况下,传递浮点数; 在Failure情况下,传递一个整数和一个字符; 在Uncertainty的情况下,没有传递参数。 如果传递其他类型或不同数字的参数,则会出现编译错误。

在“匹配”语句中,第一个臂的模式是Result :: Success(value);并且第二个臂的模式是Result :: Failure(error_code,module)。因此,在每个模式中,有各自的声明中定义的参数。在这里,做不同会导致错误。

在这种模式中,代替声明中存在的类型,放置一个名称,不一定在之前声明。例如,代替f64,已放置一个值。这些名称实际上是变量的声明,这些变量的范围仅限于声明它们的臂。当采用特定的手臂(例如,成功手臂)时,此类手臂中括号中的变量(如果存在)将使用变体中包含的值进行初始化。在该示例中,使用值23.67初始化值变量。当这样的变量将用在手臂的右侧时,它将具有初始化的值。

如果您不需要在“匹配”语句的模式中使用变量的值,为了避免编译器警告,您可以这样做:

enum Result {
Success(f64),
Failure(u16, char),
Uncertainty,
}
let outcome = Result::Success(23.67);
match outcome {
Result::Success(_) => print!("OK"),
Result::Failure(error_code, module) =>
print!("Error n. {} in module {}",
error_code, module),
Result::Uncertainty => {},
}

这将打印:“OK”。 手臂“结果::成功”需要一个参数,而不是说它是非法的,但我们不需要这种参数的值,我们不想为我们不这样做的东西伪造一个变量名 需要。 在这种情况下,下划线符号告诉编译器:“我知道你想在这里传递一个参数,但我没有任何用处,所以扔掉它”。

“匹配”表达式

与“if”表达式类似,也有“匹配”表达式:

enum CardinalPoint { North, South, West, East };
let direction = CardinalPoint::South;
print!("{}", match direction {
CardinalPoint::North => 'N',
CardinalPoint::South => 'S',
_ => '*',
});

这将打印:“S。”

我们已经看到,如果使用“if”关键字来创建if表达式,它必须还有一个else块,并且其返回的类型必须与else关键字之前的块相同。

这同样适用于匹配表达式:匹配表达式的所有臂必须具有相同类型的右侧。 在该示例中,三个臂具有值’N’,‘S’和’*’,因此它们都是char类型。

如果第三个臂被“_ => {}”替换,则会出现编译错误“匹配武器具有不兼容的类型”。 实际上,由于两个臂是char类型,而一个是()类型,因此无法确定整个匹配表达式的类型。

在匹配构造中使用警卫

假设我们想要对以下类别中的整数进行分类:所有负数,零数,一个数和所有其他正数:

for n in -2..5 {
println!("{} is {}.", n, match n {
0 => "zero",
1 => "one",
_ if n < 0 => "negative",
_ => "plural",
});
}

该程序将打印:

-2 is negative.
-1 is negative.
0 is zero.
1 is one.
2 is plural.
3 is plural.
4 is plural.

“for”语句将整数从“-2”包括到“5”排除。

对于每个已处理的数字,将打印该数字,然后进行分类。 这种分类由“匹配”构造执行,该构造在此用作具有字符串作为其值的表达式。 实际上,它的每一个臂都有一个字符串作为其值。

第三臂与其他臂不同。 它的模式是下划线,所以这样的一个臂应该总是匹配,但是这样的模式后跟一个由“if”关键字和一个布尔条件组成的子句。 只有当这样的布尔条件为真时,这样的子句才会使该模式匹配。

猜你喜欢

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