文章目录
运算符
算术和位运算符即使在两个操作数类型不同的情况下也可以应用。例如,你可以计算 y = x + z
,其中 x
是 uint8
类型,z
是 uint32
类型。在这种情况下,将使用以下机制来确定运算执行的类型(这对于溢出非常重要)以及运算符结果的类型:
- 如果右操作数的类型可以隐式转换为左操作数的类型,则使用左操作数的类型。
- 如果左操作数的类型可以隐式转换为右操作数的类型,则使用右操作数的类型。
- 否则,不允许进行运算。
如果其中一个操作数是字面量数字,它首先会转换为其“移动类型”,即可以容纳该值的最小类型(相同位宽的无符号类型被认为比有符号类型更小)。如果两个操作数都是字面量数字,则运算会使用实际所需的无限精度进行计算,以确保在结果与非字面量类型一起使用时不会丢失精度。
运算符的结果类型与执行运算的类型相同,比较运算符的结果类型总是 bool
。
运算符 **
(指数运算)、<<
和 >>
使用左操作数的类型进行运算和返回结果。
三元运算符
三元运算符用于形如 <表达式> ? <真表达式> : <假表达式>
的表达式。根据主表达式的结果,计算后两个表达式之一。如果 <表达式>
计算为真,则会计算 <真表达式>
,否则计算 <假表达式>
。
三元运算符的结果类型不像其操作数那样是有理数类型,即使所有操作数都是有理数字面量。结果类型根据两个操作数的类型确定,首先如果需要则转换为它们的“移动类型”。
因此,像 255 + (true ? 1 : 0)
这样的表达式会由于算术溢出而回退。原因是 (true ? 1 : 0)
是 uint8
类型,这会导致加法也在 uint8
类型中进行,而 256 超出了该类型的范围。
另一个结果是像 1.5 + 1.5
这样的表达式是有效的,但 1.5 + (true ? 1.5 : 2.5)
是无效的。这是因为前者是一个有理数表达式,在无限精度下计算,只有最终值很重要。而后者则涉及将一个有理数的分数转换为整数,这是当前不允许的。
复合运算符和增量/减量运算符
如果 a
是左值(即变量或可以被赋值的东西),以下运算符可作为简写形式使用:
a += e
等价于a = a + e
。运算符-=
、*=
、/=
、%=
、|=
、&=
、^=
、<<=
和>>=
按照类似的规则定义。a++
和a--
等价于a += 1
/a -= 1
,但表达式本身仍然使用a
的旧值。相比之下,--a
和++a
对a
的影响相同,但会返回变更后的值。
删除运算符
delete a
会将a
的值重置为该类型的初始值。即,对于整数,它等价于a = 0
,但它也可以用于数组,这时会将数组的长度设为 0,或者将静态数组的所有元素设置为初始值。delete a[x]
会删除数组中索引为x
的项,并保持其他元素和数组的长度不变。这意味着数组会留下一个空隙。如果你打算删除项,使用映射(mapping)可能是更好的选择。
对于结构体,delete a
会将结构体所有成员重置。换句话说,delete a
后的 a
值相当于 a
被声明但未赋值,要注意:delete
对映射没有影响(因为映射的键可能是任意的,一般是未知的)。因此,如果删除一个结构体,它会重置所有非映射的成员,并且会递归地重置成员,除非成员是映射类型。不过,映射中的单个键及其值可以被删除:如果 a
是一个映射,delete a[x]
会删除 x
索引处存储的值。
需要注意的是,delete a
实际上像是对 a
进行赋值操作,即它会把一个新对象存储到 a
中。这种区别在 a
是引用变量时尤为明显:它只会重置 a
本身,而不会影响它之前引用的值。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract DeleteExample {
uint data; // 定义一个 uint 类型的变量 data
uint[] dataArray; // 定义一个 uint 数组 dataArray
function f() public {
uint x = data; // 将 data 的值赋给 x
delete x; // 将 x 设置为 0,删除 x 对变量 data 的影响。x 是一个局部变量,不会影响 data
delete data; // 将 data 设置为 0,删除操作仅影响 data,不会影响 x(因为 x 是局部变量)
uint[] storage y = dataArray; // 声明一个引用变量 y 指向 dataArray 的存储
delete dataArray; // 删除 dataArray,结果是 dataArray 的长度被设置为 0,并且由于 y 是引用类型,y 也会被影响
// 对于引用类型的变量,delete 会影响存储对象本身,删除数组的所有元素
// 但注意,"delete y" 是无效的,因为不能对引用类型的局部变量执行删除操作
assert(y.length == 0); // 断言 dataArray 的长度已变为 0,验证删除操作生效
}
}
运算符的优先级顺序
以下是运算符的优先级顺序,按求值顺序列出。
优先级 | 描述 | 运算符 |
---|---|---|
1 | 后缀增量和减量 | ++ , -- |
新表达式 | new <typename> |
|
数组下标 | <array>[<index>] |
|
成员访问 | <object>.<member> |
|
类似函数的调用 | <func>(<args...>) |
|
括号 | (<statement>) |
|
2 | 前缀增量和减量 | ++ , -- |
一元负号 | - |
|
一元运算符 | delete |
|
逻辑非 | ! |
|
按位非 | ~ |
|
3 | 指数运算 | ** |
4 | 乘法、除法和取余 | * , / , % |
5 | 加法和减法 | + , - |
6 | 按位移位运算符 | << , >> |
7 | 按位与运算符 | & |
8 | 按位异或运算符 | ^ |
9 | 按位或运算符 | ` |
10 | 不等式运算符 | < , > , <= , >= |
11 | 等式运算符 | == , != |
12 | 逻辑与运算符 | && |
13 | 逻辑或运算符 | ` |
14 | 三元运算符 | <conditional> ? <if-true> : <if-false> |
赋值运算符 | = , ` |
|
15 | 逗号运算符 | , |