《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)
4.1 运 算 符
前面已经学习了变量和常量,本节开始对它们进行操作,这就要用到Rust的操作符(Operator)。操作符通常是由一个或多个特殊的符号组成的(也有非特殊符号的操作符,如as),比如+、−、*、/、%、&、*等。每个操作符都代表一种动作(或操作),这种动作作用于操作数之上。简单来说,就是对操作数执行某种操作,然后返回操作后得到的结果。比如,加法操作3 + 2,这里的+是操作符,加号两边的3和2是操作数,加法符号的作用是对操作数3加上操作数2,得到计算结果5并返回5。
有些语言,很多操作符都是关键字,比如add、equals等。Rust的操作符主要是由符号组成的,比如+、−等。这些符号不在字母表中,但是在所有键盘上都可以找到。这个特点使得Rust程序更简洁,也更国际化。运算符也称操作符。运算符是Rust语言的基础,所以非常重要。
4.1.1 赋值运算符
赋值运算符的功能是将一个值赋给一个变量。比如:
a = 5;
以上代码将整数5赋给变量a。= 运算符左边的部分叫作左值(lvalue,left value),右边的部分叫作右值(rvalue,right value)。左值必须是一个变量,而右值可以是一个常量、一个变量、一个运算的结果,或者是前面几项的任意组合。
有必要强调赋值运算符永远是将右边的值赋给左边,不会反过来。比如:
a = b;
以上代码将变量b的值赋给变量a,不论赋值前a存储的是什么值,这行代码执行后,a的值就和b的值一样了。但要注意,我们只是将b的值赋给a,以后如果b的值改变了,并不会影响a的值。下面来看实例。
【例4.1】 赋值运算符的使用
在命令行下用命令cargo new myrust新建一个Rust项目,项目名是myrust。
打开VS Code,再打开文件夹myrust,然后在VS Code中打开src下的main.rs,输入如下 代码:
fn main() {
let mut a:i32;
let mut b:i32; //此时a、b的值未知
a = 10; // a:10,b未知
b = 4; // a:10,b:4
a = b; // a:4,b:4
b = 7; // a:4,b:7
println!("{},{}",a,b);
}
以上代码的结果是,a的值为4,b的值为7。最后一行中b的值被改变并不会影响a,虽然在此之前我们声明了a = b;(从右到左规则,right-to-left rule)。
运行结果如下:
4,7
4.1.2 数学运算符
Rust语言支持5种数学运算符,分别为加(+)、减(−)、乘(*)、除(/)、取模(%),括号里的符号就是数学运算符号。加减乘除运算想必大家都很了解,它们和一般的数学运算符没有区别。
唯一你可能不太熟悉的是用百分号(%)表示的取模运算(Module)。取模运算是取两个整数相除的余数。例如,如果我们写a = 11 % 3;,变量a的值将会为2,因为2是11除以3的余数。比如:
fn main() {
let mut a:i32;
let mut b:i32;
let mut c:i32;
a = 11 % 3; // 取模运算得a为2
b = 4+a; //加法运算得b为6
c =(a+b)/2; //除法运算得c为4
println!("{},{},{}",a,b,c);
}
输出结果:
2,6,4
4.1.3 组合运算符
Rust以书写简练著称,其一大特色就是这些组合运算符(+=、−=、*=、/=及其他),这些运算符使得只用一个基本运算符就可以改写变量的值:
value += increase; 等同于 value = value + increase;
比如:
- a −= 5; 等同于 a = a − 5;。
- a /= b; 等同于 a = a / b;。
- price *= units + 1; 等同于price = price * (units + 1);。
其他运算符以此类推。下面来看一个组合运算符的例子,代码如下:
fn main() {
let mut a:i32;
let mut b:i32;
let mut c:i32;
a = 11 % 3; // a:2
b = 4+a; // b:6
c =(a+b)/2; //c:3
a+=c;
b*=a;
c/=2;
println!("{},{},{}",a,b,c);
}
结果输出:
6,6,4
值得庆幸的是,Rust 语言不支持自增运算符(++)和自减运算符(--),因此本节绝对不会出现类似于a+++++i这样让人血压升高的语句。其实,编程语言由于是给人用的,一定要考虑到人的局限性(就是面对复杂事物容易出错),所以编程语言一定要简单明了,Rust去掉了++和--,相对于C语言而言,绝对是个进步,可以从源头上尽可能防止人类出错。
4.1.4 关系运算符
我们用关系运算符来比较两个表达式,关系运算的结果是一个布尔值,即它的值只能是true或false。例如,我们想通过比较两个表达式来看它们是否相等,或一个值是否比另一个值大。表4-1所示为Rust的关系运算符。
表4-1 Rust的关系运算符
|-------|----------------------------------|
| 关系运算符 | 说 明 |
| == | 如果左右值相等,则运算符结果是true,否则是false |
| != | 如果左右值不相等,则运算符结果是true,否则是false |
| > | 如果左值大于右值,则运算符结果是true,否则是false |
| < | 如果左值小于右值,则运算符结果是true,否则是false |
| >= | 如果左值大于或等于右值,则运算符结果是true,否则是false |
| <= | 如果左值小于或等于右值,则运算符结果是true,否则是false |
示例代码如下:
fn main() {
let mut a:bool;
let mut b:bool;
let mut c:bool;
a=(7!=5);
b = (100<=99);
c=(6==6);
println!("{},{},{}",a,b,c);
}
运行结果:
rue,false,true
除使用数字常量外,我们也可以使用任何有效表达式,包括变量。比如下列代码:
fn main() {
let mut a:i32;
let mut b:i32;
let mut c:i32;
a=2;
b=3;
c=6;
println!("{},{},{}",(a == 5),(a*b >= c),(b+4 > a*c));
}
输出结果:false,true,false。(a*b >= c)返回true是因为它实际是(2*3 >= 6),(b+4 > a*c)返回false因为它实际是(3+4 > 2*6)。
值得注意的是,运算符=(单个等号)不同于运算符==(两个等号),前者是赋值运算符(将等号右边的表达式值赋给左边的变量);后者(==)是一个判断等于的关系运算符,用来判断运算符两边的表达式是否相等。
4.1.5 逻辑运算符
运算符!等同于boolean运算NOT(取非),它只有一个操作数(Operand),写在它的右边。它做的唯一工作就是取该操作数的反面值,也就是说如果操作数值为真(true),那么运算后值变为假(false),如果操作数值为假(false),则运算结果为真(true)。它就好像是取与操作数相反的值。例如:
- !(5 == 5)返回false,因为它右边的表达式(5 == 5)为真(true)。
- !(6 <= 4)返回true,因为(6 <= 4)为假(false)。
- !true返回假(false)。
- !false返回真(true)。
大家如果不信,可以用下列代码直接输出看看结果:
println!("{},{},{},{}",!(5 == 5),!(6 <= 4),!true,!false);
逻辑运算符&&和||用来计算两个表达式而获得一个结果值。它们分别对应逻辑运算中的与运算(AND)和或运算(OR)。它们的运算结果取决于两个操作数的关系,如表4-2所示。
表4-2 两个操作数的逻辑运(&&和||)
|---------|---------|------------|------------|
| 第一个操作数a | 第二个操作数b | a && b结果 | a || b结果 |
| true | true | true | true |
| true | false | false | true |
| false | true | false | true |
| false | false | false | false |
例如:
- ( (5 == 5) && (3 > 6) )返回false ( true && false )。
- ( (5 == 5) || (3 > 6))返回true ( true || false )。
大家如果不信,可以用下列代码直接输出看看结果:
println!("{},{}",( (5 == 5) && (3 > 6) ) ,( (5 == 5) || (3 > 6)));
4.1.6 位运算符
位运算符以比特位改写变量存储的数值,也就是改写变量值的二进制表示。Rust的位运算符如表4-3所示。
表4-3 Rust的位运算符
|-----|-------|--------------------------|-----------------|
| 名 称 | 运 算 符 | 说 明 | 范 例 |
| 位与 | & | 若相同位都是1,则返回1;否则返回 0 | (A & B) 结果为2 |
| 位或 | | | 若相同位只有一个是1,则返回1;否则返回 0 | (A | B) 结果为3 |
| 异或 | ^ | 若相同位不相同,则返回1;否则返回 0 | (A ^ B) 结果为1 |
| 位非 | ! | 把位中的1换成0,0换成1 | (!B) 结果为−4 |
| 左移 | << | 操作数中的所有位向左移动指定位数,右边的位补 0 | (A << 1) 结果为4 |
| 右移 | >> | 操作数中的所有位向右移动指定位数,左边的位补 0 | (A >> 1) 结果为1 |
下面的范例演示上面提到的所有位运算符。
fn main() {
let a:i32 = 2; // 二进制表示为 0 0 0 0 0 0 1 0
let b:i32 = 3; // 二进制表示为 0 0 0 0 0 0 1 1
let mut result:i32;
result = a & b;
println!("(a & b) => {} ",result);
result = a | b;
println!("(a | b) => {} ",result) ;
result = a ^ b;
println!("(a ^ b) => {} ",result);
result = !b;
println!("(!b) => {} ",result);
result = a << b;
println!("(a << b) => {}",result);
result = a >> b;
println!("(a >> b) => {}",result);
}
输出结果如下:
(a & b) => 2
(a | b) => 3
(a ^ b) => 1
(!b) => -4
(a << b) => 16
(a >> b) => 0
4.1.7 变量类型转换运算符
变量类型转换运算符可以将一种类型的数据转换为另一种类型的数据。在Rust中,可以使用关键字as进行类型转换,as 运算符有点像C中的强制类型转换,区别在于,它只能用于原始类型(i32、i64、f32、f64、u8、u32、char等类型),并且它是安全的。注意,不同的数值类型是不能进行隐式转换的。比如:
let b: i64 = iNum; //iNum是一个i32类型的变量
会出现编译错误,提示无法进行类型转换。这时可以使用as 进行转换,比如:
fn main() {
let mut iNum:i32;
let mut b:i64;
iNum=100;
b = iNum as i64;
print!("{}",b);
}
输出结果:100。
为什么as是安全的?尝试以下代码:
b = iNum as char;
编译器报错:
error[E0604]: only `u8` can be cast as `char`, not `i32`
可见在不相关的类型之间,Rust 会拒绝转换,这样避免了运行时错误。
4.1.8 运算符的优先级
当多个操作数组成复杂的表达式时,我们可能会疑惑哪个运算先被计算,哪个后被计算。例如以下表达式:
a = 5 + 7 % 2
我们可以怀疑它实际上表示:a = 5 + (7 % 2) 结果为6,还是 a = (5 + 7) % 2 结果为0?
正确答案为第一个,结果为6。每一个运算符都有一个固定的优先级,不仅是数学运算符(我们可能在学习数学的时候已经很了解它们的优先顺序了),所有在Rust中出现的运算符都有优先级。从最高级到最低级,运算符的优先级按表4-4排列。
表4-4 运算符的优先级
|-------|-----------------------------------------|------|
| 优 先 级 | 操 作 符 | 结合方向 |
| 1 | 一元操作符(!、&、&mut) | 从左到右 |
| 2 | 二元操作符(*、/、%) | 从左到右 |
| 3 | 二元操作符(+、−) | 从左到右 |
| 4 | 位移计算(<<、>>) | 从左到右 |
| 5 | 位操作(&) | 从左到右 |
| 6 | 位操作(|) | 从左到右 |
| 7 | 比较操作(==、!=、<、>、<=、>=) | 需要括号 |
| 8 | 逻辑与(&&) | 从左到右 |
| 9 | 逻辑或(||) | 从左到右 |
| 10 | 赋值操作(=、+=、−=、/=、%=、|=、^=、<<=、>>=) | 从右到左 |
以下是简单的示例:
fn main() {
//二元计算操作
println!("1 + 2 = {}", 1u32 + 2);
println!("1 - 2 = {}", 1i32 - 2);
//逻辑操作
println!("true AND false is {}", true && false);
println!("true OR false is {}", true || false);
println!("NOT true is {}", !true);
//位运算操作
println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);
println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);
println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101);
println!("1 << 5 is {}", 1u32 << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);
}
运行结果如下:
1 + 2 = 3
1 - 2 = -1
true AND false is false
true OR false is true
NOT true is false
0011 AND 0101 is 0001
0011 OR 0101 is 0111
0011 XOR 0101 is 0110
1 << 5 is 32
0x80 >> 2 is 0x20
所有这些运算符的优先级顺序可以通过使用一对圆括号"()"来控制,而且更易读懂,示例如下:
a = 5 + 7 % 2;
根据我们想要实现的计算不同,可以写成:
a = 5 + (7 % 2);
效果和a = 5 + 7 % 2;一样,因为%的优先级比+高,所以加不加括号没什么区别。如果要先计算5+7,则可以这样:
a = (5 + 7) % 2;
此时最终计算结果就不同了。所以如果想写一个复杂的表达式而不敢肯定各个运算的执行顺序,那么就加上括号。这样可以使代码更易读懂。