Solidity是一种静态类型语言,这意味着需要指定每个变量(状态和本地)的类型。Solidity提供了几种基本类型,可以组合形成复杂类型。
此外,类型可以在包含运算符的表达式中相互交互。有关各种运算符的快速参考,请参阅运算符的优先顺序。
值类型
以下类型也称为值类型,因为这些类型的变量将始终按值传递,即它们在用作函数参数或赋值时始终被复制。
布尔
bool
:可能的值是常量true
和false
。
运营商:
!
(逻辑否定)&&
(逻辑连词,“和”)||
(逻辑分离,“或”)==
(平等)!=
(不等式)
整数
int
/ uint
:各种大小的有符号和无符号整数。关键字uint8
以uint256
在步骤8
(无符号的8到256位)和int8
到int256
。uint
和int
是别名uint256
和int256
分别。
运营商:
- 比较:
<=
,<
,==
,!=
,>=
,>
(计算结果为bool
) - 位运算符:
&
,|
,^
(按位异或),~
(按位取反) - 换班操作员:(
<<
左移),>>
(右移) - 算术运算符:
+
,-
,一元-
,*
,/
,%
(模),**
(幂)
比较
比较的值是通过比较整数值获得的值。
位操作
位操作是在数字的二进制补码表示上执行的。这意味着,例如。~int256(0) == int256(-1)
转移
移位操作的结果具有左操作数的类型。表达式相当于,对于正整数, 等价于。对于负数, 相当于将四舍五入的幂除以(向负无穷大)。按负数移动会引发运行时异常。x << y
x * 2**y
x >> y
x / 2**y
x
x >> y
2
警告
版本之前0.5.0
右移负等同于,用于向零舍入,而不是向负无穷舍即右移。x >> y
x
x / 2**y
加法,减法和乘法
加法,减法和乘法具有通常的语义。它们用二进制补码表示,例如。在设计安全的智能合约时,您必须考虑这些溢出。uint256(0) - uint256(1) == 2**256 - 1
表达式-x
等同于where 的类型。这意味着如果类型是无符号整数类型,则不会为负数。此外,如果是否定的,可以是积极的。还有另一个警告也是由两个补码表示:(T(0) - x)
T
x
-x
x
-x
x
int x = -2**255;
assert(-x == x);
这意味着即使数字为负数,也不能假设它的否定是正数。
分部
由于操作结果的类型始终是其中一个操作数的类型,因此对整数的除法总是产生整数。在Solidity中,分部向零舍入。这意味着。
int256(-5) / int256(2) == int256(-2)
请注意,相反,文字除法会产生任意精度的小数值。
注意
除以零会导致断言失败。
模数
模运算产生操作数 除以操作数后的余数,其中和。这意味着模数与左操作数(或零)产生相同的符号,并保持为负数:
a % nranq = int(a / n)r = a - (n * q)a % n == -(abs(a) % n)a
int256(5) % int256(2) == int256(1)
int256(5) % int256(-2) == int256(1)
int256(-5) % int256(2) == int256(-1)
int256(-5) % int256(-2) == int256(-1)
注意
模数为零会导致失败的断言。
指数
Exponentiation仅适用于未签名类型。请注意您使用的类型足够大以保存结果并为潜在的包装行为做好准备。
注意
请注意,0**0
由EVM定义为1
。
定点数
警告
Solidity尚未完全支持定点数。
fixed
/ ufixed
:各种大小的有符号和无符号定点数。关键字ufixedMxN
和fixedMxN
,其中M
表示类型占用的位数,N
表示可用的小数点数。M
必须可被8整除,并从8位变为256位。N
必须在0到80之间,包括0和80。 ufixed
和fixed
是别名ufixed128x18
和fixed128x18
分别。
运营商:
- 比较:
<=
,<
,==
,!=
,>=
,>
(计算结果为bool
) - 算术运算符:
+
,-
,一元-
,*
,/
,%
(模)
注意
浮点(float
和double
许多语言,更准确地说是IEEE 754数字)和定点数之间的主要区别在于,用于整数的小数位数和小数部分(小数点后面的部分)在前者中是灵活的,而后者则严格定义。通常,在浮点中,几乎整个空间用于表示数字,而只有少量位用于定义小数点的位置。
地址
地址类型有两种形式,大致相同:
address
:保存一个20字节的值(以太坊地址的大小)。address payable
:相同address
,但附加成员transfer
和send
。
这种区别背后的想法是,您可以发送以太网的地址,而平原不能发送以太网。address payable
address
输入转化次数:
从隐式转换到被允许的,而从转换至是不可能的(执行这种转换的唯一方法是通过使用中间转换)。
address payableaddressaddressaddress payableuint160
地址文字可以隐式转换为。address payable
address
对于整数,整数文字bytes20
和契约类型,允许显式转换和转换,但需要注意以下事项:不允许转换表单。相反,表单转换的结果 具有类型,如果是整数或固定字节类型,文字或具有应付回退函数的合约。如果是没有应付回退功能的合约,那么将是类型。在外部函数中,签名用于和类型。
address payable(x)address(x)address payablexxaddress(x)addressaddressaddressaddress payable
注意
这很可能是,你并不需要关心的区别address
,并与只使用无处不在。例如,如果您正在使用提款模式,您可以(并且应该)将地址本身存储为,因为您调用了该功能 ,这是一个。address payable
address
address
transfer
msg.sender
address payable
运营商:
<=
,<
,==
,!=
,>=
和>
警告
如果您将使用更大的字节大小所涉及的类型address
,例如bytes32
,然后address
被截断。要减少编译器强制转换歧义版本0.4.24及更高版本,请在转换中使截断显式化。以地址为例0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC
。
你可以使用address(uint160(bytes20(b)))
,结果0x111122223333444455556666777788889999aAaa
,或者你可以使用address(uint160(uint256(b)))
,结果0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc
。
注意
版本0.5.0引入了address
和之间的区别。同样从该版本开始,合约不是从地址类型派生的,但如果它们具有应付回退功能,则仍然可以显式转换。address payable
address
address payable
地址成员
有关所有地址成员的快速参考,请参阅地址类型的成员。
balance
和transfer
可以使用属性查询地址的余额,balance
并使用以下transfer
函数将以太网(以wei为单位)发送到应付地址:
address payable x = address(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
transfer
如果当前合同的余额不够大或接收帐户拒绝以太网转移,则该功能将失败。该transfer
功能在故障时恢复。
注意
如果x
是合约地址,则其代码(更具体地说:其后备功能,如果存在)将与transfer
呼叫一起执行(这是EVM的一项功能,无法阻止)。如果执行耗尽gas或以任何方式失败,则以太网转移将被恢复,当前合约将以例外停止。
send
发送是低级别的对应物transfer
。如果执行失败,则当前合约不会因异常而停止,但send
会返回false
。
警告
使用中存在一些危险send
:如果调用堆栈深度为1024(这可能始终由调用者强制执行),则传输失败,并且如果接收方耗尽gas,它也会失败。因此,为了进行安全的以太传输,请始终检查返回值send
,使用transfer
甚至更好:使用收件人提取资金的模式。
call
,delegatecall
和staticcall
为了与不坚持ABI,或获得过该编码,功能更直接的控制合同接口call
,delegatecall
并staticcall
提供。它们都将一个参数作为输入并返回成功条件(作为a )和返回的数据()。的功能,, 和可被用于编码的结构化数据。bytes memory
bool
bytes memory
abi.encode
abi.encodePacked
abi.encodeWithSelector
abi.encodeWithSignature
例:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
警告
所有这些功能都是低级功能,应谨慎使用。具体来说,任何未知的合约都可能是恶意的,如果你调用它,你就可以将控制权移交给合约,而合约又可以回调你的合约,所以在调用返回时准备好改变你的状态变量。与其他契约交互的常规方法是在契约对象(x.f()
)上调用函数。
注意
先前版本的Solidity允许这些函数接收任意参数,并且还可以处理bytes4
不同类型的第一个参数。在版本0.5.0中删除了这些边缘情况。
可以使用.gas()
修改器调整供应的gas:
namReg.call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));
同样,也可以控制提供的Ether值:
nameReg.call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
最后,可以组合这些修饰符。他们的订单无关紧要:
nameReg.call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
以类似的方式,delegatecall
可以使用该函数:不同之处在于仅使用给定地址的代码,所有其他方面取自当前合同。目的delegatecall
是使用存储在另一个合同中的库代码。用户必须确保两个合同中的存储布局都适合使用委托调用。
注意
在宅基地之前,只有一个有限的变体callcode
可用,不能提供对原始msg.sender
和msg.value
价值的访问。此功能已在0.5.0版中删除。
因为staticcall
也可以使用。这基本上是相同的call
,但如果被调用的函数以任何方式修改状态,它将恢复。
所有这三个功能call
,delegatecall
以及staticcall
非常低级别的功能,只能被用作最后的手段,因为他们打破密实的类型安全。
该.gas()
选项适用于所有三种方法,但.value()
不支持该选项delegatecall
。
注意
所有合约都可以转换为address
类型,因此可以使用查询当前合约的余额address(this).balance
。
合约类型
每个合约都定义了自己的类型。您可以隐式地将合约转换为它们继承的合约。合约可以明确地转换为所有其他合约类型和address
类型。
只有在合约类型具有应付回退功能时,才能进行与该类型的显式转换。转换仍然使用而不是使用。您可以在有关地址类型的部分中找到更多信息。address payable
address(x)
address payable(x)
注意
如果声明一个合约类型的局部变量(MyContract c),则可以调用该合约上的函数。注意从同一合约类型的某个地方分配它。
您还可以实例化合约(这意味着它们是新创建的)。您可以在“新合约” 部分中找到更多详细信息。
合约的数据表示与address
类型的数据表示相同,并且此类型也在ABI中使用。
合约类型的成员是合约的外部功能,包括公共状态变量。
固定大小的字节数组
的值类型bytes1
,bytes2
,bytes3
,...,bytes32
保持字节序列从一个到最多32 byte
是一个别名bytes1
。
运营商:
- 比较:
<=
,<
,==
,!=
,>=
,>
(计算结果为bool
) - 位运算符:
&
,|
,^
(按位异或),~
(按位取反) - 移位操作员:(
<<
左移),>>
(右移) - 索引访问:如果
x
是类型bytesI
,然后x[k]
对返回的第一个字节(只读)。0 <= k < I
k
移位运算符使用任何整数类型作为右操作数(但返回左操作数的类型),表示要移位的位数。以负数换算会导致运行时异常。
成员:
.length
产生字节数组的固定长度(只读)。
注意
类型byte[]
是一个字节数组,但由于填充规则,每个元素浪费31个字节的空间(存储除外)。最好使用该bytes
类型。
动态大小的字节数组
bytes
:
动态大小的字节数组,请参见数组。不是一种价值型!
string
:
动态大小的UTF-8编码字符串,请参阅数组。不是一种价值型!
地址文字
通过地址校验和测试的十六进制文字,例如 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
是类型。长度在39到41位之间并且未通过校验和测试的十六进制文字产生警告,并被视为常规有理数字文字。address payable
注意
混合大小写地址校验和格式在EIP-55中定义。
理性和整数文字
整数文字由0-9范围内的一系列数字组成。它们被解释为小数。例如,69
意味着六十九。Solidity中不存在八进制文字,前导零无效。
小数分数文字由a形成,.
一侧至少有一个数字。实例包括1.
,.1
和1.3
。
也支持科学记数法,其中基数可以有分数,而指数则不能。实例包括2e10
,-2e10
,2e-10
,2.5e1
。
下划线可用于分隔数字文字的数字以帮助提高可读性。例如,十进制123_000
,十六进制0x2eff_abde
,科学十进制表示法1_2e345_678
都是有效的。下划线仅允许在两位数之间,并且只允许一个连续的下划线。没有额外的语义含义添加到包含下划线的数字文字中,下划线被忽略。
数字文字表达式保留任意精度,直到它们转换为非文字类型(即通过将它们与非文字表达式一起使用或通过显式转换)。这意味着计算不会溢出,并且分割不会在数字文字表达式中截断。
例如,虽然中间结果甚至不适合机器字大小,但结果是常量(类型)。此外,得到整数(尽管在它们之间使用非整数)。(2**800 + 1) - 2**800
1
uint8
.5 * 8
4
只要操作数是整数,任何可以应用于整数的运算符也可以应用于数字文字表达式。如果两者中的任何一个是小数,则不允许位操作,如果指数是小数,则不允许取幂(因为这可能导致非有理数)。
注意
Solidity对每个有理数都有一个数字类型。整数文字和有理数字文字属于数字文字类型。此外,所有数字文字表达式(即仅包含数字文字和运算符的表达式)都属于数字文字类型。所以数量字面表述,并都属于相同数量的文本类型的有理数三人。1 + 2
2 + 1
警告
用于在版本0.4.0之前的Solidity中截断的整数文字的除法,但它现在转换为有理数,即不等于,但是。5 / 2
2
2.5
注意
一旦将非文字表达式与非文字表达式一起使用,它们就会转换为非文字表达式。忽略类型,分配给b
下面的表达式的值求值为整数。因为a
是类型uint128
,表达式必须具有适当的类型。由于是的类型没有普通型和,密实度编译器不接受这样的代码。2.5 + a
2.5
uint128
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
字符串文字
字符串文字用双引号或单引号("foo"
或'bar'
)编写。它们并不像C中那样暗示尾随零; "foo"
代表三个字节,而不是四个字节 与整数文字一样,它们的类型可以变化,但它们可以隐式转换为bytes1
...... bytes32
,如果它们适合,则可以转换bytes
为string
。
字符串文字支持以下转义字符:
\<newline>
(逃避实际换行)\\
(反斜杠)\'
(单引号)\"
(双引号)\b
(退格)\f
(换页)\n
(新队)\r
(回车)\t
(标签)\v
(垂直标签)\xNN
(十六进制逃脱,见下文)\uNNNN
(unicode逃逸,见下文)
\xNN
采用十六进制值并插入适当的字节,同时\uNNNN
采用Unicode代码点并插入UTF-8序列。
以下示例中的字符串长度为十个字节。它以换行符开头,后跟双引号,单引号为反斜杠字符,然后(不带分隔符)字符序列abcdef
。
"\n\"\'\\abc\
def"
任何不是换行符的unicode行终止符(即LF,VF,FF,CR,NEL,LS,PS)都被认为是终止字符串文字。如果字符串文字前面没有a,则换行符仅终止字符串文字\
。
十六进制文字
十六进制文字以关键字为前缀,hex
并用双引号或单引号(hex"001122FF"
)括起来。它们的内容必须是十六进制字符串,它们的值将是这些值的二进制表示形式。
十六进制文字的行为类似于字符串文字,并具有相同的可转换性限制。
枚举
枚举是在Solidity中创建用户定义类型的一种方法。它们可以显式转换为所有整数类型,但不允许隐式转换。在运行时从整数检查显式转换,该值位于枚举范围内,否则会导致失败的断言。枚举至少需要一名成员。
数据表示与C中的枚举相同:选项由后续的无符号整数值表示0
。
pragma solidity >=0.4.16 <0.6.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity. The integer type used is just
// large enough to hold all enum values, i.e. if you have more than 256 values,
// `uint16` will be used and so on.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
}
函数类型
函数类型是函数的类型。函数类型的变量可以从函数中分配,函数类型的函数参数可以用于将函数传递给函数调用并从函数调用返回函数。函数类型有两种形式 - 内部和外部函数:
内部函数只能在当前合约内部调用(更具体地说,在当前代码单元内部,也包括内部库函数和继承函数),因为它们不能在当前合约的上下文之外执行。通过跳转到其条目标签来实现调用内部函数,就像在内部调用当前合约的函数一样。
外部函数由地址和函数签名组成,它们可以通过外部函数调用传递和返回。
函数类型标注如下:
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]
与参数类型相反,返回类型不能为空 - 如果函数类型不返回任何内容,则 必须省略整个部分。returns (<return types>)
默认情况下,函数类型是内部函数,因此internal
可以省略关键字。请注意,这仅适用于函数类型。必须为合同中定义的函数明确指定可见性,它们没有默认值。
转换:
可以显式转换外部函数类型的值,address
从而得到函数合约的地址。
函数类型A
可以隐式转换为函数类型,B
当且仅当它们的参数类型相同,它们的返回类型相同,它们的内部/外部属性相同且状态可变性A
不比状态可变性更严格B
。特别是:
pure
功能可以被转换为view
与non-payable
功能view
函数可以转换为non-payable
函数payable
函数可以转换为non-payable
函数
函数类型之间不可能进行其他转换。
关于payable
并且non-payable
可能有点混乱的规则,但实质上,如果函数是payable
,这意味着它也接受零以太的支付,所以它也是non-payable
。另一方面,non-payable
函数将拒绝发送给它的以太,因此non-payable
函数不能转换为payable
函数。
如果未初始化函数类型变量,则调用它会导致失败的断言。如果在使用后调用函数,也会发生同样的情况delete
。
如果在Solidity上下文之外使用外部函数类型,则将它们视为function
类型,它将函数标识符一起编码为单个bytes24
类型的地址。
请注意,当前合同的公共函数既可以用作内部函数,也可以用作外部函数。要f
用作内部函数,只需使用f
,如果要使用其外部表单,请使用this.f
。
成员:
公共(或外部)函数也有一个特殊的成员调用selector
,它返回ABI函数选择器:
pragma solidity >=0.4.16 <0.6.0;
contract Selector {
function f() public pure returns (bytes4) {
return this.f.selector;
}
}
显示如何使用内部函数类型的示例:
pragma solidity >=0.4.16 <0.6.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
另一个使用外部函数类型的示例:
pragma solidity >=0.4.22 <0.6.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // known contract
uint exchangeRate;
function buySomething() public {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(oracle),
"Only oracle can call this."
);
exchangeRate = response;
}
}
注意
Lambda或内联函数已计划但尚不支持。
参考类型
可以通过多个不同的名称修改引用类型的值。将此与值类型进行对比,只要使用值类型的变量,您就可以获得独立的副本。因此,必须比值类型更谨慎地处理引用类型。目前,引用类型包括结构,数组和映射。如果使用引用类型,则必须显式提供存储类型的数据区域:( memory
其生命周期仅限于函数调用),storage
(存储状态变量的位置)或calldata
(包含特殊数据的位置)函数参数,仅适用于外部函数调用参数)。
更改数据位置的分配或类型转换将始终产生自动复制操作,而同一数据位置内的分配仅在某些情况下复制存储类型。
数据位置
每个引用类型(即数组和结构)都有一个附加注释,即“数据位置”,关于它的存储位置。有三个数据位置: memory
,storage
和calldata
。Calldata仅对外部合约函数的参数有效,并且是此类参数所必需的。Calldata是一个不可修改的非持久性区域,其中存储了函数参数,其行为大多类似于内存。
注意
在版本0.5.0之前,数据位置可以省略,并且根据变量的类型,函数类型等默认为不同的位置,但是所有复杂类型现在必须给出明确的数据位置。
数据位置不仅与数据的持久性相关,而且与分配的语义相关:存储和内存(或来自calldata)之间的分配始终创建独立的副本。从内存到内存的分配仅创建引用。这意味着在引用相同数据的所有其他内存变量中也可以看到对一个内存变量的更改。从存储到本地存储变量的分配也仅分配引用。相反,存储的所有其他分配始终复制。这种情况的示例是状态变量的赋值或存储结构类型的局部变量的成员,即使局部变量本身只是一个引用。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint[] x; // the data location of x is storage
// the data location of memoryArray is memory
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// This does not work either, since it would "reset" the pointer, but there
// is no sensible location it could point to.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
数组
数组可以具有编译时固定大小,也可以是动态的。对元素的限制很少,它也可以是另一个数组,映射或结构。但是,对类型的一般限制适用于映射只能用于存储,而公开可见的函数需要ABI类型的参数。
固定大小k
和元素类型的数组T
被写为T[k]
动态大小的数组T[]
。例如,5个动态数组的数组uint
是uint[][5]
(注意,与其他语言相比,符号反转)。要访问第三个动态数组中的第二个uint,您可以使用x[2][1]
(索引从零开始,访问以与声明相反的方式工作,即x[2]
从右侧削减类型中的一个级别)。
在其末尾访问数组会导致恢复。如果要添加新元素,则必须使用.push()
或增加该.length
成员(请参阅下文)。
类型的变量bytes
和string
特殊数组。A bytes
类似于byte[]
,但它在calldata和内存中紧密包装。string
等于bytes
但不允许长度或索引访问。所以bytes
应该总是优先考虑,byte[]
因为它更便宜。根据经验,使用bytes
任意长度的原始字节数据和string
任意长度的字符串(UTF-8)数据。如果你可以限制长到一定的字节数,总是用一个bytes1
来bytes32
,因为他们是便宜得多。
注意
如果要访问字符串的字节表示s
,请使用 bytes(s).length
/ 。请记住,您正在访问UTF-8表示的低级字节,而不是单个字符!bytes(s)[7] = 'x';
可以标记数组public
并使Solidity创建一个getter。数字索引将成为getter的必需参数。
分配内存数组
您可以使用该new
关键字在内存中创建具有运行时相关长度的数组。相对于存储阵列,它是不能够调整大小的存储器阵列(例如,通过分配给.length
成员)。您必须提前计算所需的大小或创建新的内存阵列并复制每个元素。
pragma solidity >=0.4.16 <0.6.0;
contract C {
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;
}
}
数组文字/内联数组
数组文字是作为表达式编写的数组,不会立即分配给变量。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f() public pure {
g([uint(1), 2, 3]);
}
function g(uint[3] memory) public pure {
// ...
}
}
数组文字的类型是固定大小的内存数组,其基类型是给定元素的通用类型。请注意,目前,固定大小的内存阵列无法分配给动态大小的内存阵列,即以下内容不可能:[1, 2, 3]
uint8[3] memory
uint8
uint
pragma solidity >=0.4.0 <0.6.0;
// This will not compile.
contract C {
function f() public {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] memory x = [uint(1), 3, 4];
}
}
计划在将来消除这种限制,但由于数组如何在ABI中传递,目前会产生一些复杂性。
成员
长度:
数组的length
成员包含其元素数。一旦创建,内存数组的长度是固定的(但是动态的,即它可以取决于运行时参数)。对于动态大小的阵列(仅适用于存储),可以指定此成员来调整阵列的大小。访问当前长度之外的元素不会自动调整数组的大小,而是会导致失败的断言。增加长度会为数组添加新的零初始化元素。减少长度delete
会对每个删除的元素执行隐式:ref:。
推:
动态存储阵列和bytes
(不string
)具有一个名为的成员函数push
,可用于在数组末尾附加元素。该元素将被初始化为零。该函数返回新的长度。
流行:
动态存储阵列和bytes
(不string
)具有一个名为的成员函数pop
,可用于从数组末尾删除元素。这也隐式调用:ref:delete
on被删除的元素。
警告
如果.length--
在空数组上使用它会导致下溢,从而将长度设置为2**256-1
。
注意
增加存储阵列的长度具有恒定的gas成本,因为存储被假定为零初始化,而减小长度至少具有线性成本(但在大多数情况下比线性更差),因为它包括明确清除被移除的元素类似于打电话:ref:delete
关于他们。
注意
在外部函数中不可能使用数组数组(但在公共函数中支持它们)。
注意
在Byzantium之前的EVM版本中,无法从函数调用中访问动态数组返回。如果调用返回动态数组的函数,请确保使用设置为Byzantium模式的EVM。
pragma solidity >=0.4.16 <0.6.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// Note that the following is not a pair of dynamic arrays but a
// dynamic array of pairs (i.e. of fixed size arrays of length two).
// Because of that, T[] is always a dynamic array of T, even if T
// itself is an array.
// Data location for all state variables is storage.
bool[2][] m_pairsOfFlags;
// newPairs is stored in memory - the only possibility
// for public contract function arguments
function setAllFlagPairs(bool[2][] memory newPairs) public {
// assignment to a storage array performs a copy of ``newPairs`` and
// replaces the complete array ``m_pairsOfFlags``.
m_pairsOfFlags = newPairs;
}
struct StructType {
uint[] contents;
uint moreInfo;
}
StructType s;
function f(uint[] memory c) public {
// stores a reference to ``s`` in ``g``
StructType storage g = s;
// also changes ``s.moreInfo``.
g.moreInfo = 2;
// assigns a copy because ``g.contents``
// is not a local variable, but a member of
// a local variable.
g.contents = c;
}
function setFlagPair(uint index, bool flagA, bool flagB) public {
// access to a non-existing index will throw an exception
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) public {
// if the new size is smaller, removed array elements will be cleared
m_pairsOfFlags.length = newSize;
}
function clear() public {
// these clear the arrays completely
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// identical effect here
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes memory data) public {
// byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]"
m_byteData = data;
m_byteData.length += 7;
m_byteData[3] = 0x08;
delete m_byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// Dynamic memory arrays are created using `new`:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Inline arrays are always statically-sized and if you only
// use literals, you have to provide at least one type.
arrayOfPairs[0] = [uint(1), 2];
// Create a dynamic byte array:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(uint8(i));
return b;
}
}
结构
Solidity提供了一种以结构形式定义新类型的方法,如以下示例所示:
pragma solidity >=0.4.11 <0.6.0;
contract CrowdFunding {
// Defines a new type with two fields.
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address payable beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// Creates new struct in memory and copies it to storage.
// We leave out the mapping type, because it is not valid in memory.
// If structs are copied (even from storage to storage), mapping types
// are always omitted, because they cannot be enumerated.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) public payable {
Campaign storage c = campaigns[campaignID];
// Creates a new temporary memory struct, initialised with the given values
// and copies it over to storage.
// Note that you can also use Funder(msg.sender, msg.value) to initialise.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
合约不提供众筹合约的全部功能,但它包含了解结构所必需的基本概念。结构类型可以在映射和数组中使用,它们本身可以包含映射和数组。
虽然struct本身可以是映射成员的值类型,但它可以包含其类型的动态大小数组,但结构不可能包含其自己类型的成员。这种限制是必要的,因为结构的大小必须是有限的。
请注意,在所有函数中,如何将结构类型分配给具有数据位置的局部变量storage
。这不会复制结构但只存储引用,以便对局部变量成员的赋值实际写入状态。
当然,您也可以直接访问结构的成员而不将其分配给局部变量,如。campaigns[campaignID].amount = 0
映射
使用语法声明映射类型。用户定义的或复杂的类型,例如合同类型,枚举,映射,结构和除了和不允许的任何数组类型。 可以是任何类型,包括映射。mapping(_KeyType => _ValueType)
_KeyType
bytes
string
bytes
string
_ValueType
您可以将映射视为散列表,它实际上是初始化的,这样每个可能的键都存在并映射到一个值,其字节表示全为零,即类型的默认值。相似性在那里结束,关键数据不存储在映射中,只有其keccak256
散列用于查找值。
因此,映射没有设置密钥或值的长度或概念。
映射只能具有数据位置,storage
因此允许用于状态变量,作为函数中的存储引用类型,或作为库函数的参数。它们不能用作参数或返回公开可见的合同函数的参数。
您可以将映射类型的变量标记为,public
并且Solidity 为您创建一个 getter。它_KeyType
成为getter的参数。如果_ValueType
是值类型或结构,则getter返回_ValueType
。如果_ValueType
是数组或映射,则getter对每个参数都有一个参数_KeyType
递归。例如,使用映射:
pragma solidity >=0.4.0 <0.6.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(address(this));
}
}
注意
映射不可迭代,但可以在它们之上实现数据结构。有关示例,请参阅可迭代映射。
删除
delete a
为类型指定初始值a
。即整数它相当于,但它也可以用于数组,它可以分配长度为零的动态数组或相同长度的静态数组,并重置所有元素。对于结构,它分配一个结构,重置所有成员。换句话说,after 的值与没有赋值时声明的值相同,但需要注意以下几点:a = 0
a
delete a
a
delete
对映射没有影响(因为映射的键可能是任意的,通常是未知的)。因此,如果删除结构,它将重置所有不是映射的成员,并且除非它们是映射,否则还会递归到成员中。但是,可以删除单个键及其映射到的内容:如果a
是映射,则将删除存储在的值。delete a[x]
x
重要的是要注意,它的行为类似于赋值,即它存储一个新对象。当引用变量时,这种区别是可见的:它只会重置自身,而不是之前引用的值。delete a
a
a
a
a
pragma solidity >=0.4.0 <0.6.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // sets x to 0, does not affect data
delete data; // sets data to 0, does not affect x
uint[] storage y = dataArray;
delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
assert(y.length == 0);
}
}
基本类型之间的转换
隐式转换
如果运算符应用于不同类型,则编译器会尝试将其中一个操作数隐式转换为另一个操作数的类型(对于赋值也是如此)。一般来说,如果语义有意义并且没有信息丢失,uint8
则值类型之间的隐式转换是可能的:可转换为uint16
和转换 int128
为int256
,但int8
不可转换为uint256
(因为uint256
不能保持例如-1
)。
有关详细信息,请参阅有关类型本身的部分。
显式转换
如果编译器不允许隐式转换但您知道自己在做什么,则有时可能会进行显式类型转换。请注意,这可能会给您一些意外的行为并允许您绕过编译器的某些安全功能,因此请务必测试结果是否符合您的要求!使用以下示例将负数int8
转换为uint
:
int8 y = -3;
uint x = uint(y);
在此代码段的末尾,x
将具有值0xfffff..fd
(64个十六进制字符),在256位的二进制补码表示中为-3。
如果将整数显式转换为较小的类型,则会截断高阶位:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
如果将整数显式转换为更大的类型,则将其填充在左侧(即在更高的订单端)。转换结果将等于原始整数。
uint16 a = 0x1234; uint32 b = uint32(a); // b将是0x00001234现在断言(a == b);
固定大小的字节类型在转换期间表现不同。它们可以被认为是单个字节的序列,转换为较小的类型会切断序列:
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12
如果将固定大小的字节类型显式转换为更大的类型,则将其填充在右侧。以固定索引访问字节将导致转换前后的相同值(如果索引仍在范围内):
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
由于整数和固定大小的字节数组在截断或填充时表现不同,因此只允许整数和固定大小字节数组之间的显式转换(如果两者具有相同的大小)。如果要在不同大小的整数和固定大小的字节数组之间进行转换,则必须使用中间转换,以使所需的截断和填充规则显式:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // d will be 0x12
文字和基本类型之间的转换
整数类型
十进制和十六进制数字文字可以隐式转换为任何大小足以表示它而不截断的整数类型:
uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456
固定大小的字节数组
十进制数字文字不能隐式转换为固定大小的字节数组。十六进制数字文字可以是,但仅当十六进制数字的数量完全符合字节类型的大小时。作为例外,具有零值的十进制和十六进制文字都可以转换为任何固定大小的字节类型:
bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine
字符串文字和十六进制字符串文字可以隐式转换为固定大小的字节数组,如果它们的字符数与字节类型的大小相匹配:
bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed
地址
如地址文字中所述,通过校验和测试的正确大小的十六进制文字属于address
类型。没有其他文字可以隐式转换为该address
类型。