---
# System prepended metadata

title: 複合數據類型
tags: [Solidity]

---

# 複合數據類型
## 映射（Mappings）
映射在Solidity中是一種特殊的數據結構，用於存儲鍵值對（key-value pairs）。它類似於其他編程語言中的哈希表或字典，但在Solidity中有其獨特的特性和限制。
### 語法
```solidity=
複製程式碼
mapping(keyType => valueType) [visibility] mappingName;
```
* keyType：鍵的類型，可以是整數、地址、字串等基本類型。但不能是映射、結構、動態數組等複雜類型。
* valueType：值的類型，可以是任何類型，包括基本類型、結構、數組甚至其他映射。
* visibility：可見性修飾符，如public、private等。
### 範例
```solidity=
mapping(address => uint256) public balances;
```
* balances：映射每個地址（address）到其對應的餘額（uint256）。
* public：可見性修飾符，允許外部訪問。

### 使用方法
#### 設置值：
```solidity=
balances[msg.sender] = 100;
```
將當前呼叫者的餘額設置為100。
#### 獲取值：

```solidity=
uint256 userBalance = balances[userAddress];
```
獲取userAddress的餘額。

#### 刪除值：
```solidity=
delete balances[userAddress];
```
將userAddress的餘額重置為類型的默認值，對於uint256即為0。

### 特點與注意事項
1. 默認值：如果鍵不存在，映射會返回值類型的默認值。例如，uint256的默認值是0。
1. 不可遍歷：映射無法被遍歷，因為Solidity無法獲取所有鍵的列表。
1. Storage位置：映射只能宣告在storage中，不能在memory或calldata中使用。
### 應用場景
1. Token餘額：追蹤每個地址的代幣餘額。
1. 授權：管理地址與權限之間的關係。
1. 資料關聯：將唯一的鍵（如ID）映射到特定的資料結構。
### 重要概念詳解
#### 映射的不可遍歷性
由於映射無法遍歷，如果需要獲取所有鍵，可以考慮：
1. 輔助數組：在每次更新映射時，同步更新一個數組，儲存所有的鍵。
1. 事件（Events）：在更新時觸發事件，讓前端或監聽器記錄相關資訊。

## 結構（Structs）
結構允許您定義自定義的複合數據類型，將多個相關的變數組合在一起，方便管理和操作。

### 語法
```solidity=
struct StructName {
    type1 member1;
    type2 member2;
    // 更多成員...
}
```
### 範例
```solidity=
struct Person {
    string name;
    uint256 age;
}

Person[] public people;
```
* Person：定義了一個包含name和age的結構。
* people：一個Person類型的動態數組，存儲多人資訊。
### 使用方法
#### 新增資料：
```solidity=
function addPerson(string memory _name, uint256 _age) public {
    people.push(Person(_name, _age));
}
```
或
```solidity=
people.push(Person({name: _name, age: _age}));
```
#### 讀取資料：

```solidity=
Person memory person = people[0];
string memory name = person.name;
uint256 age = person.age;
```
### 特點與注意事項
1. 數據位置：在函數中使用結構時，需要指定數據位置，如memory或storage。
1. 嵌套結構：結構可以包含其他結構，但需要注意深層次的嵌套可能增加複雜性和Gas成本。
1. 限制：結構不能包含var、mapping類型的成員（在某些情況下可能會有限制）。
### 應用場景
* 資料模型：定義複雜的資料結構，如用戶資訊、產品詳細等。
* 狀態管理：追蹤合約內的狀態和關聯數據。
### 重要概念
#### 結構的深層拷貝與引用
* Storage與Memory：在storage中，結構是引用類型；在memory中，是值類型（深拷貝）。
* 修改數據：在函數內修改結構時，需要注意數據位置，否則可能不會影響到原始資料。

## 枚舉（Enums）
**枚舉（Enumeration）** 是在Solidity中用於定義一組具名常量的數據類型。它可以讓您的代碼更具可讀性和可維護性，特別是在需要使用一組預定義選項的情況下。

### 語法
```solidity=
enum EnumName {
    Option1,
    Option2,
    Option3,
    // 更多選項...
}
```
* EnumName：枚舉的名稱。
* Option：枚舉中的各個選項，默認從0開始自增。
### 範例
```solidity=
enum Status {
    Pending,
    Shipped,
    Delivered,
    Canceled
}

Status public orderStatus;
```
* Status：定義了一個包含四個狀態的枚舉。
* orderStatus：一個公開的狀態變量，用於追蹤訂單的狀態。
### 使用方法
設置枚舉值：
```solidity=
function setStatus(Status _status) public {
    orderStatus = _status;
}
```
或者直接使用枚舉的選項：
```solidity=
orderStatus = Status.Shipped;
```
比較枚舉值：
```solidity=
if (orderStatus == Status.Delivered) {
    // 執行相應的邏輯
}
```
獲取枚舉的整數值：
```solidity=
uint256 statusValue = uint256(orderStatus);
```
從整數轉換為枚舉值（不建議，可能導致錯誤）：
```solidity=
orderStatus = Status(2); // 將狀態設置為Delivered
```
### 特點與注意事項
* 默認值：枚舉類型的默認值為其第一個選項，索引為0。
* 存儲方式：在區塊鏈中，枚舉實際上被存儲為uint8（如果選項少於256個）。
* 類型安全：枚舉提供了類型安全性，防止使用無效的值。
* 限制：不能直接獲取枚舉的長度或列出所有選項。
### 應用場景
* 狀態機：追蹤合約或流程的當前狀態，如訂單狀態、投票階段等。
* 選項設置：定義一組預定的配置或模式。
* 角色管理：管理用戶或地址的角色類型。
### 重要概念
#### 枚舉的底層表示
* 整數表示：每個枚舉選項在底層都被映射為整數，從0開始遞增。
* 類型轉換：可以將枚舉類型顯式轉換為uint類型，但需謹慎。
### 最佳實踐
* 明確命名：使用清晰、描述性的枚舉名稱和選項名稱。
* 避免數值依賴：不要在代碼中依賴枚舉的底層整數值，應該使用枚舉選項本身。
* 限制範圍：在可能的情況下，添加檢查以確保枚舉值在有效範圍內，特別是在從外部輸入獲取值時。
* 避免過度使用：僅在選項數量有限且固定的情況下使用枚舉。
### 注意事項
#### 升級合約時的問題
* 枚舉的順序：在升級合約或重新部署時，切勿更改枚舉選項的順序，因為這會改變底層整數值，導致數據不一致。
* 新增選項：如果需要新增枚舉選項，應該只在末尾添加，並確保與現有數據兼容。
#### 枚舉的最大選項數
* 選項限制：由於枚舉默認使用uint8，因此最多可以有256個選項。如果需要更多選項，可能需要考慮其他數據結構。
## 陣列（Arrays）
陣列是Solidity中用於存儲相同類型元素的集合。陣列可以是固定長度的，也可以是動態長度的，並且可以是一維或多維的。

### 語法

```solidity=
//固定長度陣列
type[length] arrayName;

//動態長度陣列
type[] arrayName;
```

### 使用方法
#### 固定長度陣列
```solidity=
//初始化
fixedArray = [10, 20, 30, 40, 50];

//存取元素
uint256 firstElement = fixedArray[0]; // 10

//修改元素
fixedArray[2] = 35; // 將第三個元素改為35
```
#### 動態長度陣列
```solidity=
//新增元素
dynamicArray.push(100);

//移除元素
dynamicArray.pop(); // 移除最後一個元素

//獲取長度
uint256 length = dynamicArray.length;

//刪除元素
delete dynamicArray[1]; // 將索引1的元素重置為默認值
```
### 二維陣列
```solidity=
//新增子陣列
twoDArray.push([1, 2, 3]);

//存取元素
uint256 value = twoDArray[0][1]; // 2
```

### 特點與注意事項
* 索引從0開始：陣列的索引是從0開始的。
* 越界訪問：嘗試訪問不存在的索引會導致運行時錯誤。
* 類型必須一致：陣列中的所有元素必須是相同的類型。
* 長度不可變：固定長度陣列的長度在編譯時就已確定，無法在運行時修改。

### 重要概念
#### push()
>用途：在陣列末尾新增元素。
#### pop()
>用途：移除陣列末尾的元素。
>注意：僅適用於動態陣列，且在storage中。
### 應用場景
* 批量處理：如批量轉賬、批量操作。
* 資料存儲：存儲同類型的資料集合，如地址列表、交易記錄等。
* 多維數據：處理矩陣、表格等複雜數據結構。
### 最佳實踐
* 限制陣列長度：避免無限制地增長陣列，可能導致Gas成本過高。
* 優化Gas成本：在可能的情況下，使用映射替代大型陣列。
* 小心數據位置：明確指定memory或storage，避免意外的數據拷貝或引用。
* 驗證索引：在訪問陣列元素前，檢查索引是否在有效範圍內。
### 注意事項
#### 陣列的數據位置與拷貝
* memory與storage的區別：memory中的陣列是臨時的，函數執行完後即被銷毀；storage中的陣列是永久存儲在區塊鏈上的。
* 拷貝行為：將storage陣列賦值給memory陣列時，會創建一個副本。
#### 不支持的操作
* 不支持的內置函數：Solidity的陣列不支持如sort()、concat()等高級函數，需要自行實現。
* 無法直接刪除指定索引的元素並縮短陣列長度：使用delete關鍵字只會重置元素值，但不會改變陣列長度。
* 在Solidity 0.6.0之後，已不建議直接修改length屬性

## 陣列 VS 映射
| **比較項目**   | **映射（Mapping）**                                                                                                      | **陣列（Array）**                                                                                                                   |
|----------------|---------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| **定義**       | 一種儲存鍵值對（key-value pair）的資料結構，允許透過鍵快速存取對應的值。                                                    | 一組有序的元素集合，透過索引（index）來存取個別元素。                                                                                |
| **語法**       | `mapping(keyType => valueType) mappingName`                                                                                | - 動態陣列：`valueType[] arrayName`<br>- 固定大小陣列： `valueType[fixedSize] arrayName`                                             |
| **存取方式**   | 使用鍵來存取或修改值，例如：`mappingName[key] = value;`                                                                     | 使用索引來存取或修改元素，例如：`arrayName[index] = value;`                                                                          |
| **初始化**     | 所有可能的鍵在預設情況下皆存在，且其對應的值為型別的默認值（如數字為 0，布林值為 false）。                                    | 需要明確初始化，每個元素未被賦值前不會自動存在。                                                                                     |
| **可列舉性**   | 無法直接遍歷映射中的所有鍵或值，需要額外的輔助資料結構（如陣列）來記錄鍵的列表。                                              | 可以使用迴圈遍歷陣列中的所有元素。                                                                                                  |
| **長度獲取**   | 無法獲取映射的大小（鍵值對的數量）。                                                                                       | 可以使用 `arrayName.length` 獲取陣列的長度。                                                                                        |
| **新增元素**   | 直接為新鍵賦值即可新增鍵值對，例如：`mappingName[newKey] = value;`                                                          | - 動態陣列：使用 `arrayName.push(value);` 新增元素。<br>- 固定大小陣列：無法新增，大小固定。                                         |
| **刪除元素**   | 使用 `delete mappingName[key];` 刪除指定的鍵值對。                                                                          | - 使用 `delete arrayName[index];` 將元素重置為默認值，但不改變陣列長度。<br>- 動態陣列可使用 `pop()` 刪除最後一個元素，減少陣列長度。 |
| **適用場景**   | - 儲存關聯性資料，例如：帳戶地址到餘額的對應關係。<br>- 快速查找某個鍵是否存在。                                                | - 儲存有序數據，例如：投票者名單、交易記錄。<br>- 需要遍歷或排序的數據集合。                                                       |
| **限制**       | - 無法遍歷或獲取所有鍵。<br>- 鍵類型有限制，只能使用值型別（不可使用映射、陣列等複雜型別）。                                    | - 固定大小陣列無法動態增減元素。<br>- 操作大型陣列可能會消耗較多 Gas。                                                             |
| **Gas 效率**   | - 單次存取成本較低，適合頻繁查詢。<br>- 刪除操作實際上是將值重置為默認值，成本較低。                                            | - 動態陣列的 `push` 和 `pop` 操作成本較低。<br>- 遍歷或修改大量元素可能會消耗較多 Gas。                                            |
| **安全性考量** | - 若未妥善管理，可能會有鍵值覆蓋的風險。<br>- 需要防範重入攻擊時的資料完整性問題。                                              | - 需注意陣列越界（index out of bounds）問題。<br>- 操作陣列時需考慮 Gas 上限，以避免交易失敗。                                      |
| **範例應用**   | ```mapping(address => uint256) public balances;``` | ```uint256[] public voterIds;```|
