操作符
即使两个操作数的类型不一样,也可以进行算术和位操作运算。 例如,你可以计算 y = x + z ,其中 x 是 uint8 , z 是 uint32 类型。 在这些情况下,将使用以下机制来确定运算结果的类型(这在溢出的情况下很重要)。
- 如果右操作数的类型可以隐含地转换为左操作数的类型的类型,则使用左操作数的类型。
- 如果左操作数的类型可以隐含地转换为右操作数的类型的类型,则使用右操作数的类型。
- 否则,该操作不被允许。
如果其中一个操作数是一个 常量数字,会首先被转换为能容纳该值的最小的类型 (相同位数时,无符号类型被认为比有符号类型 “小”)。 如果两者都是常量数字,则以任意的精度进行计算。
操作符的结果类型与执行操作的类型相同,除了比较运算符,其结果总是 bool。
运算符 **, << 和 >> 使用左边操作数的类型来作为运算结果类型。
三元运算符
三元运算符是一个表达是形式: <expression> ? <trueExpression> : <falseExpression> 。 它根据 <expression> 的执行结果,选择后两个给定表达式中的一个。 如果 <expression> 执行结果 true ,那么 <trueExpression> 将被执行,否则 <falseExpression> 被执行。
复合操作及自增自减操作
a += e 等同于 a = a + e。其它运算符如 -=, *=, /=, %=, |=, &= , ^= , <<= 和 >>= 都是如此定义的。 a++ 和 a-- 分别等同于 a += 1 和 a -= 1,但表达式本身的值等于 a 在计算之前的值。 与之相反, --a 和 ++a 虽然最终 a 的结果与之前的表达式相同,但表达式的返回值是计算之后的值。
delete
delete a 的结果是将 a 类型初始值赋值给 a。即对于整型变量来说,相当于 a = 0,delete 也适用于数组,对于动态数组来说,是将重置为数组长度为0的数组,而对于静态数组来说,是将数组中的所有元素重置为初始值。对数组而言, delete a[x] 仅删除数组索引 x 处的元素,其他的元素和长度不变,这以为着数组中留出了一个空位。如果打算删除项,映射可能是更好的选择。
如果对象 a 是结构体,则将结构体中的所有属性(成员)重置。
换句话说,在 delete a 之后 a 的值与在没有赋值的情况下声明 a 的情况相同
类型转换
隐式转换
在某些情况下,编译器会自动进行隐式类型转换, 这些情况包括: 在赋值, 参数传递给函数以及应用运算符时。 通常,如果可以进行值类型之间的隐式转换, 并且不会丢失任何信息。 都是可以隐式类型转换
例如, uint8 可以转换成 uint16, int128 转换成 int256,但 int8 不能转换成 uint256 (因为 uint256 不能涵盖某些值,例如, -1)。
如果将运算符应用于不同的类型,则编译器将尝试将其中一个操作数隐式转换为另一个操作数的类型(赋值也是如此)。 这意味着操作始终以操作数之一的类型执行。
在下面的示例中,加法的操作数 y 和 z 没有相同的类型,但是 uint8 可以被隐式转换为 uint16,相反却不可以。 因此, 在执行加法之前,将 y 转换为 z 的类型, 在 uint16 类型中。 表达式 y + z 的结果类型是 uint16 。 在执行加法之后。 因为它被赋值给 uint32 类型的变量,又进行了另一个隐式转换.
uint8 y;
uint16 z;
uint32 x = y + z;
显式转换
如果某些情况下编译器不支持隐式转换,但是你很清楚你要做的结果,这种情况可以考虑显式转换。 注意这可能会发生一些无法预料的后果,因此一定要进行测试,确保结果是你想要的! 下面的示例是将一个 int8 类型的负数转换成 uint:
int8 y = -3;
uint x = uint(y);
这段代码的最后, x 的值将是 0xfffff..fd (64 个 16 进制字符),因为这是 -3 的 256 位补码形式。
如果一个类型显式转换成更小的类型,相应的高位将被舍弃:
uint32 a = 0x12345678;
uint16 b = uint16(a); // 此时 b 的值是 0x5678
定长字节数组转换则有所不同, 他们可以被认为是单个字节的序列和转换为较小的类型将切断序列:
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b 为 0x12
如果将定长字节数组显式转换为更大的类型,将按正确的方式填充。 以固定索引访问转换后的字节将在和之前的值相等 (如果索引仍然在范围内):
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b 为 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
因为整数和定长字节数组在截断(或填充)时行为是不同的, 如果整数和定长字节数组有相同的大小,则允许他们之间进行显式转换, 如果要在不同的大小的整数和定长字节数组之间进行转换 ,必须使用一个中间类型来明确进行所需截断和填充的规则:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b 为 0x00001234
uint32 c = uint32(bytes4(a)); // c 为 0x12340000
uint8 d = uint8(uint16(a)); // d 为 0x34
uint8 e = uint8(bytes1(a)); // e 为 0x12
字面常量与基本类型的转换
整型与字面常量转换
十进制和十六进制字面常量可以隐式转换为任何足以表示它而不会截断的整数类型 :
uint8 a = 12; // 可行
uint32 b = 1234; // 可行
uint16 c = 0x123456; // 失败, 会截断为 0x3456
定长字节数组与字面常量转换
十进制字面常量不能隐式转换为定长字节数组。十六进制字面常量可以是,但仅当十六进制数字大小完全符合定长字节数组长度。 不过零值例外,零的十进制和十六进制字面常量都可以转换为任何定长字节数组类型:
bytes2 a = 54321; // 不可行
bytes2 b = 0x12; // 不可行
bytes2 c = 0x123; // 不可行
bytes2 d = 0x1234; // 可行
bytes2 e = 0x0012; // 可行
bytes4 f = 0; // 可行
bytes4 g = 0x0; // 可行
字符串字面常量和十六进制字符串字面常量可以隐式转换为定长字节数组,如果它们的字符数与字节类型的大小相匹配:
bytes2 a = hex"1234"; // 可行
bytes2 b = "xy"; // 可行
bytes2 c = hex"12"; // 不可行
bytes2 d = hex"123"; // n不可行
bytes2 e = "x"; // 不可行
bytes2 f = "xyz"; // 不可行
地址类型
通过校验和测试的正确大小的十六进制字面常量会作为 address 类型。
只有 bytes20 和 uint160 允许显式转换为 address 类型
从 bytes20 或其他整型显示转换为 address 类型时,都会作为 address payable 类型。
一个地址 address a 可以通过 payable(a) 转换为 address payable 类型.