Beginning_Rust:使用原始类型(第六章)(完+1)

在本章中,您将学习:
•如何以十六进制,八进制或二进制表示法编写数字文字

•如何使用下划线字符使数字文字更易于阅读

•如何使用指数表示法以紧凑的形式写入大数字或小数字

•哪些是十个原始整数数字类型,以及两个原始浮点数数字类型; 这是他们的范围; 什么时候更好地使用它们

•如何指定具体类型或无约束类型的数字文字

•如何将数值转换为其他数字类型

•其他原始类型:布尔值,字符和空元组

•类型推断的工作原理

•如何表达数组和向量的类型

•如何为编译时常量指定名称

•如何使用编译器来发现表达式的类型

非十进制数字基

我们每天写数字的方式使用所谓的“十进制表示法”或“十进制表示法”,但有时在不同于十的基数中编写数字是很方便的:

let hexadecimal = 0x10;
let decimal = 10;
let octal = 0o10;
let binary = 0b10;
print!("{} {} {} {}",
hexadecimal, decimal, octal, binary);

这将打印:“16 10 8 2”。 这是因为,如果一个字面整数以零数字开头后跟一个“x”(即“hexadecima(十六进制)”的第三个字母),那么该数字用十六进制表示法表示; 相反,如果它以零开头,后跟“o”(即“octal(八进制)”的初始值),则它是以八进制表示法表示的数字; 相反,如果它以零开头,后跟“b”(即“binary(二进制)”的初始值),则它是以二进制表示法表示的数字。 在所有其他情况下,数字以十进制表示法表示。

此示例的编号以不同的符号表示,但它们都是相同的类型:整数。 其实你可以写:

let hexadecimal = 0x10;
let octal = 0o10;
let binary = 0b10;
let mut n = 10;
print!("{} ", n);
n = hexadecimal;
print!("{} ", n);
n = octal;
print!("{} ", n);
n = binary;
print!("{} ", n);

这将打印:“10 16 8 2”。
n变量可以从其他变量接收赋值,因为它们都是相同的类型。

相反,浮点数只能用十进制表示法表示。

请注意,此类表示仅存在于源代码中,因为Rust编译器生成的机器代码始终使用二进制表示法,包括整数和浮点数。
例如,程序:

print!(“{} {}”,0xA,0b100000000);

和程序

print!("{} {}", 10, 256);

生成完全相同的可执行程序。

最后一点:用作十六进制数字的字母可以是无差别的大写或小写。 例如,数字0xAEf5b等于数字0xaeF5B。

相反,用于表示数字基数的字母必须是小写的。 因此,表达式0X4,004 e 0B4是非法的。

数字文字中的下划线

我们看到我们可以写整数“十亿”,为1000000000。但是你确定它包含正好九个零吗? 如果将其写为1_000_000_000,则整数“十亿”更具可读性。 下划线字符(“_”)可以插入任何文字数字,甚至是浮点数,编译器会忽略它们。

即使数字3 ___ 4_.56_也是有效数字,它等于34.56。 虽然通常下划线字符仅用于将十进制数字或八进制数字分组三,或十六进制或二进制数字四,如:

let hexadecimal = 0x_00FF_F7A3;
let decimal = 1_234_567;
let octal = 0o_777_205_162;
let binary = 0b_0110_1001_1111_0001;
print!("{} {} {} {}",
hexadecimal, decimal, octal, binary);

这将打印“16775075 1234567 134023794 27121”。

指数表示法

浮点数可以达到巨大的价值,无论是正数还是负数,如十亿数十亿,还有非常小的数值,非常接近于零,就像十亿分之一十亿分之一十亿分之一。 如果我们使用到目前为止使用的符号来编写如此大或小的数字的文字,我们通常应该写很多零,并且即使使用下划线,结果数字也难以阅读。

但您也可以用另一种方式编写浮点字面数字:

let one_thousand = 1e3;
let one_million = 1e6;
let thirteen_billions_and_half = 13.5e9;
let twelve_millionths = 12e-6;

第一行使用字面意思“将十倍提升到三次幂”。 在十进制表示法中,它相当于在“e”之前写入数字,然后将小数点向右移动“e”之后指示的位数,如果没有足够的数字,则添加零。 在我们的例子中,我们写“1”,然后我们将点移动三个位置,添加尽可能多的零,因此我们得到数字“1000”。

“e”之前的数字被命名为“尾数”,而它后面的数字被命名为“指数”。 它们都是带符号的十进制数。 尾数也可以有一个小数点,后面可以跟一个小数部分。

这种表示法被命名为“指数”。 以指数表示法编写的文字仍然是浮点数,即使它们没有小数点也是如此。

示例中的第二个字面意思是“一次十次提升到六次幂”,即数字“1000000”。

第三个字面意思是“十三点十五次提升到九次幂”。 已经有一个十进制数字,超出该数字我们必须移动周期,之后我们必须添加八个零,以获得值“13500000000”。 这个数字也可以写成1.35e10,这意味着“一点三十五倍提升到十分之一”,或者说135e8,或者也就是13500e6,以及其他方式,都产生相同的机器代码。

最后,第四个字面意思是“十二倍十提升到第六个负面的力量”,或者等价地,“十二个除以十个提升到第六个力量”。 使用十的负指数相当于写入尾数,然后将点向左移动指数的减号后面指示的数字,添加所需的零。 因此,数字12e-6等于数字0.000012。

各种有符号整数

到目前为止,我们说有两种类型的数字:整数和浮点数。

有些编程语言只有浮点数,没有特定的整数类型; 对于这些语言,具有两种不同的数字类型更复杂。 但在Rust中实际上有十种不同的整数数字类型和两种浮点数字类型,所以它相对来说非常复杂。 具有更多数字类型可以具有优势,因此许多编程语言具有多种数字类型。

到目前为止,我们使用整数和浮点数,没有进一步的规范,但在定义这些数字的内部格式时可能更精确。

这里出现了Rust的一个重要方面:效率。实际上,Rust的设计非常高效。

一种简单的语言只能使用32位整数。但是如果我们想要存储一长串的小数字,比如介于0和200之间,并且如果每个值都存储在32位对象中,则会浪费一些内存,因为它可以表示0到200之间的任何数字。仅使用8位,即32位的四分之一。

这不仅可以节省RAM空间或存储空间,还可以优化速度;因为我们的对象越大,它们使用的缓存空间就越多;和缓存空间是严格限制的。如果某个对象无法包含在缓存中,则其访问权限会降低该程序的速度。要拥有一个快速程序,您应该在缓存中保留尽可能多的已处理数据。为此目的,物体应尽可能小。在我们的示例中,我们不应该使用超过8位来存储我们的数字。

另一方面,32位数字可能不足以表示应用程序所需的所有值。例如,程序可能需要精确存储大于十亿的数字。在这种情况下,需要具有多于32位的类型。

因此,Rust提供了使用8位整数,16位整数,32位整数以及64位整数的机会。 如果需要其中许多,例如在数组或向量中,建议使用能够表示应用程序逻辑所需的所有值的最小数据类型。

让我们看看我们如何使用这些数字:

let a: i8 = 5;
let b: i16 = 5;
let c: i32 = 5;
let d: i64 = 5;
print!("{} {} {} {}", a, b, c, d);

插入第一个语句中的“:i8”子句和后面三个语句中的类似子句定义了声明的变量的类型,以及由此变量表示的对象的类型。

单词i8,i16,i32和i64是Rust关键字,分别标识类型“8位有符号整数”,类型“16位有符号整数”,类型“32位有符号整数”,和类型“64位有符号整数”i字母是“整数”的首字母。

这些类型精确地识别对象将使用多少位。例如,“a”变量将使用8位,它将能够表示256个不同的值,并且是有符号数,这样的对象将能够包含-128和+127之间的值,包括极值。

变量“a”,“b”,“c”和“d”有四种不同的类型,因此如果我们将其中一个变量的赋值语句附加到另一个变量,如b = d;,我们将得到一个编译错误。

我们已经看到,不可能计算整数和浮点数之间的加法,因为它们具有不同的类型。类似地,不可能将两个具有不同位数的整数相加:

let a: i8 = 5;
let b: i16 = 5;
print!("{}", a + b);

此语句使用消息“不匹配的类型”生成编译错误。 相反,以下代码有效并将打印“23 23 23”:

let a: i16 = 5;
let b: i16 = 18;
let c: i64 = 5;
let d: i64 = 18;
let e: i32 = 5;

let f: i32 = 18;
print!("{}", a + b);
print!("{}", c + d);
print!("{}", e + f);

也许有人会想知道为什么整数的位数必须恰好是8,16,32或64,而不是,例如,19。
这是由于三个原因,都与效率有关:

•每个现代处理器都有算术和数据传输指令,只能有效地应用于具有8位,16位,32位和64位的数字。无论如何,处理32位数字的相同机器语言指令将处理19位数,因此区分19位类型和32位类型没有任何优势。

•操作大小为2的对象时,内存管理更有效。因此,具有不同大小的对象会导致代码效率降低或需要分配额外空间以达到2的幂(此操作被称为“填充”)。

•如果对不同大小的对象执行相同的概念操作,则编译器必须生成不同的机器语言指令。如果有许多不同的类型,则必须有很多机器代码,即使所有这些类型的源代码都相同。这种现象被称为“代码膨胀”。它严重地使用了指令缓存,因此导致程序变慢。相反,如果程序只包含几种类型,编译器可以生成更紧凑的代码,这更适合CPU缓存。

第三个原因可能表明并不总是使用尽可能小的类型,而是只使用一种类型,这种类型可能很大。 实际上,使用最小可能类型的规则仅适用于相当大的数据集合,而对于单个对象(也称为“标量”)或非常小的集合,使用尽可能少的数据类型更有效。 因此,最大化高速缓存效率的一般规则是“最小化机器代码加数据所使用的存储器”。

无符号整数数字类型

如果我们必须定义一个包含整数的对象,该整数的值可以在0到200之间,那么哪种类型最好使用? 根据最后一节,在可以代表所有这些值的那些中使用最小类型可能更好。 i8类型是最小的,但它只能表示介于-128和+127之间的值,因此它没有用。 因此,到目前为止遇到的类型,我们必须使用i16。

这不是最佳的,因为如果我们重新解释它们,所有0到255之间的值(包括极值)只能使用8位来表示。 并且这种重新解释已经包含在所有现代处理器的机器语言中,因此不使用它将是一种遗憾(读作“低效”)。

因此,Rust允许使用其他四种数字类型:

let a: u8 = 5;
let b: u16 = 5;
let c: u32 = 5;
let d: u64 = 5;
print!("{} {} {} {}", a, b, c, d);

这里我们介绍了其他四种类型的整数。 “u”字母,“unsigned”的简写,表示它是无符号整数。 “u”字母后面的数字表示这个对象使用了多少位; 例如,“a”变量使用8位,使用它可以表示256个不同的值,因此,无论是无符号数,这样的值将是0到255之间的整数,包括极值。

但是至少有另一个理由选择无符号数字来签名数字。 如果我们想检查有符号整数x是否在0和0之间,我们应该写出布尔表达式0 <= x && x <n。 但是如果x是无符号数,那么只需使用表达式x <n就可以完成这样的检查。

请注意,变量“a”“b”,“c”和“d”有四种不同的类型,它们在它们之间是不同的,也来自相应的带符号类型。

目标相关的整数数字类型

到目前为止,我们已经看到八种不同的类型来表示整数,但Rust仍然有其他整数数字类型。

当您访问数组或向量中的项目时,哪种类型应具有索引?

您可以认为,如果您有一个小数组,您可以使用i8值或u8值,而如果您有一个更大的数组,则需要使用i16值或u16值。

事实并非如此。 它是最有效的类型,用作数组或向量的索引:

•在16位计算机上,它是无符号的16位整数;
•在32位计算机上,它是无符号的32位整数;
•在64位计算机上,它是无符号的64位整数。

换句话说,数组或向量的索引应该是无符号的,并且它应该具有相同大小的内存地址。

目前,16位系统不支持Rust,但它适用于32位系统和64位系统。 那么,我们应该使用哪种类型来编写一些在32位计算机和64位计算机上都应该是最佳的源代码?

请注意,它与编译器运行的系统无关,而是与编译器生成的程序运行的系统无关。 实际上,通过所谓的“交叉编译”,编译器可以为具有与运行编译器的架构不同的架构的系统生成机器代码。 生成机器代码的系统被命名为“target”。因此需要指定一个大小取决于目标的整数数字类型,如果目标是32位系统,则为32位整数, 如果目标是64位系统,则为64位整数。

为此,Rust包含isize类型和usize类型:

let arr = [11, 22, 33];
let i: usize = 2;
print!("{}", arr[i]);

这将打印“33”。

在单词usize中,“u”字母表示它是无符号整数,“size”字表示它是一种被认为可以测量某些(可能非常大)对象长度的类型。
编译器将usize类型实现为u32类型,如果它为32位系统生成机器代码,则将其实现为u64类型,如果它为64位系统生成机器代码。 如果支持16位系统,那么在为这样的系统生成代码时,可能会将usize类型实现为u16类型。

通常,每次需要无符号整数时,usize类型都很有用,它具有相同大小的内存地址(也就是指针)。 特别是,如果必须索引数组:

let arr = [11, 22, 33];
let i: usize = 2;
print!("{}", arr[i]);
let i: isize = 2;
print!("{}", arr[i]);
let i: u32 = 2;
print!("{}", arr[i]);
let i: u64 = 2;
print!("{}", arr[i]);

此代码将生成三个编译错误,每个打印调用一个,第一个除外。 实际上,只允许usize类型作为数组的索引。

如果使用向量而不是数组,则会打印类似的错误消息。

通过这种方式,Rust允许我们以最有效的方式访问数组和向量。

请注意,即使在32位系统上使用u32类型的索引,也不允许在64位系统上使用u64类型的索引。 这保证了源代码的可移植性。

对于对称性,还有isize类型,它是有符号整数,在目标系统中具有相同大小的内存地址。

类型推断

在前面的章节中,我们在没有指定类型的情况下声明变量,我们讨论的是“整数”,“浮点数”等类型。

在本章中,我们开始向变量声明添加数据类型注释。

但是如果没有指定类型,变量是否仍然具有特定类型,或者它们是泛型类型?

let a = [0];
let i = 0;
print!("{}", a[i]);

该计划有效。 怎么会? 我们不是说索引数组,只有usize表达式有效吗?

实际上,每个变量和每个表达式总是具有明确定义的类型,但并不总是需要明确指定这样的类型。 在许多情况下,编译器能够推断出它,或者,正如通常所说的那样,从使用的变量或表达式的方式推断它。

例如,在前面的例子中,在为“i”分配整数值0之后,编译器推断出“i”的类型必须是整数的类型,但是它还没有确切地确定哪一个是 Rust中有十种整数类型。 我们说这种变量的类型是泛型或更好的无约束整数的类型。

但是,当编译器意识到这样的变量用于索引数组时,操作只允许使用usize类型,编译器会将usize类型分配给“i”变量,因为它是唯一允许的类型。 在这个节目中,

let i = 0;
let _j: u16 = i;

编译器首先确定“i”是“无约束整数”类型,然后它确定“_j”是u16类型,因为这种类型被明确注释,然后,因为“i”用于初始化“_j”, 操作只允许使用类型为u16的表达式,它确定“i”属于这种类型。
相反,编译这个程序

let i = 0;
let _j: u16 = i;
let _k: i16 = i;

在第三行产生错误,预期消息i16,找到u16。 实际上,按照上述推理,编译器在第二行确定“i”必须是u16类型,但在第三行“i”用于初始化类型i16的变量。
相反,该计划有效:

let i = 0;
let _j: u16 = i;
let _k = i;

在这种情况下,变量“_k”出现为u16类型。

请注意,这种推理始终在编译时执行。 在每次成功编译的最后阶段,每个变量都有一个具体的约束类型。

如果编译器无法推断变量的类型,则会生成编译错误。

相反,如果编译器成功仅推断出整数为1的类型,但它不能将其约束为特定的整数类型,那么,作为“默认”整数类型,它采用类型i32。
例如:

let i = 8;
let j = 8_000_000_000;
print!("{} {}", i, j);

该程序将打印:“8 -589934592”。

这两个变量都是i32类型。 太糟糕了,第二个用一个太大而不能包含在i32对象中的数字初始化。

编译器意识到这一点,因此它会向i32发出超出范围的警告文字。 在Rust中,出于效率原因,与C语言类似,整数数字溢出在编译时或运行时都不会生成错误。 然而,效果是数字以截断的二进制格式存储,仅留下最低有效32位,然后这些位由打印宏解释,因为它们是有符号整数。

类型推断算法

我们看到编译器总是尝试为每个变量和每个表达式确定具体类型。 对于我们目前所看到的,使用的算法如下。

如果明确指定了类型,那么类型必须是指定的类型。

如果还没有确定变量或表达式的类型,并且在表达式或只能使用特定类型有效的声明中使用此类变量或表达式,则此类型将在此确定 这种变量或表达的方式。 这种确定可以是受约束的类型,或者它可以是不受约束的类型。 约束类型是特定类型,如i8或u64,而无约束类型是类别类别,如{integer}。

如果在解析结束时,编译器仅确定变量是无约束整数数字类型,则此类型被定义为i32。 相反,如果类型完全未确定,则会生成编译错误。

浮点数字类型

关于浮点数值类型,情况类似于整数的情况但更简单:目前在Rust中只有两种浮点类型。

let a: f64 = 4.6;
let b: f32 = 3.91;
print!("{} {}", a, b);

该程序将打印:“4.6 3.91”。

“f64”类型是64位浮点数,而“f32”类型是32位浮点数。 “f”字母是“浮点”的简写。 这些类型完全对应于C语言的“double”和“float”类型。

到目前为止,Rust没有其他数字类型,但如果添加了128位浮点类型,它的名称可能是“f128”。

我们所说的整数类型也适用于这些类型。 例如:

let a = 4.6;
let mut _b: f32 = 3.91e5;
_b = a;

该计划有效。 解析第一行的编译器确定变量“a”具有无约束的浮点数字类型。 然后,解析第三行,它确定变量“a”的类型为f32,因为这是唯一允许为f32类型的变量赋值的类型。

“默认”浮点类型是64位浮点类型。 因此,如果此程序中没有最后一行,则“a”变量将为f64类型。

对于浮点数,在32位和64位数字之间进行选择的标准与整数数字相似,但有些模糊。 32位数字仍然占据64位数字的一半内存和缓存。 并且,由64位数字表示的最大值仍然大于可由32位数字表示的最大值。 然而,后者是如此之大,很少超过

相反,更重要的事实是,如果64位数字在尾数中具有更多的数字,并且这使得这些数字更加精确。 实际上,32位数字具有24位尾数,而64位数字具有53位尾数。

为了给你一个想法,32位数字可以精确地表示所有整数,最多只能达到大约1600万,而64位数字可以精确表示所有整数,最多可达到大约900万。 换句话说,f32类型的每个值(以十进制表示法表示)有近7位有效数字,而每个f64有近16位有效数字。

类型的转换

我们曾多次说Rust执行严格的检查:每次编译器需要某种类型的表达式时,如果找到另一种类型的表达式,即使相似,也会产生错误; 并且在每个算术表达式中,编译器期望其操作数具有相同的类型。

这些规则似乎可能禁止涉及不同类型对象的任何计算,但事实并非如此:

let a: i16 = 12;
let b: u32 = 4;
let c: f32 = 3.7;
print!("{}", a as i8 + b as i8 + c as i8);

这将打印:“19”。

变量“a”,“b”和“c”具有三种不同的类型。 最后一个甚至不是整数。 但是,通过使用“as”运算符,后跟类型名称,您可以进行多次转换,包括上面显示的三种转换。

该示例的所有三个对象都被转换为i8类型的对象,因此可以对这样的结果对象进行求和。

请注意,如果目标类型的表达不如原始类型,则可能会丢失信息。 例如,当您将小数值3.7转换为整数类型i8时,将丢弃小数部分,并获得3。

此代码的行为有点难以预测:

let a = 500 as i8;
let b = 100_000 as u16;
let c = 10_000_000_000 as u32;
print!("{} {} {}", a, b, c);

也许令人惊讶的是,它将打印:“ - 12 34464 1410065408”。

只有在考虑用于表示整数的二进制代码时,才能轻易理解这种行为。

值500不能仅使用8位表示,但至少需要9位。 如果我们采用它的二进制表示,我们提取其最低有效8位,然后我们将这样的8位序列解释为“i8”对象,当我们以十进制表示法打印这样的对象时,我们得到-12。

类似地,解释为无符号整数的十万分之一二进制表示的最低有效16位以十进制表示法打印为34464;

十亿分之二十进制表示的最低有效32位,解释为无符号数,以十进制表示法打印为1410065408。

因此,as运算符,如果应用于整数对象,则从这样的对象中提取足够的最低有效位来表示指定的类型,并且它生成表达式结果的值。

输入数字文字的后缀

到目前为止,我们使用了两种数字文字:整数,如-150; 和浮点数,如6.022e23。 前者是“无约束整数”类型,后者是“无约束浮点数”类型。

如果要约束数字,有以下几种方法:

let _a: i16 = -150;
let _b = -150 as i16;
let _c = -150 + _b - _b;
let _d = -150i16;

所有这四个变量都是i16型,它们具有相同的值。

第一个已被明确声明为此类型。 在第二行中,无约束整数数值表达式已转换为特定类型。 在第三行中,表达式包含特定类型的子表达式“_b”,因此整个表达式都是这样的类型。 最后,在第四行中使用了新的表示法。

如果在整数数字文字之后指定了一个类型,则文字会获得这样的类型,就像插入了“as”关键字一样。 请注意,在文字和类型之间,不允许空白。 如果您愿意,可以添加一些下划线,例如-150_i16或5__u32。

类似地,您可以修饰浮点数字文字:6.022e23f64是64位浮点数,而-4f32和0_f32是32位浮点数。 请注意,如果没有小数位,则不需要小数点。

所有数字类型

总之,这是一个使用所有Rust数字类型的示例:

let _: i8 = 127;
let _: i16 = 32_767;
let _: i32 = 2_147_483_647;
let _: i64 = 9_223_372_036_854_775_807;
let _: isize = 100; // The maximum value depends on the target architecture
let _: u8 = 255;
let _: u16 = 65_535;
let _: u32 = 4_294_967_295;
let _: u64 = 18_446_744_073_709_551_615;
let _: usize = 100; // The maximum value depends on the target architecture
let _: f32 = 1e38;
let _: f64 = 1e308;

以下是所有Rust内置整数数字类型的列表:

类型占用的字节数最小值最大值
i81-128+127
i162-32,768+32,767
i324-2,147,483,648+2,147,483,647
i648-2^63+263 - 1
isize4 or 8on a 32-bit target: -2,147,483,648; on a 64-bit target: -2^63on a 32-bit target: +2,147,483,647; on a 64-bit target: +2^63 - 1
u810+255
u1620+65,535
u3240+4,294,967,295
u6480+2^64 - 1
usize4 or 80on a 32-bit target: +4,294,967,295;on a 64-bit target: +264 - 1

相反,只有两种浮点数字类型:
•f32,具有32位,相当于C语言的float类型。
•f64,具有64位,相当于C语言的double类型。

布尔和字符

除了数字类型,Rust还定义了一些其他原始内置类型:

let a: bool = true; print!("[{}]", a);
let b: char = 'a'; print!("[{}]", b);

这将打印:“[true] [a]”。

我们已经看到的bool类型等同于具有相同名称的C ++语言的类型。 它只承认两个值:false和true。 它主要用于if和while语句中的条件。

char类型,实际上我们还没有看到,看起来像具有相同名称的C语言的类型,但实际上它与它有很大的不同。 首先,C语言char通常只占用一个字节,而隔离的Rust char占用四个字节。 这是因为Rust字符是Unicode字符,而Unicode标准定义了超过一百万个可能的值。

文字字符用单引号括起来,也可以是非ASCII字符。 例如,这段代码

let e_grave = 'è';
let japanese_character = 'さ';
println!("{} {}", e_grave, japanese_character);

将打印“èさ”。

请注意,与C语言不同,bool和char都不以任何方式被视为数字,因此以下两个语句都是非法的:

let _a = 'a' + 'b';
let _b = false + true;

但是,这两种类型都可以转换为数字:

print!("{} {} {} {} {}", true as u8, false as u8,
'A' as u32, 'à' as u32, '€' as u32);

这将打印:“1 0 65 224 8364”。

通过这种方式,我们发现true表示数字1,false表示数字0,“A”字符表示数字65,严重“a”表示数字224,欧元符号表示数字8364。

相反,如果您想将数字转换为布尔值或字符,您可以使用以下功能:

let truthy = 1;
let falsy = 0;
print!("{} {} {} {}", truthy != 0, falsy != 0,
65 as char, 224 as char);

这将打印:“true falseAà”。

相反,您不能将as bool子句与数字一起使用,因为并非每个数值都对应一个布尔值; 事实上只有零和一个具有此属性,因此通常这种转换不会很好地定义。

因此,如果对于一个数字,零值意味着表示虚假,要将这个数字转换为布尔值,即看它是否对应于真值,就足以检查它是否与零不同。

字符的情况类似。 每个字符由一个32位数字表示,因此它可以转换为它,但不是每个32位数字都代表一个字符,因此一些(实际上,大多数)32位数字不能转换为字符。 因此,表达式8364作为char是非法的。

要将任何数字转换为字符,您需要使用库函数,此处未对此进行描述。

但是,对于0到255之间的每个数字,都有一个与之对应的Unicode字符,因此可以将其转换为任意数量的u8类型的字符。 实际上,在上面的例子中,对于数字65和224已经完成了。

对于那些不知道Unicode的人来说,看到与前256个数字对应的所有字符可能会很有趣:

for i in 0..256 {
println!("{}: [{}]", i, i as u8 as char);
}

这将打印256行,每行包含一个数字及其对应的字符。 这些字符中的一些是终端控制代码,例如“换行”和“回车”,而其他字符是不可打印的。

请注意,在将其转换为char之前,我必须转换为u8类型。

空元组

还有另一种原始的,奇怪的类型,它在Rust中的名称是“()”,它是一对括号。 这种类型只有一个值,它的编写方式与其类型相同,即“()”。 这种类型有点对应于C语言的“void”类型,或者对应于“未定义”类型的JavaScript,因为它表示缺少类型信息。 为了能够发音它的名字,它被命名为“空元组”。

此类型出现在几种情况下,如下所示:

let a: () = ();
let b = { 12; 87; 283 };
let c = { 12; 87; 283; };
let d = {};
let e = if false { };
let f = while false { };
print!("{:?} {:?} {:?} {:?} {:?} {:?}",
a, b, c, d, e, f);

此代码将打印:“()283()()()()”。

第一行声明一个类型为“()”的变量,并使用唯一可能的值对其进行初始化。 在最后一行中,“print”宏无法将此类型与占位符“{}”匹配,因此必须使用调试占位符“{:?}”。

第二行声明一个变量并使用块的值初始化它。 从这里出现一些新概念。

第一个概念是可以使用像“12”或“87”这样的简单数字来代替任何语句,因为任何表达式都可以用来代替语句。 当然,这样的声明什么都不做,因此它不会生成任何机器代码。

第二个概念是,如果存在这样的表达式,则将块的值定义为其最后一个表达式的值; 因此,在第二行的情况下,块的值是整数“283”,并且这样的值用于初始化“b”变量,因此它将是i32类型。

第三行显示了块的内容以语句终结符(分号字符)结尾的情况。 在这种情况下,块的值是“()”,并且这样的值用于初始化“c”变量,因此它将是“()”类型。

第四行声明“d”变量,并使用空块的值初始化它。 空块也有空元组作为它们的值。

在第五行中有一个没有“else”分支的条件表达式。 当缺少“else”分支时,隐含“else {}”子句。 因此,这样的陈述意味着“让e = if false {} else {}”。 这样的条件表达式是有效的,因为两个分支具有相同的类型。

第六行显示“while”语句也具有空元组的值。 实际上,“while”语句块和“while”语句本身必须始终具有空元组的值,因此将“while”构造用作表达式几乎没有意义。 这也适用于“循环”和“for”循环。

数组和矢量类型

在展示数组和向量时,我们说如果我们改变所包含项的类型,我们也会隐式地改变数组和向量的类型; 如果我们更改包含项的数量,我们也会隐式更改数组的类型,但不会更改向量的类型。

如果你想明确数组或向量的类型,你应该写:

let _array1: [char; 3] = ['x', 'y', 'z'];
let _array2: [f32; 200] = [0f32; 200];
let _vector1: Vec<char> = vec!['x', 'y', 'z'];
let _vector2: Vec<i32> = vec![0; 5000];

如图所示,表示数组类型的表达式既包含项的类型,也包含它们的数字,用分号分隔并用方括号括起来。

相反,向量的类型被写为单词Vec(带有大写的首字母),后面是包含的项的类型,用尖括号括起来。

常量

以下程序是非法的:

let n = 20;
let _ = [0; n];

之所以如此,因为数组必须具有编译时已知的长度,即使“n”是不可变的,因此,在某种意义上,常量,其初始值可以在运行时确定,因此不允许指定 数组的大小。

但是以下程序是有效的:

const N: usize = 20;
let _ = [0; N];

“const”关键字允许我们声明一个标识符,该标识符具有在编译时定义的值,当然在运行时不再可更改。 在其声明中,需要指定其类型。

Rust常量对应于C ++语言const项。

Rust常量可以被认为是在编译时与值相关联而不与对象相关联的名称。 编译器在程序中使用常量名称的每个位置替换此值。

发现表达式的类型

你经常会遇到一个表达式,想知道这种表达的类型是什么。

这可以通过解释器,集成开发环境或书面文档来解决,但是有一个技巧可以仅使用编译器来回答这类问题。

假设我们想知道表达式4u32 / 3u32的类型,在某些语言中是浮点数。

我们只是添加一个语句,尝试使用该表达式来初始化bool变量。 如果程序编译没有错误,这意味着我们的表达式是bool类型。 但在我们的案例中,我们有:

let _: bool = 4u32 / 3u32;

编译此程序会生成错误不匹配的类型,错误消息的详细信息解释了预期的bool,找到了u32。 从这样的解释,我们知道我们的表达式是u32类型。 有时,错误消息更加模糊。

let _: bool = 4 / 3;

对于这个程序,错误解释是“预期bool,找到积分变量”,然后“期望类型bool找到类型{integer}”。 表达式“整数类型”和等价的“{integer}”并不表示具体类型; 它表示一个仍然不受约束的类型,编译器已经确定它是一个整数类型,但还没有几个现有的整数类型中的哪一个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值