【区块链安全 | 第二十篇】类型之运算符

在这里插入图片描述

运算符

算术和位运算符在两个操作数类型不同时也可以使用。我们可以编写表达式 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 产生相同的值变化,但返回的是变更后的值。

删除运算符

  • delete a 会将变量 a 重置为其类型的默认初始值。例如,对于整数,它等价于 a = 0。它也可用于数组,此时会将数组长度设置为 0,或将静态数组的所有元素重置为初始值。

  • delete a[x] 会删除数组中索引为 x 的元素,保留其他元素和数组的原始长度。这意味着数组中会留下一个空位。如果你需要频繁删除元素,使用映射(mapping)可能是更合适的选择。

对于结构体,delete a 会将该结构体的所有成员重置。换句话说,执行 delete a 后的结果相当于该结构体被声明但尚未赋值。请注意:delete 对映射类型无效(因为映射的键可能是任意值且通常未知)。因此,删除一个结构体时会重置其所有非映射成员,并递归地重置成员值,除非某个成员本身是映射类型。不过,你可以使用 delete a[x] 来删除映射中某个特定键的值。

需要特别注意的是,delete a 实质上相当于对 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逗号运算符,
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值