做封面模板下载网站,Wordpress 防注入代码,公司建网站空间,权威的赣州网站建设Solidity 是一种静态类型语言#xff0c;这意味着每个变量#xff08;状态变量和局部变量#xff09;都需要在编译时指定变量的类型。
“undefined”或“null”值的概念在Solidity中不存在#xff0c;但是新声明的变量总是有一个 默认值 #xff0c;具体的默认值跟类型相…Solidity 是一种静态类型语言这意味着每个变量状态变量和局部变量都需要在编译时指定变量的类型。
“undefined”或“null”值的概念在Solidity中不存在但是新声明的变量总是有一个 默认值 具体的默认值跟类型相关。 要处理任何意外的值应该使用 错误处理 来恢复整个交易或者返回一个带有第二个 bool 值的元组表示成功。
值类型
以下类型也称为值类型因为这些类型的变量将始终按值来传递。 也就是说当这些变量被用作函数参数或者用在赋值语句中时总会进行值拷贝。
布尔类型
bool 可能的取值为字面常量值 true 和 false
运算符
! 逻辑非逻辑与 “and”
|| 逻辑或 “or” 等于
! 不等于整型
int / uint 分别表示有符号和无符号的不同位数的整型变量。 支持关键字 uint8 到 uint256 无符号从 8 位到 256 位以及 int8 到 int256以 8 位为步长递增。 uint 和 int 分别是 uint256 和 int256 的别名。
运算符
比较运算符 ! 返回布尔值
位运算符 | ^ 异或 ~ 位取反
移位运算符 左移位 右移位
算数运算符 - 一元运算负 - 仅针对有符号整型 * / % 取余或叫模运算 ** 幂对于整形 X可以使用 type(X).min 和 type(X).max 去获取这个类型的最小值与最大值。 0.8.0 开始算术运算有两个计算模式一个是 “wrapping”截断模式或称 “unchecked”不检查模式一个是”checked” 检查模式。 默认情况下算术运算在 “checked” 模式下即都会进行溢出检查如果结果落在取值范围之外调用会通过 失败异常 回退。 你也可以通过 unchecked { … } 切换到 “unchecked”模式 比较运算
位运算
位运算在数字的二进制补码表示上执行。 这意味着 ~int2560 int256-1
移位
移位操作的结果具有左操作数的类型同时会截断结果以匹配类型。 右操作数必须是无符号类型。 尝试按带符号的类型移动将产生编译错误。 x y 等于数学表达式 x * 2 ** y。x y 等于数学表达式 x / 2 ** y 四舍五入到负无穷。对于移位操作不会像算术运算那样执行溢出检查其结果总是被截断。
加、减、乘法运算
表达式 -x 相当于 (T(0) - x) 这里 T 是指 x 的类型。 -x 只能应用在有符号型的整数上。 如果 x 为负数 -x 为正数。 由于使用两进制补码表示数据你还需要小心:
如果有 int x type(int).min; 那 -x 将不在正数取值的范围内。 这意味着这个检测 unchecked { assert(-x x); } 是可以通过的即这种情况下不能假设它的负数会是正数如果是 checked 模式则会触发异常。
除法运算
除法运算结果的类型始终是其中一个操作数的类型整数除法总是产生整数。 在Solidity中分数会取零。 这意味着 int256(-5) / int256(2) int256(-2) 。 除以0 会发生 Panic 错误 而且这个检查不可以通过 unchecked { … } 禁用掉。 模运算取余
模运算 an 是在操作数 a 的除以 n 之后产生余数 r 其中 q int(a / n) 和 r a - (n * q) 。 这意味着模运算结果与左操作数相同的符号相同或零。 对于 负数的a : a % n -(-a % n) 几个例子
int256(5) % int256(2) int256(1)
int256(5) % int256(-2) int256(1)
int256(-5) % int256(2) int256(-1)
int256(-5) % int256(-2) int256(-1)幂运算
幂运算仅适用于无符号类型。 结果的类型总是等于基数的类型. 请注意类型足够大以能够容纳幂运算的结果要么发生潜在的assert异常或者使用截断模式。
x**3 的例子表达式 xxx 也许更便宜。 在任何情况下都建议进行 gas 消耗测试和使用优化器。
定长浮点型
Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量但不能给它们赋值或把它们赋值给其他变量。。
地址类型 Address
地址类型有两种形式他们大致相同 address保存一个20字节的值以太坊地址的大小。address payable 可支付地址与 address 相同不过有成员函数 transfer 和 send 。这种区别背后的思想是 address payable 可以向其发送以太币而不能先一个普通的 address 发送以太币例如它可能是一个智能合约地址并且不支持接收以太币。
类型转换:
允许从 address payable 到 address 的隐式转换而从 address 到 address payable 必须显示的转换, 通过 payable(
) 进行转换。 地址类型成员变量
balance 和 transfer 成员
可以使用 balance 属性来查询一个地址的余额 也可以使用 transfer 函数向一个可支付地址payable address发送 以太币 以 wei 为单位
address x 0x123;
address myAddress this;
if (x.balance 10 myAddress.balance 10) x.transfer(10);如果当前合约的余额不够多则 transfer 函数会执行失败或者如果以太转移被接收帐户拒绝 transfer 函数同样会失败而进行回退。 如果 x 是一个合约地址它的代码更具体来说是, 如果有receive函数, 执行 receive 接收以太函数, 或者存在fallback函数,执行 Fallback 回退函数 函数会跟 transfer 函数调用一起执行这是 EVM 的一个特性无法阻止。 如果在执行过程中用光了 gas 或者因为任何原因执行失败以太币 交易会被打回当前的合约也会在终止的同时抛出异常。 send 成员
send 是 transfer 的低级版本。如果执行失败当前的合约不会因为异常而终止但 send 会返回 false。 在使用 send 的时候会有些风险如果调用栈深度是 1024 会导致发送失败这总是可以被调用者强制如果接收者用光了 gas 也会导致发送失败。 所以为了保证 以太币 发送的安全一定要检查 send 的返回值使用 transfer 或者更好的办法 使用接收者自己取回资金的模式 call delegatecall 和 staticcall
为了与不符合 应用二进制接口 的合约交互或者要更直接地控制编码提供了函数 calldelegatecall 和 staticcall 。 它们都带有一个 bytes memory 参数和返回执行成功状态bool和数据bytes memory。
函数 abi.encodeabi.encodePackedabi.encodeWithSelector 和 abi.encodeWithSignature 可用于编码结构化数据。
bytes memory payload abi.encodeWithSignature(register(string), MyName);
(bool success, bytes memory returnData) address(nameReg).call(payload);
require(success);
可以使用 gas 修改器 调整提供的 gas 数量
address(nameReg).call{gas: 1000000}(abi.encodeWithSignature(register(string), MyName));delegatecall 的目的是使用另一个合约中的库代码。 用户必须确保两个合约中的存储结构都适合委托调用 delegatecall。
从以太坊拜占庭byzantium版本开始 提供了 staticcall 它与 call 基本相同但如果被调用的函数以任何方式修改状态变量都将回退。
所有三个函数 call delegatecall 和 staticcall 都是非常低级的函数应该只把它们当作 最后一招 来使用因为它们破坏了 Solidity 的类型安全性。
所有三种方法都提供 gas 选项而 value 选项仅 call 支持 。
code 和 codehash 成员
你可以查询任何智能合约的部署代码。使用 .code 来获取EVM的字节码其返回 bytes memory 值可能是空。 使用 .codehash 获得该代码的 Keccak-256哈希值 (为 bytes32 )。注意 addr.codehash 比使用 keccak256(addr.code) 更便宜。 所有合约都可以转换为 address 类型因此可以使用 address(this).balance 查询当前合约的余额。 合约类型
每一个 contract 定义都有他自己的类型。
您可以隐式地将合约转换为从他们继承的合约。 合约可以显式转换为 address 类型。
只有当合约具有 接收receive函数 或 payable 回退函数时才能显式和 address payable 类型相互转换 转换仍然使用 address(x) 执行 如果合约类型没有接收或payable 回退功能则可以使用 payable(address(x)) 转换为 address payable 。
合约不支持任何运算符。
合约类型的成员是合约的外部函数及 public 的 状态变量。
对于合约 C 可以使用 type© 获取合约的类型信息
定长字节数组
关键字有bytes1 bytes2 bytes3 … bytes32。
运算符
比较运算符 ! 返回布尔型
位运算符 | ^ 按位异或 ~ 按位取反
移位运算符 左移位 右移位
索引访问如果 x 是 bytesI 类型那么 x[k] 其中 0 k I返回第 k 个字节只读。该类型可以和作为右操作数的无符号整数类型进行移位运算但返回结果的类型和左操作数类型相同右操作数表示需要移动的位数。 进行有符号整数位移运算会引发运行时异常
变长字节数组
bytes: 变长字节数组参见 数组。它并不是值类型。 string: 变长 UTF-8 编码字符串类型参见 数组。并不是值类型。
地址字面常量
比如像 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF 这样的通过了地址校验和测试的十六进制字面常量会作为 address 类型。 而没有通过校验测试, 长度在 39 到 41 个数字之间的十六进制字面常量会产生一个错误,您可以在零前面添加对于整数类型或在零后面添加对于bytesNN类型以消除错误。
字符串字面常量及类型
字符串字面常量是指由双引号或单引号引起来的字符串 “foo” 或者 ‘bar’。 它们也可以分为多个连续的部分 “foo” “bar” 等效于 “foobar”这在处理长字符串时很有用。 不像在 C 语言中那样带有结束符 “foo” 相当于 3 个字节而不是 4 个。 和整数字面常量一样字符串字面常量的类型也可以发生改变
但它们可以隐式地转换成 bytes1…… bytes32如果合适的话还可以转换成 bytes 以及 string。
例如 bytes32 samevar “stringliteral” 字符串字面常量在赋值给 bytes32 时被解释为原始的字节形式。
字符串字面常量支持下面的转义字符
\newline (转义实际换行)
\\ (反斜杠)
\ (单引号)
\ (双引号)
\b (退格)
\f (换页)
\n (换行符)
\r (回车)
\t (标签 tab)
\v (垂直标签)
\xNN (十六进制转义见下文)
\uNNNN (unicode 转义见下文)Unicode 字面常量
常规字符串文字只能包含ASCII而Unicode文字以关键字unicode为前缀可以包含任何有效的UTF-8序列。 它们还支持与转义序列完全相同的字符作为常规字符串文字。 string memory a unicodeHello ; 十六进制字面常量
十六进制字面常量以关键字 hex 打头后面紧跟着用单引号或双引号引起来的字符串例如hex001122FF 。 字符串的内容必须是一个十六进制的字符串它们的值将使用二进制表示。
枚举类型
枚举是在Solidity中创建用户定义类型的一种方法。 它们是显示所有整型相互转换但不允许隐式转换。 从整型显式转换枚举会在运行时检查整数时候在枚举范围内否则会导致异常 Panic异常 。 枚举需要至少一个成员,默认值是第一个成员枚举不能多于 256 个成员。
数据表示与C中的枚举相同选项从“0”开始的无符号整数值表示。
使用 type(NameOfEnum).min 和 type(NameOfEnum).max 你可以得到给定枚举的最小值和最大值。
用户定义的值类型
一个用户定义的值类型允许在一个基本的值类型上创建一个零成本的抽象。 这类似于一个别名但有更严格的类型要求。
用户定义值类型使用 type C is V 来定义其中 C 是新引入的类型的名称 V 必须是内置的值类型”底层类型”。 函数 C.wrap 被用来从底层类型转换到自定义类型。同样地函数函数 C.unwrap 用于从自定义类型转换到底层类型。 type UFixed256x18 is uint256; 函数类型
函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量也可以将一个函数作为参数进行传递还能在函数调用中返回函数类型变量。 函数类型有两类 - 内部internal 函数类型 - 外部external 函数类型。
内部函数只能在当前合约内被调用更具体来说在当前代码块内包括内部库函数和继承的函数中因为它们不能在当前合约上下文的外部被执行。 调用一个内部函数是通过跳转到它的入口标签来实现的就像在当前合约的内部调用一个函数。
外部函数由一个地址和一个函数签名组成可以通过外部函数调用传递或者返回。 function () {internal|external} [pure|constant|view|payable] [returns ()] 与参数类型相反返回类型不能为空 —— 如果函数类型不需要返回则需要删除整个 returns () 部分。
函数类型默认是内部函数因此不需要声明 internal 关键字。
类型转换
函数类型 A 可以隐式转换为函数类型 B 当且仅当: 它们的参数类型相同返回类型相同它们的内部/外部属性是相同的并且 A 的状态可变性比 B 的状态可变性更具限制性比如
pure 函数可以转换为 view 和 non-payable 函数
view 函数可以转换为 non-payable 函数
payable 函数可以转换为 non-payable 函数public或 external函数都有下面的成员
.address 返回函数的合约地址。
.selector 返回 ABI 函数选择器引用类型
引用类型可以通过多个不同的名称修改它的值
引用类型包括结构数组和映射如果使用引用类型则必须明确指明数据存储哪种类型的位置空间里
内存 即数据在内存中因此数据仅在其生命周期内函数调用期间有效。不能用于外部调用。
存储 状态变量保存的位置只要合约存在就一直存储
调用数据 用来保存函数参数的特殊数据位置是一个只读位置。数据位置
所有的引用类型如 数组 和 结构体 类型都有一个额外注解 数据位置 来说明数据存储位置。
有三种位置 内存memory 、 存储storage 以及 调用数据 calldata。
调用数据 是不可修改的、非持久的函数参数存储区域效果大多类似 内存 。 主要用于外部函数的参数但也可用于其他变量。
数据位置与赋值行为
在 存储 和 内存 之间两两赋值或者从 调用数据 赋值 都会创建一份独立的拷贝。
从 内存 到 内存 的赋值只创建引用 这意味着更改内存变量其他引用相同数据的所有其他内存变量的值也会跟着改变。
从 存储 到本地存储变量的赋值也只分配一个引用。
其他的向 存储 的赋值总是进行拷贝。 这种情况的示例如对状态变量或 存储 的结构体类型的局部变量成员的赋值即使局部变量本身是一个引用也会进行一份拷贝译者注查看下面 ArrayContract 合约 更容易理解。数组
数组可以在声明时指定长度也可以动态调整大小长度。
一个元素类型为 T固定长度为 k 的数组可以声明为 T[k]而动态数组声明为 T[]。
可以使用 .push() 方法在末尾追加一个新元素其中 .push() 追加一个零初始化的元素并返回对它的引用。
bytes 和 string 也是数组 bytes 和 string 类型的变量是特殊的数组。 bytes 类似于 bytes1[]但它在 调用数据 和 内存 中会被“紧打包”译者注将元素连续地存在一起不会按每 32 字节一单元的方式来存放。 string 与 bytes 相同但不允许用长度或索引来访问。
函数 bytes.concat 和 string.concat
可以使用 string.concat 连接任意数量的 string 字符串。 该函数返回一个 string memory 包含所有参数的内容无填充方式拼接在一起。 如果你想使用不能隐式转换为 string 的其他类型作为参数你需要先把它们转换为 string。
同样 bytes.concat 函数可以连接任意数量的 bytes 或 bytes1 … bytes32 值。 该函数返回一个 bytes memory 包含所有参数的内容无填充方式拼接在一起。
创建内存数组
可使用 new 关键字在 内存 中基于运行时创建动态长度数组。 与 存储 数组相反的是你 不能 通过修改成员变量 .push 改变 内存 数组的大小。
必须提前计算所需的大小或者创建一个新的内存数组并复制每个元素。
在Solidity中的所有变量新分配的数组元素总是以 默认值 初始化。
pragma solidity 0.4.16 0.9.0;contract TX {function f(uint len) public pure {uint[] memory a new uint[](7);bytes memory b new bytes(len);assert(a.length 7);assert(b.length len);a[6] 8;}
}数组常量
它总是一个静态大小的内存数组其长度为表达式的数量。
数组的基本类型是列表上的第一个表达式的类型。
[1, 2, 3] 的类型是 uint8[3] memory。 因为每个常量的类型都是 uint8 如果你希望结果是 uint[3] memory 类型你需要将第一个元素转换为 uint 。
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.16 0.9.0;contract LBC {function f() public pure {g([uint(1), 2, 3]);}function g(uint[3] memory) public pure {// ...}
}
数组常量 [1, -1] 是无效的因为第一个表达式类型是 uint8 而第二个类似是 int8 他们不可以隐式的相互转换。 为了确保可以运行你是可以使用例如 [int8(1), -1] 。
如果要初始化动态长度的数组则必须显示给各个元素赋值:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.0 0.9.0;contract C {function f() public pure {uint[] memory x new uint[](3);x[0] 1;x[1] 3;x[2] 4;}
}
数组成员
length: 数组有 length 成员变量表示当前数组的长度。 一经创建内存 数组的大小就是固定的但却是动态的也就是说它可以根据运行时的参数创建。push():动态的 存储 数组以及 bytes 类型 string 类型不可以都有一个 push() 的成员函数它用来添加新的零初始化元素到数组末尾并返回元素引用 因此可以这样 x.push().t 2 或 x.push() b.push(x):动态的 存储 数组以及 bytes 类型 string 类型不可以都有一个 push() 的成员函数用来在数组末尾添加一个给定的元素这个函数没有返回值pop():变长的 存储 数组以及 bytes 类型 string 类型不可以都有一个 pop() 的成员函数 它用来从数组末尾删除元素。 同样的会在移除的元素上隐含调用 delete 这个函数没有返回值。
对存储数组元素的悬空引用
当使用存储数组时你需要注意避免悬空的引用。 悬空引用是指一个指向不再存在的东西的引用或者是对象被移除而没有更新引用。 例如如果你将一个数组元素的引用存储在一个局部的引用中然后从包含数组中 .pop() 出来就会发生悬空引用。
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0 0.9.0;contract C {uint[][] s;function f() public {// 保存s最后一个元素的指向。uint[] storage ptr s[s.length - 1];// 移除 s 最后一个元素s.pop();// 向不再属于数组的元素写入数据ptr.push(0x42);// 现在添加元素到 s 不会添加一个空元素, 而是数组长度为 1 0x42 作为其元素。s.push();assert(s[s.length - 1][0] 0x42);}
}ptr.push(0x42) 写入 不 会回退, 尽管 ptr 不再指向有效的 s 元素 由于编译器假定未使用的存储空间总是被清零的 所以随后的 s.push() 不会明确地将零写入存储空间。 所以在 push() 之后 s 的最后一个元素的长度是 1 并且包含 0x42 作为第一个元素。 0x42作为其第一个元素。
数组切片
数组切片是数组连续部分的视图用法如x[start:end] start 和 end 是 uint256 类型或结果为 uint256 的表达式。 x[start:end] 的第一个元素是 x[start] 最后一个元素是 x[end - 1] 。
如果 start 比 end 大或者 end 比数组长度还大将会抛出异常。
start 和 end 都可以是可选的 start 默认是 0 而 end 默认是数组长度。
结构体
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.6.0 0.9.0;// 定义的新类型包含两个属性。
// 在合约外部声明结构体可以使其被多个合约共享。 在这里这并不是真正需要的。
struct Funder {address addr;uint amount;
}contract CrowdFunding {// 也可以在合约内部定义结构体这使得它们仅在此合约和衍生合约中可见。struct Campaign {address beneficiary;uint fundingGoal;uint numFunders;uint amount;mapping (uint Funder) funders;}
}映射
映射类型在声明时的形式为 mapping(KeyType ValueType)。 其中 KeyType 可以是任何基本类型即可以是任何的内建类型 bytes 和 string 或合约类型、枚举类型。 而其他用户定义的类型或复杂的类型如映射、结构体、即除 bytes 和 string 之外的数组类型是不可以作为 KeyType 的类型的。
可迭代映射
映射本身是无法遍历的即无法枚举所有的键。不过可以在它们之上实现一个数据结构来进行迭代。 例如以下代码实现了 IterableMapping 库然后 User 合约可以添加数据 sum 函数迭代求和所有值。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }struct itmap {mapping(uint IndexValue) data;KeyFlag[] keys;uint size;
}type Iterator is uint;library IterableMapping {function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {uint keyIndex self.data[key].keyIndex;self.data[key].value value;if (keyIndex 0)return true;else {keyIndex self.keys.length;self.keys.push();self.data[key].keyIndex keyIndex 1;self.keys[keyIndex].key key;self.size;return false;}}function remove(itmap storage self, uint key) internal returns (bool success) {uint keyIndex self.data[key].keyIndex;if (keyIndex 0)return false;delete self.data[key];self.keys[keyIndex - 1].deleted true;self.size --;}function contains(itmap storage self, uint key) internal view returns (bool) {return self.data[key].keyIndex 0;}function iterateStart(itmap storage self) internal view returns (Iterator) {return iteratorSkipDeleted(self, 0);}function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {return Iterator.unwrap(iterator) self.keys.length;}function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {return iteratorSkipDeleted(self, Iterator.unwrap(iterator) 1);}function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {uint keyIndex Iterator.unwrap(iterator);key self.keys[keyIndex].key;value self.data[key].value;}function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {while (keyIndex self.keys.length self.keys[keyIndex].deleted)keyIndex;return Iterator.wrap(keyIndex);}
}// 如何使用
contract User {// Just a struct holding our data.itmap data;// Apply library functions to the data type.using IterableMapping for itmap;// Insert somethingfunction insert(uint k, uint v) public returns (uint size) {// This calls IterableMapping.insert(data, k, v)data.insert(k, v);// We can still access members of the struct,// but we should take care not to mess with them.return data.size;}// Computes the sum of all stored data.function sum() public view returns (uint s) {for (Iterator i data.iterateStart();data.iterateValid(i);i data.iterateNext(i)) {(, uint value) data.iterateGet(i);s value;}}
}三元运算符
三元运算符是一个表达是形式 expression ? trueExpression : falseExpression
复合操作及自增自减操作
如果 a 是一个 LValue即一个变量或者其它可以被赋值的东西以下运算符都可以使用简写
a e 等同于 a a e。其它运算符如 - * / % | ^ 和 都是如此定义的。 a 和 a-- 分别等同于 a 1 和 a - 1但表达式本身的值等于 a 在计算之前的值。 与之相反 --a 和 a 虽然最终 a 的结果与之前的表达式相同但表达式的返回值是计算之后的值。
delete
delete a 的结果是将 a 类型初始值赋值给 a。即对于整型变量来说相当于 a 0delete 也适用于数组对于动态数组来说是将重置为数组长度为0的数组而对于静态数组来说是将数组中的所有元素重置为初始值。对数组而言 delete a[x] 仅删除数组索引 x 处的元素其他的元素和长度不变这以为着数组中留出了一个空位。如果打算删除项映射可能是更好的选择。
如果对象 a 是结构体则将结构体中的所有属性(成员)重置。
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.0 0.9.0;contract DeleteLBC {uint data;uint[] dataArray;function f() public {uint x data;delete x; // 将 x 设为 0并不影响数据delete data; // 将 data 设为 0并不影响 x因为它仍然有个副本uint[] storage y dataArray;delete dataArray;// 将 dataArray.length 设为 0但由于 uint[] 是一个复杂的对象y 也将受到影响// 因为它是一个存储位置是 storage 的对象的别名。// 另一方面delete y 是非法的引用了 storage 对象的局部变量只能由已有的 storage 对象赋值。assert(y.length 0);}
}显式转换
如果某些情况下编译器不支持隐式转换但是你很清楚你要做的结果这种情况可以考虑显式转换。 注意这可能会发生一些无法预料的后果因此一定要进行测试确保结果是你想要的 下面的示例是将一个 int8 类型的负数转换成 uint
int8 y -3;
uint x uint(y);这段代码的最后 x 的值将是 0xfffff…fd 64 个 16 进制字符因为这是 -3 的 256 位补码形式。