常量和不可变状态变量
状态变量可以声明为constant或immutable。在这两种情况下,变量在合约构建后都不能修改。对于constant变量,值必须在编译时固定,而对于immutable,它仍然可以在构造时赋值。
也可以constant在文件级别定义变量。
编译器不会为这些变量保留存储槽,每次出现都会被相应的值替换。
与常规状态变量相比,常量和不可变变量的 gas 成本要低得多。对于常量变量,分配给它的表达式被复制到所有访问它的地方,并且每次都重新计算。这允许局部优化。不可变变量在构造时被评估一次,它们的值被复制到代码中访问它们的所有位置。对于这些值,保留 32 个字节,即使它们可以容纳更少的字节。因此,常量值有时可能比不可变值便宜。
目前并非所有常量和不可变类型都已实现。唯一支持的类型是 字符串(仅用于常量)和值类型。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.4; uint constant X = 32**22 + 8; contract C { string constant TEXT = "abc"; bytes32 constant MY_HASH = keccak256("abc"); uint immutable decimals; uint immutable maxBalance; address immutable owner = msg.sender; constructor(uint decimals_, address ref) { decimals = decimals_; // Assignments to immutables can even access the environment. maxBalance = ref.balance; } function isBalanceTooHigh(address other) public view returns (bool) { return other.balance > maxBalance; } }
持续的
对于constant
变量,该值在编译时必须是一个常量,并且必须在声明变量的地方赋值。任何访问存储、区块链数据(例如block.timestamp
,address(this).balance
或 block.number
)或执行数据(msg.value
或gasleft()
)或调用外部合约的表达式都是不允许的。允许可能对内存分配产生副作用的表达式,但不允许对其他内存对象产生副作用的表达式。允许使用内置函数 keccak256
、sha256
、ripemd160
、和 (尽管除了
之外ecrecover
,它们确实调用了外部合约)。addmod
mulmod
keccak256
允许对内存分配器产生副作用的原因是应该可以构造复杂的对象,例如查找表。此功能尚未完全可用。
不可变
声明为的变量immutable
比声明为的变量的限制要少一些constant
:不可变变量可以在合约的构造函数中或在它们的声明点被分配一个任意值。它们只能分配一次,从那时起,即使在施工期间也可以读取。
编译器生成的合约创建代码将在返回之前修改合约的运行时代码,将所有对不可变对象的引用替换为分配给它们的值。如果您将编译器生成的运行时代码与实际存储在区块链中的运行时代码进行比较,这一点很重要。
笔记
在声明时分配的不可变对象仅在合约的构造函数执行后才被视为已初始化。这意味着您不能使用依赖于另一个不可变对象的值内联初始化不可变对象。但是,您可以在合约的构造函数中执行此操作。
这是为了防止对状态变量初始化和构造函数执行顺序的不同解释,特别是在继承方面。