在本章中,您将学习:
- “值”,“对象”和“变量”的概念
- 变量“可变性”的概念
- 初始化和重新分配之间的差异
- 如何避免对未使用的变量发出警告
- “布尔表达式”概念
- 编译器为分配执行哪些类型检查
- 某些运算符如何执行算术运算和赋值
- 如何调用Rust标准库中定义的函数
将名称与值相关联
到目前为止,我们已经看到了三种值:字符串,整数和浮点数。
但是值不应该与对象和变量混淆。 所以,让我们定义“值”,“对象”和“变量”这两个词的实际含义。
“价值”一词表示抽象的数学概念。 例如,当你说“值12”时,你的意思是数字12的数学概念。在数学中,世界上只有一个数字12。 甚至是真或“Hello”是概念上存在于Universe中的单个实例中的值,因为它们是概念。
但是值可以存储在计算机的存储器中。 您可以将数字12或字符串“Hello”存储在内存的多个位置。 因此,您可以拥有两个不同的内存位置,这两个位置都包含12值。
包含值的内存部分名为“object”。 位于存储器的不同位置的两个不同的对象如果包含相同的值则被称为“相等”。 相反,当且仅当它们不相同时,两个值被称为“相等”,即它们实际上是相同的值。
编译Rust源代码时,生成的可执行程序仅包含具有内存位置和值的对象。 这些对象没有名称。 但是在源代码中,您可能希望将名称与对象关联,以便稍后引用它们。 例如,您可以编写,作为主函数的主体:
let number = 12;
let other_number = 53;
print!("{}", number + other_number);
这将打印65。
单词“let”,如已经看到的单词“fn”,是该语言保留的关键字,即不能用于其他目的的单词。
第一个语句在程序中引入一个包含值12的对象,并将名称号与该对象相关联。 第二个语句引入另一个对象并将其与另一个名称相关联。 第三个语句使用先前定义的名称访问这两个对象。
第一个语句具有以下效果:
它保留一个足够大的对象(即一个存储区)来包含一个整数;
它以二进制格式将值12存储在这样的对象中;
它将名称编号与此类对象相关联,以便可以在源代码的后续点使用此名称来指示此类对象。
因此,这样的陈述不是简单的“别名”声明。 这并不意味着“从现在开始,我们每次用字号码时,我们将意味着值12”; 相反,它意味着“应保留一个存储空间以包含最初的值12,从现在开始,每次我们使用字数时,我们将意味着这样的存储空间”。 因此,此类语句声明该对象的对象和名称。 “对象名称”的同义词是“标识符”。
标识符 - 对象对称为“变量”。 所以,该语句是变量的声明。
但它不仅仅是一个定义。 变量的简单声明仅为对象保留空间,并将标识符与此对象相关联。 对象的值仍未定义。 相反,此语句还指定此对象的初始值。 分配对象的初始值称为“初始化”该对象。 所以,我们说这个语句声明并初始化一个变量。
为对象保留存储区的操作被命名为该对象的“分配”。 相反,删除一个对象,使其内存区域可用于分配其他对象,被命名为该对象的“释放”。 所以我们说这个语句分配一个对象,将它分配给一个标识符,并初始化该对象(或由对标识符对象组成的变量)。
这些概念与C语言的概念相同。
第二个陈述与第一个陈述类似。
在声明并初始化变量之后,您可以在表达式中使用此类变量的名称,并且对此变量的求值会给出存储在其对象中的值。 实际上,上面的第三个语句似乎添加了两个变量的名称,并添加了它们的当前值。
如果省略前两个语句中的任何一个,则第三个语句将生成编译错误,因为它将使用未声明的变量; 这是被禁止的。 在以下代码的第二个语句中:
let number = 12;
print!("{} {}", number, 47);
打印两个数字,12和47,但打印12是因为它是变量的值,而47是文字。
可变变量
在适当地声明变量之后,您可以在另一种名为“赋值”的语句中修改其值:
let mut number = 12;
print!("{}", number);
number = 53;
print!(" {}", number);
This will print 12 53.
第一个语句声明变量number并将其初始化为值12.第二个语句打印此变量的值。 第三个语句将值53赋给同一个变量。 第四个语句打印变量的新值。
赋值不分配对象。 它只是修改已分配对象的值。
您可能注意到第一个语句包含单词“mut”; 它是Rust的关键字,它是“mutable”的缩写。 实际上,传统的名称“变量”有些不合适,因为它也适用于实际上不能变异的对象,因此是常量,并且不可变; 有人试图使用替代名称“绑定”,但名称“变量”在如此多的语言中根深蒂固,其含义几乎是普遍的。 因此,在本文中,我们将始终使用“变量”一词。
鉴于“变量”一词既用于将名称与可能被更改的对象相关联,又用于将名称与无法更改的对象相关联,第一类变量被命名为“可变变量”,而 第二个被命名为“不可变变量”。
简单的关键字“let”声明了一个不可变的变量,而序列“let mut”需要能够声明一个可变的变量。
在上一节中,我们声明了两个不可变变量,实际上这些变量的值在初始化后从未被修改过。
相反,在上面显示的最后一个程序中,假设我们想要修改变量的值,我们将其声明为可变。 否则,在第三个语句中,我们将收到此消息的编译错误:重新分配不可变变量number
。
相当于上一节中第一个Rust程序的C语言版本是:
#include <stdio.h>
int main() {
int const number = 12;
int const other_number = 53;
printf("%d", number + other_number);
return 0;
}
而本节中相当于Rust程序的版本是:
#include <stdio.h>
int main() {
int number = 12;
printf("%d", number);
number = 53;
printf(" %d", number);
return 0;
}
请注意,当Rust声明不包含“mut”关键字时,相应的C声明包含“const”关键字,相反,当Rust声明包含“mut”关键字时,相应的C声明不包含“const”关键字 “关键字。
换句话说,在C语言中,最简单的声明形式定义了一个可变变量,你必须添加一个关键字来获得不变性,而在Rust中,最简单的声明形式定义了一个不可变变量,你必须添加一个关键字来获得可变性。
不变异的可变变量
正如我们之前所说的,如果在初始化之后,您尝试为不可变变量分配新值,则会出现编译错误。 另一方面,将变量声明为可变,然后永远不会为其分配新值也不是错误。 但是,编制者注意到这种情况的不足之处,并将其作为警告报告。 代码
let mut number = 12;
println!("{}", number);
可以生成以下编译消息(可能会根据编译器的版本和编译选项而更改):
warning: variable does not need to be mutable
--> main.rs:2:9
|
2 | let mut number = 12;
| ---^^^^^^^
| |
| help: remove this `mut`
|
= note: #[warn(unused_mut)] on by default
警告消息的第二行指示导致警告的源代码部分。 它是文件main.rs,从第2行的第9列开始。消息的后六行显示了代码的相关部分的代码行,并建议进行更正。
最后一行表示存在可以设置为启用或禁用此特定类型的警告报告的编译指令。 如警告所示,编译器的默认行为是在永远不会更改某些可变变量时打印警告。
未初始化的变量
到目前为止,每次我们声明一个变量时,我们也会在同一个语句中初始化它。 相反,我们也允许声明一个变量而不在同一语句中初始化它,如下面的程序:
let number;
number = 12;
print!("{}", number);
它将打印12。
那么下面的代码是做什么的?
let number;
print!("{}", number);
它会生成编译错误。 编译器注意到在第二行中,在不分配任何值的情况下计算变量号,因此第二个语句将具有未定义的行为。
相反,以下代码有效。
let number1;
let number2 = 22;
number1 = number2;
print!("{}", number1);
该程序将打印22.这样的值首先用于初始化第二个语句中的变量number2,然后在第三个语句中初始化变量number1。
相反,以下代码将生成另一个编译错误:
let number1;
print!("{}", number1);
number1 = 12;
实际上,在这种情况下,变量number1在第三个语句中初始化,但是当它的值尚未定义时,它已在第二个语句中进行了评估。
但是,单独站立的以下声明是非法的:
let number;
对变量赋值的第一个命名为“初始化”,如果它发生在声明语句本身或后面的语句中。 相反,进一步的任务被命名为“重新分配”。
因此,规则是每个变量(可变或不可变)必须具有一个初始化,并且这种初始化必须在遇到试图评估此类变量的语句之前发生。 不可变变量不能重新分配。 如果可变变量没有重新分配,编译器可以发出警告。
前导下划线
有时候,它恰好声明一个变量,为它赋值,并且再也不会使用这样的变量。 当然,所有这些都有明确的行为; 但它有什么用于初始化变量,但从不使用它的值? 编译器正确地怀疑它是编程错误,并将其报告为警告。 如果您编译此代码:
let number = 12;
你收到以下警告:
warning: unused variable: `number
--> main.rs:2:9
|
2 | let number = 12;
| ^^^^^^
|
= note: #[warn(unused_variables)] on by default
= note: to avoid this warning, consider using `_number` instead
如果这种警告令人讨厌,那么通过使用警告指示的指令来使其静音; 但警告的最后一行建议有一种更简单的方法:
let _number = 12;
此代码不会生成警告。 标识符的任何部分都允许使用下划线字符(“_”),但如果它是第一个字符,则会产生静音此类警告的效果。 因此,按照惯例,只要您在获得初始值后不评估此类变量,就会将前导下划线置于标识符中。
此外,以下语句不会生成错误或警告:
let _ = 12;
不过,这句话有另一种含义。 它不声明变量。 单个下划线字符不是有效标识符,但它是一个占位符,表示您不想指定任何名称。 这是一个“不在乎”的象征。
当您尝试评估该符号时,会出现差异。 以下程序有效:
let _number = 12;
print!("{}", _number);
但下面的一个不是:
let _ = 12;
print!("{}", _);
它生成一个编译错误,其中包含消息“expected expression,found_
”。
因此,孤立的下划线不是有效的表达式; 相反,它是您不想指定的某些语法符号的占位符。 当然,代码的任何一点都不允许这样做。 我们已经看到允许不指定要声明的变量的名称,但是在表达式中不允许进行评估,因为这样的符号没有值。
布尔值
为了表示真值,使用关键字true和false。 这些关键字是具有非数字类型的表达式,名为“布尔”。
写下代码
let truth = true;
let falsity = false;
print!("{} {}", truth, falsity);
这将打印出真假。
布尔值,除了通过评估关键字true和生成
false,由关系表达式生成。 例如:
let truth = true;
let falsity = false;
print!("{} {}", truth, falsity);
这将打印出真假。
布尔值,除了通过评估关键字true和生成
false,由关系表达式生成。 例如:
let truth = 5 > 2;
let falsity = -12.3 >= 10.;
print!("{} {} {}", truth, falsity, -50 < 6);
这将打印true false true。
这将打印true false true。
在上面的代码中,表达式5> 2(应该是“5大于2”)是算术正确的,因此它使用真值布尔值初始化真值变量。
在第二行中执行类似的操作,其中将两个浮点数与大于或等于运算符进行比较。
最后,在第三行中,“less-than”运算符直接用在表达式中,该表达式是调用print宏的第四个参数。
关系运算符如下:
•==:等于
•!=:不同于
•<:小于
•<=:小于或等于
•>:大于
•> =:大于或等于
如您所见,Rust关系运算符与C语言中使用的运算符相同。
它们中的每一个都适用于两个整数,或两个浮点数,甚至适用于其他类型的值,如字符串。 例如:
print!("{} {} {}", "abc" < "abcd", "ab" < "ac", "A" < "a");
这将打印真实的真实。
但是,要比较的两个值必须是相同的类型。 例如,表达式3.14> 3无效。
比较字符串时,“<”运算符,而不是被认为是“小于”,应该被认为是“先行”,而“>”运算符应该被认为是“跟随”。 排序标准是语言词典,也称为“词典”。
这样的标准如下。 首先比较两个字符串的第一个字符,然后继续执行两个字符串相同位置的比较字符,直到出现下列情况之一:
•如果两个字符串都没有更多字符,则它们是相等的。
•如果一个字符串没有其他字符,而另一个字符串有其他字符,则较短的字符串位于较长的字符串之前。
•如果两个字符串都有更多字符,并且下一个相应字符不同,则字母表中另一个字符前面的字符的字符串先于另一个字符串。
在该示例的第一个比较中,在处理了两个字符串的前三个字符之后,第一个字符串结束,而第二个字符串继续,因此第一个字符串在顺序中的第二个字符串之前。
在第二个比较中,第二个字符是不同的,“b”字母在“c”字母之前,因此整个字符串“ab”在字符串“ac”之前。
在第三次比较中,将大写“A”与小写“a”进行比较。 大写字母被定义为在小写字母之前,因此在这种情况下第一个字符串在第二个字母之前。
布尔表达式
布尔值可以与所谓的逻辑连接词组合:
let truth = true;
let falsity = false;
println!("{} {}", ! truth, ! falsity);
println!("{} {} {} {}", falsity && falsity, falsity && truth,
truth && falsity, truth && truth);
println!("{} {} {} {}", falsity || falsity, falsity || truth,
truth || falsity, truth || truth);
这将打印:
false true
false false false true
false true true true
那些懂C语言的人不会发现什么新东西。
运算符“!”,读作“not”,为false参数生成一个真值,为true参数生成一个false值。
运算符“&&”,读作“logical-and”,如果两个参数都为真,则产生一个真值,而在其他情况下产生一个假值。
如果运算符“||”读取为“logical-or”,则如果其参数均为false,则生成false值,在其他情况下为true值。
逻辑连接词没有相同的优先级:
print!("{} {}", true || true && ! true,
(true || true) && ! true);
这将打印:“true false”。
操作符“!” 具有最高优先级,并将第一个表达式转换为true || 真假; 那么“&&”运算符的优先级高于“||” 运算符,接下来将对其进行求值,并将该表达式转换为true || 假; 最后是“||” 运算符被计算,并将该表达式转换为true。
如果您需要不同的评估顺序,可以使用括号,它实际上会更改第二个表达式的值,首先是true && false,然后是false。
操作类型一致性
该程序
let mut n = 1;
print!("{}", n);
n = 2;
print!(" {}", n);
n = 3;
print!(" {}", n);
将打印1 2 3.但如果我们将第五行更改为
n = 3.14;
编译器将报告值3.14的类型错误。 实际上,第一行创建一个变量,使用整数进行初始化,其类型为“整数”; 第三行为这样的变量分配一个仍然是“整数”类型的值; 但第五行会为这个变量赋一个浮点数值。 这是不允许的,对于“整数”类型的变量,只能分配“整数”类型的值。
通常,在Rust中,每个变量都是在编译时定义的类型,并且每个表达式都有一个在编译时定义的值。
例如,以下所有表达式都是“整数”类型:12,12-3,12(7%5)。 以下表达式的类型为“浮点数”:12.,12. - 3.,12. - (7.%5.)。 表达式“hello”是“string”类型,表达式false和4> 3是“Boolean”类型。
可以从用于初始化这种变量的表达式的类型推导出变量的类型,或者如通常所说的那样推断出变量的类型。 仅包含该行的程序
let number;
如前所述,这是非法的; 但原因是编译错误消息,其中包含所需的文本类型注释,然后文本无法推断类型。
一旦编译器理解了哪个是变量的类型,对这个变量的所有赋值必须在“=”符号的右边具有这种类型的表达式。
另外,请注意这一点:
let number1 = 12;
let _number2 = number1;
这里,字面整数12的类型为“整数”,因此number1变量也是“整数”类型,因此出现在=符号右边第二行的初始化表达式属于该类型 ,因此变量_number2属于同一类型,因为它是由这样的表达式初始化的。
回到上一个我们尝试使用数字3.14的例子,有几种方法可以解决类型不匹配的问题。 一个是写:
let mut n = 1.;
print!("{}", n);
n = 2.;
print!(" {}", n);
n = 3.14;
print!(" {}", n);
在此代码中,只有浮点数,因此没有类型错误。
类型和可变性的变化
let mut n = 1;
print!("{}", n);
n = 2;
print!(" {}", n);
let n = 3.14;
print!(" {}", n);
在这种情况下,第一个语句声明类型为“可变整数”的变量“n”,并初始化它; 第三个语句改变了这个变量的值; 并且第五个语句重新声明变量“n”,并使用“浮点数”类型的表达式对其进行初始化,因此变量本身必须是该类型。
在某些编程语言中,这是不允许的。 相反,Rust允许它,因为重新声明不会覆盖现有变量,但它们总是创建新变量。
当然,在这样的声明之后,第一个变量不再可访问,但它没有被销毁。 我们说旧变量已经被新变量“遮蔽”了。
请注意,此最后一个声明创建了另一个类型的变量,并且具有不同的可变性,即,当在第一个语句中声明的变量是可变的时,在第五个语句中声明的变量不是。 因此,例如,以下代码在最后一个语句处生成编译错误:
let mut _n = 1;
_n = 2;
let _n = 3.14;
_n = 5.9;
由于重新声明引入了一个隐藏第一个变量的新变量,因此这个变量可以是任何类型:
let x = 120; print!("{} ", x);
let x = "abcd"; print!("{} ", x);
let mut x = true; print!("{} ", x);
x = false; print!("{}", x);
这将打印120 abcd true false。
赋值算术运算符
在Rust中,通常需要编写这样的代码:
let mut a = 12;
a = a + 1;
a = a - 4;
a = a * 7;
a = a / 6;
print!("{}", a);
这将打印10.与C语言类似,此类表达式可以通过以下方式缩写:
let mut a = 12;
a += 1;
a -= 4;
a *= 7;
a /= 6;
print!("{}", a);
实际上,那些不仅是缩写,它们是不同的运算符,然而它们的行为类似于算术运算符,后面是赋值; 也就是说,例如,运算符+ =等效于首先执行加法,然后分配结果总和。
使用标准库的功能
作为任何编程语言,即使Rust只使用该语言的内置功能也很少,并且其大多数功能都委托给外部库。
除了使用可下载的库之外,每个Rust安装都提供了一个官方库,即所谓的“标准库”。 与C语言不同,C语言需要使用#include指令在源代码中包含标准库所需的文件,默认情况下,Rust包含其整个标准库,应用程序代码可以立即使用标准库的功能 库,无需包含外部模块:
print!("{} {}", str::len("abcde"), "abcde".len());
这将打印5 5。
这里调用或调用函数或例程。 它的名字是len,缩写为“length”,它是标准库的一部分。
在Rust中,要调用许多函数(包括len函数),有两种可能的语法形式,上面的示例显示了它们。
len函数返回作为参数传递的字符串中包含的字节数。
第一种语法形式具有过程风格,而第二种语法形式具有面向对象的风格。
在第一种形式中,首先指定包含该函数的模块的名称,在这种情况下,它是str,其中定义了字符串操作函数; 然后,指定函数的名称,用几个冒号字符(::)分隔; 然后,指定可能的参数,括在括号中。
在第二种形式中,首先,指定第一个参数; 然后,函数的名称,用点分隔; 最后,括在括号中的可能的其他参数。
即使没有参数,也需要括号,例如C语言。