跳至內容
0%

什麼是 Solidity arrays?

Alchemy team headshot

作者 Alchemy

發布於 2023年9月22日閱讀時間 2 分鐘

陣列是線性資料結構,用來儲存固定數量、相同資料型別的元素,這些元素被儲存在連續且相鄰的記憶體位置中。陣列中的每一個元素都可以透過其位置來指定,這個位置通常稱為索引。

陣列採用以索引為基礎的系統運作,從 0 到 n-1,其中 n 代表陣列的總大小。相較於建立多個相同型別的獨立變數,開發者只需要宣告一個所需大小的陣列,即可透過索引存取其中儲存的元素。

Solidity(Ethereum 及 EVM 相容區塊鏈上主要使用的 web3 程式語言)中,陣列可以是固定大小或動態大小的。作為一種物件導向程式語言,Solidity 支援智慧合約之間的繼承,多個合約可以被繼承到單一合約中。

若想更深入了解 Solidity 陣列,可以註冊 Alchemy University 的免費 Solidity 開發者課程

什麼是動態陣列和固定陣列?

動態陣列的大小在宣告時並未預先定義,這與具有預先定義大小的固定陣列不同。 隨著元素被系統性地加入,動態陣列的大小會隨之改變,而陣列的實際大小會在執行期間決定。

相對地,固定陣列有預先定義的大小,陣列中元素的數量不應超過陣列的大小。在少數未指定陣列大小的情況下,系統會建立一個「足夠大小」的陣列,這個「足夠」的大小指的是足以容納初始化內容的大小。

儲存陣列與記憶體陣列

在 Solidity 中,開發者需要考慮兩種類型的陣列:儲存陣列與記憶體陣列。

儲存陣列

儲存陣列通常宣告為狀態變數,可以是固定或動態大小。值得注意的是,長度為動態的儲存陣列可以調整大小,這表示它們可以使用 push() 和 pop() 函式,分別用於在陣列中新增與移除元素。

記憶體陣列

記憶體陣列是以 memory 作為其資料位置來宣告的。與儲存陣列類似,記憶體陣列在編譯時期也可以是固定或動態長度,但是它們在記憶體配置後就無法再調整大小。這表示 pop() 和 pull() Solidity 函式無法用於記憶體陣列。

固定大小的記憶體陣列在宣告時會自動配置——就像 uint256[5] memory numbers 這樣的敘述。然而,若要配置動態記憶體陣列,則需要使用 new 運算子。例如,你可以使用 uint256[] memory numbers = new uint256[](5)``;

注意: 使用陣列前務必先進行初始化,以取得可供使用的有效位址。

Solidity 陣列與 mapping 的比較

Solidity 中的 mapping 與陣列類似,都是用來儲存一組資料的參考型別。然而 mapping 的語法和結構相當不同,這使它能夠發揮獨特且重要的作用。mapping 是一個鍵值表,每個鍵和值都有各自預先定義的型別。可以把 mapping 想像成在 Solidity 智慧合約內建立一個空表格,等待被填入資料。

與陣列不同,mapping 沒有可取得的長度,鍵或值也不需要從初始化階段就「設定」好。此外,無法像對 Solidity 陣列那樣對 mapping 進行迴圈遍歷。

然而,從 mapping 中取得一筆資料,遠比從陣列中取得相同資料更有效率。從陣列中取得資料需要遍歷整個陣列,直到找到你要找的特定元素為止,而 mapping 則可以立即取得該資料。

從 mapping 取得資料所帶來的效能優勢,對於節省 Ethereum 智慧合約的 gas相當重要,因為在區塊鏈上修改資料的交易需要支付 gas 費用。因此,盡可能有效率地在智慧合約中儲存或取得資料,長期下來可以為開發者節省 ETH。

總結來說,如果你需要以例如 for 迴圈的方式遍歷一組資料,就應該使用陣列。如果不需要遍歷一組資料,而是可以根據已知的鍵來取得值,那麼就可以考慮使用 mapping。

宣告與初始化陣列

在 Solidity 中宣告陣列相對簡單。需要指定元素的資料型別以及元素的數量,陣列的大小應為正整數,且資料型別必須是 Solidity 中有效的型別。

例如,下方基本的指令會初始化陣列,一旦資料被插入後,就會顯示在 console.log 中:

陣列中的每一項稱為一個元素,每個元素都可以透過「n - 1」這個函式,以其數字索引來存取,其中 n 代表元素編號。

一般來說,元素相關的編號從 0 開始。這表示第 10 個元素會在索引 9 處被存取,第 11 個元素則在索引 10 處,依此類推。

關於相同資料型別的限制很重要,因為陣列是儲存在連續的記憶體格中,這代表每個格子都必須是相同的型別,因此也必須是相同的大小。

Struct

Struct 讓程式設計師可以定義自己的資料型別。一旦定義了 struct,它就可以作為狀態變數使用,或用於許多其他函式中,可以使用位置引數或關鍵字的方式來使用。第二種方式可以避免需要記住 struct 中所包含成員的順序。

以下是一個新 struct 的範例:

Mapping

Mapping 可以視為鍵與值的儲存方式,每一個可能的鍵都存在,而任何值都可以透過該鍵在一次操作中設定或取得。KeyType 可以是任何內建的值型別(例如 bytes、string,或任何 contract/enum 型別)。ValueType 則可以是任何型別,包括 mapping、陣列及 struct。

Mapping 可以按以下方式宣告:

注意:mapping 變數唯一允許的資料位置是 storage。

Member

要在陣列中找到特定成員,意味著要搜尋整個陣列直到找到該成員為止。開發者應該了解兩個重要的函式:lengthpush

  • Length——回傳陣列的大小,也可以用來改變動態陣列的大小。
  • Push——讓開發者可以直接在動態儲存陣列的末端附加一個元素,並回傳陣列新的長度。

Solidity 陣列疑難排解

開發者偶爾可能會遇到編譯錯誤,這些錯誤可能源自於對宣告、建立及初始化陣列規則的誤解。以下是排解 Solidity 陣列常見錯誤的一些高階方法:

1. Array.length

array.length 函式讓開發者可以檢查陣列中存在的元素數量。記憶體陣列的大小在宣告時就已固定,而動態陣列則是在執行期間定義的,因此進行操作時需要用到 length。

2. Abi.encode 與 abi.encodePacked

當呼叫外部合約的函式時,EVM 會計算出一個包含函式簽章與引數的 bytes 緩衝區,而序列化引數的方式有兩種:abi.encodeabi.encodePacked

Abi.encode 依照 ABI 規範來編碼其參數。實際上,ABI 的設計目的是用來呼叫合約,其中參數會被填補至 32 bytes。如果是要呼叫合約,最有可能使用的就是這個函式。

相比之下,abi.encodePacked 使用該型別所需的最小空間來編碼其參數。編碼一個 unit8 只會用到 1 byte,當你想節省一些空間且不打算呼叫合約時,就會使用這個函式。

3. Byte[] 或 bytes

這兩種陣列型別都可以儲存任意長度的原始位元組資料。byte\[\] 與 bytes 的差別在於,bytes\[\] 遵循陣列型別的規則,而 Solidity 中記憶體陣列的元素永遠佔用 32 bytes 的倍數空間。這表示如果某個元素不足 32 bytes 的倍數,就會被填補直到達到所需的大小。舉例來說,在使用 byte 陣列時,每個元素可能會浪費多達 31 bytes,而使用 bytes 陣列或 string 則不會發生這種情況。

4. String

這種特定的動態陣列是 UTF-8 資料型別,其功能與其他程式語言不同,Solidity 並未提供取得字串長度、串接兩個字串或比較兩個字串的函式。可以使用 bytes[] 快速將 string 轉換為位元組陣列。

5. Solidity 陣列的 null

雖然 undefinednullnilnone 等概念在 Python 和 JavaScript 等其他程式語言中固有存在,但在 Solidity 中並不存在這樣的概念。開發者可以將其稱為零值或預設值的概念,因為每個值一旦被建立,就會在記憶體中佔有一個位置,因此必然包含某個內容。

如何進一步學習 Solidity 陣列

若想進一步了解 Solidity 程式語言,並熟悉陣列及其他基礎主題,歡迎前往 Alchemy University 的 Ethereum bootcamp。Ethereum bootcamp 是一門免費、為期 7 週的程式設計課程,內含引導式影片與實作專案。

如果開發者是程式設計的新手,Alchemy 的 3 週 JavaScript for Ethereum 課程是開始 Ethereum bootcamp 前很好的先修課程。

Background gradient

打造區塊鏈魔法

Alchemy 結合最強大的 Web3 開發者產品與工具,並提供資源、社群與卓越的支援。