數(shù)組可以具有編譯時固定大小,也可以具有動態(tài)大小。
固定大小k和元素類型的數(shù)組的類型T寫為T[k],動態(tài)大小的數(shù)組寫為T[]。
例如,一個由 5 個動態(tài)數(shù)組組成的數(shù)組uint寫為 uint[][5]. 與其他一些語言相比,這種表示法是相反的。在 Solidity 中,X[3]總是一個包含三個 type 元素的數(shù)組X,即使X它本身就是一個數(shù)組。在其他語言(例如 C)中并非如此。
索引從零開始,訪問與聲明的方向相反。
例如,如果您有一個變量,則使用 訪問第三個動態(tài)數(shù)組中的第七個,并使用 訪問第三個動態(tài)數(shù)組。同樣,如果您有一個類型的數(shù)組也可以是一個數(shù)組,那么總是有 type 。uint[][5] memory xuintx[2][6]x[2]T[5] aTa[2]T
數(shù)組元素可以是任何類型,包括映射或結(jié)構(gòu)。類型的一般限制適用,因為映射只能存儲在 storage數(shù)據(jù)位置,公開可見的函數(shù)需要ABI 類型的參數(shù)。
可以標(biāo)記狀態(tài)變量數(shù)組public并讓 Solidity 創(chuàng)建一個getter。數(shù)字索引成為 getter 的必需參數(shù)。
訪問超出其末尾的數(shù)組會導(dǎo)致斷言失敗。方法.push()和.push(value)可用于在數(shù)組末尾追加一個新元素,其中.push()追加一個零初始化元素并返回對它的引用。
bytes
和string
作為數(shù)組bytes
和 類型的變量string
是特殊的數(shù)組。類型與bytes
類似bytes1[]
,但它緊緊地封裝在 calldata 和內(nèi)存中。string
等于bytes
但不允許長度或索引訪問。
Solidity 沒有字符串操作函數(shù),但有第三方字符串庫。您還可以使用 keccak256-hash 比較兩個字符串, 并使用 連接兩個字符串。keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))
string.concat(s1, s2)
您應(yīng)該使用bytes
overbytes1[]
因為它更便宜,因為使用bytes1[]
inmemory
在元素之間添加了 31 個填充字節(jié)。請注意,在 中storage
,由于緊密包裝而缺少填充,請參見bytes 和 string。作為一般規(guī)則,bytes
用于任意長度的原始字節(jié)數(shù)據(jù)和string
任意長度的字符串
(UTF-8) 數(shù)據(jù)。如果您可以將長度限制為一定數(shù)量的字節(jié),請始終使用其中一種值類型bytes1
,bytes32
因為它們便宜得多。
筆記
如果要訪問 string 的字節(jié)表示s
,請使用 bytes(s).length
/ 。請記住,您訪問的是 UTF-8 表示的低級字節(jié),而不是單個字符。bytes(s)[7] = 'x';
bytes.concat
和string.concat
您可以使用 連接任意數(shù)量的string
值string.concat
。該函數(shù)返回一個包含參數(shù)內(nèi)容的單個數(shù)組,沒有填充。如果要使用其他類型的參數(shù)不能隱式轉(zhuǎn)換為,則需要先轉(zhuǎn)換為。string memory
string
string
類似地,該bytes.concat
函數(shù)可以連接任意數(shù)量的bytes
或值。該函數(shù)返回一個包含參數(shù)內(nèi)容的單個數(shù)組,沒有填充。如果要使用字符串參數(shù)或其他不能隱式轉(zhuǎn)換為的類型,則需要先將它們轉(zhuǎn)換為或/…/ 。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); } }
如果您調(diào)用string.concat
或bytes.concat
不使用參數(shù),它們將返回一個空數(shù)組。
可以使用new
運(yùn)算符創(chuàng)建具有動態(tài)長度的內(nèi)存數(shù)組。與存儲數(shù)組相反,無法調(diào)整內(nèi)存數(shù)組的大?。ɡ?,.push
成員函數(shù)不可用)。您要么必須提前計算所需的大小,要么創(chuàng)建一個新的內(nèi)存數(shù)組并復(fù)制每個元素。
與 Solidity 中的所有變量一樣,新分配的數(shù)組的元素始終使用默認(rèn)值初始化。
// 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; } }
數(shù)組字面量是一個或多個表達(dá)式的逗號分隔列表,用方括號 ( [...]
) 括起來。例如. 數(shù)組字面量的類型確定如下:[1, a, f(3)]
它始終是一個靜態(tài)大小的內(nèi)存數(shù)組,其長度是表達(dá)式的數(shù)量。
數(shù)組的基本類型是列表中第一個表達(dá)式的類型,這樣所有其他表達(dá)式都可以隱式轉(zhuǎn)換為它。如果這是不可能的,這是一個類型錯誤。
有一個所有元素都可以轉(zhuǎn)換為的類型是不夠的。其中一個元素必須屬于該類型。
在下面的例子中,類型是 ,因為每個常量的類型都是。如果希望結(jié)果為類型,則需要將第一個元素轉(zhuǎn)換為.[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 { // ... } }
數(shù)組字面量無效,因為第一個表達(dá)式的類型是,而第二個表達(dá)式的類型是,并且它們不能隱式相互轉(zhuǎn)換。為了使它工作,你可以使用,例如。[1, -1]
uint8
int8
[int8(1), -1]
由于不同類型的固定大小的內(nèi)存數(shù)組不能相互轉(zhuǎn)換(即使基類型可以),如果你想使用二維數(shù)組字面量,你總是必須明確指定一個通用的基類型:
// 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; } }
固定大小的內(nèi)存數(shù)組不能分配給動態(tài)大小的內(nèi)存數(shù)組,即以下是不可能的:
// 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]; } }
計劃在將來取消此限制,但由于數(shù)組在 ABI 中的傳遞方式,它會產(chǎn)生一些復(fù)雜性。
如果要初始化動態(tài)大小的數(shù)組,則必須分配各個元素:
// 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; } }
數(shù)組有一個length
包含其元素數(shù)量的成員。內(nèi)存數(shù)組的長度在創(chuàng)建后是固定的(但是是動態(tài)的,即它可以依賴于運(yùn)行時參數(shù))。
動態(tài)存儲數(shù)組 和bytes
(not string
) 有一個名為的成員函數(shù)push()
,您可以使用它在數(shù)組末尾附加一個零初始化元素。它返回對元素的引用,以便可以像 或一樣使用它。x.push().t = 2
x.push() = b
動態(tài)存儲數(shù)組 和bytes
(not string
) 有一個名為的成員函數(shù)push(x)
,您可以使用它在數(shù)組末尾附加給定元素。該函數(shù)不返回任何內(nèi)容。
動態(tài)存儲數(shù)組 和bytes
(not string
) 有一個名為的成員函數(shù)pop()
,您可以使用該函數(shù)從數(shù)組末尾刪除一個元素。這也隱式調(diào)用刪除元素上的刪除。
筆記
通過調(diào)用增加存儲數(shù)組的長度push()
具有恒定的gas成本,因為存儲是零初始化的,而通過調(diào)用減少長度pop()
的成本取決于被刪除元素的“大小”。如果該元素是一個數(shù)組,它可能會非常昂貴,因為它包括顯式清除已刪除的元素,類似于對它們調(diào)用delete。
筆記
要在外部(而不是公共)函數(shù)中使用數(shù)組數(shù)組,您需要激活 ABI coder v2。
筆記
在拜占庭之前的 EVM 版本中,無法訪問函數(shù)調(diào)用返回的動態(tài)數(shù)組。如果您調(diào)用返回動態(tài)數(shù)組的函數(shù),請確保使用設(shè)置為拜占庭模式的 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; } }
更多建議: