基础类型之间的转换
隐式转换
隐式类型转换是在某些情况下由编译器自动应用的,例如在赋值、传递函数参数和应用运算符时。一般来说,当值类型之间的隐式转换是语义上合理的且没有信息丢失时,才会发生。
例如,uint8
可以转换为 uint16
,int128
可以转换为 int256
,但 int8
不能转换为 uint256
,因为 uint256
无法容纳像 -1
这样的负值。
如果对不同类型的操作数应用运算符,编译器会尝试将其中一个操作数隐式转换为另一个操作数的类型(赋值时也是如此)。这意味着运算总是在其中一个操作数的类型下执行。
关于哪些隐式转换是可能的,更多的细节请参阅相关类型的章节。
以下是一个例子,y
和 z
是加法的操作数,它们的类型不同,但 uint8
可以隐式转换为 uint16
,而 uint16
不能隐式转换为 uint8
。因此,在加法执行之前,y
会被转换为 z
的类型(即 uint16
)。表达式 y + z
的结果类型是 uint16
。由于它被赋值给类型为 uint32
的变量,因此在加法后还会进行一次隐式转换。
uint8 y;
uint16 z;
uint32 x = y + z;
显式转换
如果编译器不允许隐式转换,但你确信转换是可行的,有时可以使用显式类型转换。此操作可能会导致意外行为,并且允许绕过编译器的一些安全特性,因此必须确保结果符合你的预期,并进行充分的测试!
以下是一个将负数 int
转换为 uint
的例子:
int y = -3;
uint x = uint(y);
在此代码段的末尾,x
将具有值 0xfffff..fd
(64个十六进制字符),它表示 -3
在 256 位二进制补码表示中的值。
如果将整数显式转换为较小的类型,则会截断高位:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b 现在将是 0x5678
如果将整数显式转换为较大的类型,则会在左侧进行填充(即填充到高位端)。转换后的结果将与原始整数相等:
uint16 a = 0x1234;
uint32 b = uint32(a); // b 现在将是 0x00001234
assert(a == b);
固定大小的字节类型在转换时的行为不同。它们可以被看作是单个字节的序列,转换为较小类型时会截断该序列:
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
bytes
数组和 bytes calldata
切片可以显式转换为固定字节类型(bytes1
到 bytes32
)。如果数组的长度大于目标固定字节类型,将会在末尾进行截断。如果数组的长度小于目标类型,则会在末尾用零填充。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;
contract C {
bytes s = "abcdefgh";
function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) {
require(c.length == 16, "");
bytes16 b = bytes16(m); // 如果 m 的长度大于 16,将发生截断
b = bytes16(s); // 右侧填充零,结果为 "abcdefgh\0\0\0\0\0\0\0\0"
bytes3 b1 = bytes3(s); // 截断,b1 等于 "abc"
b = bytes16(c[:8]); // 也用零填充
return (b, b1);
}
}
关键点:
1.显式转换可以绕过编译器的安全检查,因此需要谨慎使用。
2.当转换为较小类型时,高位会被截断;当转换为较大类型时,低位会被填充零。
3.固定大小字节数组在转换时会根据目标类型的大小进行截断或填充。