数组
数组可以具有编译时固定大小,也可以具有动态大小。
固定大小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)
您应该使用bytesoverbytes1[]因为它更便宜,因为使用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 memorystringstring
类似地,该bytes.concat函数可以连接任意数量的bytes或值。该函数返回一个包含参数内容的单个数组,没有填充。如果要使用字符串参数或其他不能隐式转换为的类型,则需要先将它们转换为或/…/ 。bytes1 ... bytes32bytes memorybytesbytesbytes1bytes32
// 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] memoryuint8uint[3] memoryuint
// 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]uint8int8[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 = 2x.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;
}
}