数组
数组可以具有编译时固定大小,也可以具有动态大小。
固定大小k和元素类型的数组的类型T写为T[k],动态大小的数组写为T[]。
例如,一个由 5 个动态数组组成的数组uint写为 uint[][5]. 与其他一些语言相比,这种表示法是相反的。在 Solidity 中,X[3]总是一个包含三个 type 元素的数组X,即使X它本身就是一个数组。在其他语言(例如 C)中并非如此。
索引从零开始,访问与声明的方向相反。
例如,如果您有一个变量,则使用 访问第三个动态数组中的第七个,并使用 访问第三个动态数组。同样,如果您有一个类型的数组也可以是一个数组,那么总是有 type 。uint[][5] memory xuintx[2][6]x[2]T[5] aTa[2]T
数组元素可以是任何类型,包括映射或结构。类型的一般限制适用,因为映射只能存储在 storage数据位置,公开可见的函数需要ABI 类型的参数。
可以标记状态变量数组public并让 Solidity 创建一个getter。数字索引成为 getter 的必需参数。
访问超出其末尾的数组会导致断言失败。方法.push()和.push(value)可用于在数组末尾追加一个新元素,其中.push()追加一个零初始化元素并返回对它的引用。
bytes
和string
作为数组
bytes
和 类型的变量string
是特殊的数组。类型与bytes
类似bytes1[]
,但它紧紧地封装在 calldata 和内存中。string
等于bytes
但不允许长度或索引访问。
Solidity 没有字符串操作函数,但有第三方字符串库。您还可以使用 keccak256-hash 比较两个字符串, 并使用 连接两个字符串。keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))
string.concat(s1, s2)
您应该使用bytes
overbytes1[]
因为它更便宜,因为使用bytes1[]
inmemory
在元素之间添加了 31 个填充字节。请注意,在 中storage
,由于紧密包装而缺少填充,请参见bytes 和 string。作为一般规则,bytes
用于任意长度的原始字节数据和string
任意长度的字符串
(UTF-8) 数据。如果您可以将长度限制为一定数量的字节,请始终使用其中一种值类型bytes1
,bytes32
因为它们便宜得多。
笔记
如果要访问 string 的字节表示s
,请使用 bytes(s).length
/ 。请记住,您访问的是 UTF-8 表示的低级字节,而不是单个字符。bytes(s)[7] = 'x';
功能bytes.concat
和string.concat
您可以使用 连接任意数量的string
值string.concat
。该函数返回一个包含参数内容的单个数组,没有填充。如果要使用其他类型的参数不能隐式转换为,则需要先转换为。string memory
string
string
类似地,该bytes.concat
函数可以连接任意数量的bytes
或值。该函数返回一个包含参数内容的单个数组,没有填充。如果要使用字符串参数或其他不能隐式转换为的类型,则需要先将它们转换为或/…/ 。bytes1 ... bytes32
bytes memory
bytes
bytes
bytes1
bytes32
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.12; contract C { string s = "Storage"; function f(bytes calldata bc, string memory sm, bytes16 b) public view { string memory concatString = string.concat(s, string(bc), "Literal", sm); assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concatString).length); bytes memory concatBytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b); assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concatBytes.length); } }
如果您调用string.concat
或bytes.concat
不使用参数,它们将返回一个空数组。
分配内存数组
可以使用new
运算符创建具有动态长度的内存数组。与存储数组相反,无法调整内存数组的大小(例如,.push
成员函数不可用)。您要么必须提前计算所需的大小,要么创建一个新的内存数组并复制每个元素。
与 Solidity 中的所有变量一样,新分配的数组的元素始终使用默认值初始化。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.16 <0.9.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; } }
数组文字
数组字面量是一个或多个表达式的逗号分隔列表,用方括号 ( [...]
) 括起来。例如. 数组字面量的类型确定如下:[1, a, f(3)]
它始终是一个静态大小的内存数组,其长度是表达式的数量。
数组的基本类型是列表中第一个表达式的类型,这样所有其他表达式都可以隐式转换为它。如果这是不可能的,这是一个类型错误。
有一个所有元素都可以转换为的类型是不够的。其中一个元素必须属于该类型。
在下面的例子中,类型是 ,因为每个常量的类型都是。如果希望结果为类型,则需要将第一个元素转换为.[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 C { 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.16 <0.9.0; contract C { function f() public pure returns (uint24[2][4] memory) { uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3], [uint24(0xffff), 4]]; // The following does not work, because some of the inner arrays are not of the right type. // uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]]; return x; } }
固定大小的内存数组不能分配给动态大小的内存数组,即以下是不可能的:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.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 中的传递方式,它会产生一些复杂性。
如果要初始化动态大小的数组,则必须分配各个元素:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.16 <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
包含其元素数量的成员。内存数组的长度在创建后是固定的(但是是动态的,即它可以依赖于运行时参数)。 - 推():
-
动态存储数组 和
bytes
(notstring
) 有一个名为的成员函数push()
,您可以使用它在数组末尾附加一个零初始化元素。它返回对元素的引用,以便可以像 或一样使用它。x.push().t = 2
x.push() = b
- 推(x):
-
动态存储数组 和
bytes
(notstring
) 有一个名为的成员函数push(x)
,您可以使用它在数组末尾附加给定元素。该函数不返回任何内容。 - 流行():
-
动态存储数组 和
bytes
(notstring
) 有一个名为的成员函数pop()
,您可以使用该函数从数组末尾删除一个元素。这也隐式调用删除元素上的删除。
笔记
通过调用增加存储数组的长度push()
具有恒定的gas成本,因为存储是零初始化的,而通过调用减少长度pop()
的成本取决于被删除元素的“大小”。如果该元素是一个数组,它可能会非常昂贵,因为它包括显式清除已删除的元素,类似于对它们调用delete。
笔记
要在外部(而不是公共)函数中使用数组数组,您需要激活 ABI coder v2。
笔记
在拜占庭之前的 EVM 版本中,无法访问函数调用返回的动态数组。如果您调用返回动态数组的函数,请确保使用设置为拜占庭模式的 EVM。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract ArrayContract { uint[2**20] 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][] 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 ``pairsOfFlags``. 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 pairsOfFlags[index][0] = flagA; pairsOfFlags[index][1] = flagB; } function changeFlagArraySize(uint newSize) public { // using push and pop is the only way to change the // length of an array if (newSize < pairsOfFlags.length) { while (pairsOfFlags.length > newSize) pairsOfFlags.pop(); } else if (newSize > pairsOfFlags.length) { while (pairsOfFlags.length < newSize) pairsOfFlags.push(); } } function clear() public { // these clear the arrays completely delete pairsOfFlags; delete aLotOfIntegers; // identical effect here pairsOfFlags = new bool[2][](0); } bytes 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[]" byteData = data; for (uint i = 0; i < 7; i++) byteData.push(); byteData[3] = 0x08; delete byteData[2]; } function addFlag(bool[2] memory flag) public returns (uint) { pairsOfFlags.push(flag); return pairsOfFlags.length; } 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] = bytes1(uint8(i)); return b; } }