本篇預計閱讀時間 15 分鐘, 實作時間 30 分鐘。
在開始之前先來思考底下的練習題,閱讀後即可練習完成。
練習題
-
練習題 1
-
如何利用迴圈找出陣列 [34, 231, 4534, 231, 23, 45, 80, 67798, 67783] 的最小值與最大值。
-
-
練習題 2
-
如何利用迴圈找出 [34, 231, 4534, 231, 23, 45, 80, 67798, 67783] 有幾個奇數。
-
-
練習題 3
-
如何利用迴圈找出 [2, 256, 4534, 1024, 23, 45, 0, 2048, 67783, 8, 64] 有幾個為 2 的正整數次方。
-
-
練習題 4
-
如何利用 mapping 建立一個名為 whitelist 的容器,並儲存三筆 address 映射 bool 資料。
-
-
練習題 5
-
小明想要架設一個交易多餘電力的合約,在合約中宣告一個 struct 表示當前加入此電力系統的會員們,並在內宣告一個 mapping 需要由 address 映射 uint,代表他們當前擁有可販賣的多餘電力。
-
-
練習題 6
-
承上題,宣告一個 enum 分別代表三種會員電力系統的狀態:malfunction, insufficient, surplus。
-
-
練習題 7
-
承上題,在練習題 5 的 struct 中再宣告一個 mapping 由 address 映射 uint,用於儲存每位會員的電力系統狀態。
-
Array
一個程式語言中最經典的資料結構莫過於 array
了,陣列的性質如下:
-
擁有兩個成員:
.length
,.push(element)
-
可為固定或動態的長度,取決於我們是否宣告陣列的長度。
-
T[][5]
是五個動態陣列長度的組合(reverse notation)
pragma solidity ^0.8.11; contract MyArray {
uint256[] arr_1;
uint256[] arr_2 = [3,2,4];
uint256[5] arr_3;
uint[][] array2D = [ [1,2,3], [4,5,6] ];
function getValueOfIndex(uint256 _index) public view returns (uint256) {
return arr_2[_index];
}
// 在一個陣列的最後加入一個元素
function addToArray(uint256 _value) public {arr_2.push(_value);
}
// 調用一個陣列的長度
function valueCount() public view returns(uint) {return arr_3.length;}}
動態記憶體配置
uint[] memory arr = new uint[](3);
如果我們訪問了超過記憶體的值,Solidity 不會向其他語言一樣丟 Runtime Error 回來,而是會返回該型態的預設值。那我們所謂的非法訪問其實是訪問一個負的 index。
Mapping
mapping
在 Solidity 的運作就像是 python 的 dict
或常見的資料結構 hash-map。
圖 6-1 雜湊表
在圖 6-1 中的例子,當我們 call "k1" 的時候,這個 mapping 就會回傳 key "k1" 指向的值,也就是 "AAA,BBB,CCC"。此外,我們還可以儲存新的資料進去 mapping
。
mapping(uint => string) public names;
在以上這段語法中,每一個 key
在這個名為 names
的 mapping
中,是以 uint
形態存在。而相對應的每個 value
型態為 string
。
宣告語法為:mapping(_keyType => _valueType) name
;
-
_keyType
可以是任何的元素型態,也就是說它可以為任何的既有數值型態包含bytes
和string
-
_valueType
可以是任何的型態,包含複雜的資料結構,甚至是mapping
Mapping具有以下的特色:
-
所有元素都會被初始化
-
不存在長度,如果需要紀錄長度得額外宣告一個變數
-
mapping
的 Public state variables 會成為Getter Function -
如果需要可遍歷的
mapping
可以藉由導入一些libraries
達到。
pragma solidity ^0.8.11; contract MyMappings {mapping(uint256 => address) nfts; uint256 counter = 0;
function getOwnerOfNFT(uint256 _id) public view returns (address) {
return nfts[_id];
}
function mintNFT() public {
nfts[counter] = msg.sender;counter++;
}
}
如同二維陣列,我們也可以宣告 Nested mapping 來做為類似的用途:
mapping(address => mapping(address => bool)) a;
既然用途很像,我們什麼時候用 array
,什麼時候用 mapping
呢?
我會用使用情況來區分我們要使用哪種資料結構:
-
當常常需要遍歷(iterate)這個資料結構時,建議使用
array
-
如果我們需要常常查看某些特定的鍵值(key)時,建議使用
mapping
需要注意的是,我們沒辦法使用動態記憶體配置來宣告 mapping。同時 mapping 也不具有可以直接呼叫長度的成員函式。
Structs
宣告 struct
在這裡的用途和 C 的語言一樣,如果是沒有學過 C/C++ 的話,可以把 struct
理解成一種比較低階的 C++, python 的 class
。
定義一個 struct
之後會產生一種新的自定義、抽象、複雜的的資料型態。
我們就可以藉由這個資料型態宣告我們自己的結構變數。
struct User {
address id;
string name;
}
//Method 1 依照順序宣告 (argument order matters)
User("0xAio90....", "Mike");
//Method 2 註明則不必按照順序宣告 (argument order does not
matter)
User({name: "Mike", id: "0xAio90...."});
就像是我們定義一個新的形態是車子,並且定義一台車子裡面要有的資料有:持有者(address
)、品牌(string
)、公升數(uint
)。
此後就可以宣告一個資料型態為車子的變數 myCar
,並定義這個變數裡面的每個型態。
struct Car {
address owner;
string brand;
uint cc;}
myCar = Car({string: "Toyota", address: "0xAio90....", cc: 2000});
Structs 具有以下的性質:
-
在 Solidity 中不可以使用遞迴性的方式宣告變數,也就是說
struct
的成員的型別不可是該struct
。 -
以 structs 取代 objects 可以達到 gas consumption
-
可以組合
struct
和mapping
或甚至其他容器來建置些複雜的資料結構,像是以下例子是使用巢狀的資料結構(Nested Data Structures)來達成目的。
struct User {
uint id;
string name;
}
uint[] userIds;
mapping(uint => User) users;
Enum
枚舉(enum
)和 struct
非常類似,只是 enum
中的每個參數會自動從 0 開始依序初始化。
Enum 具有以下的性質:
-
內部已有整數型態的元素存在
-
可以存進 uint8 的型態,如果數值數量介於 0~255,若大於 256 個值則可宣告為 uint16
enum ActionChoices { GoLeft, GoRight, GoStraight, SitsStill }ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.Gostraight;
在 Solidity 中 enum
更常被用來進行模型選擇(model choice),或者追蹤狀態(track of state)。
pragma solidity ^0.8.11;
contract MyEnums {
enum Rarity {
original, // 0
rare, // 1
super_rare // 2
}
Rarity public rarity;
constructor() {
rarity = Rarity.rare;
}
// 我們可以直接將一個型態為該 enum 的變數賦值為某特定屬性
function makeSuperRare() public {
rarity = Rarity.super_rare;
}
// 我們可以藉由 enum 初始化的 INDEX 來進行賦值
function set(Rarity _rarity) public {
rarity = _rarity;}
// 我們可以藉由 delete 來重置這個 enum 成他的初始值 0function reset() public {delete rarity;
}
}
struct
和 enum
的差別:
-
Struct 用於表示一個複雜變數
-
Enum 用於表示一個單一變數的不同狀態或值
練習題解答
-
練習題解答 1
-
利用迴圈找出陣列 [34, 231, 4534, 231, 23, 45, 80, 67798, 67783] 的最小值與最大值。
-
提供 2 個方法來取最大值與最小值,分別是根據型別的定義和 type().min 或 .max 這兩者。
pragma solidity ^0.8.11;
contract Practice{
uint[] array = [34, 231, 4534, 231, 23, 45, 80, 67798, 67783];
function getMin() public view returns(uint){
uint min = 2**256 - 1;
for(uint i = 0; i < array.length; i++){
if(array[i] < min){
min = array[i];
}
}
return min;
}
function getMax() public view returns(uint){
uint max = type(uint256).min;
// for(uint i = 0; i < array.length; i++){
// if(array[i] > max){
// max = array[i];
// }
//}
-
練習題解答 2
-
利用迴圈找出 [34, 231, 4534, 231, 23, 45, 80, 67798, 67783] 有幾個奇數。
-
pragma solidity ^0.8.11; contract Practice{
uint[] array = [34, 231, 4534, 231, 23, 45, 80, 67798, 67783];
function getOdd() public view returns(uint){ uint cnt = 0;
for(uint i = 0; i < array.length; i++){ if(array[i] % 2 != 0){
cnt++; }
}
return cnt; }
}
-
練習題解答 3
-
利用迴圈找出 [2, 256, 4534, 1024, 23, 45, 0, 2048, 67783, 8, 64] 有幾個為 2 的正整數次方。
-
pragma solidity ^0.8.11;
contract Practice{
uint[] array = [2, 256, 4534, 1024, 23, 45, 0, 2048, 67783, 8, 64];
function getEven() public view returns(uint){
uint cnt = 0;
for(uint i = 0; i < array.length; i++){
if(array[i] % 2 == 0){
cnt++;
}
}
return cnt;
}
}
這個例子跟取得偶數的函式是一樣的。
-
練習題解答 4
-
利用 mapping 建立一個名為 whitelist 的容器,並儲存三筆 address 映射bool 資料。
-
pragma solidity ^0.8.11;
contract Practice{
mapping(address => bool) public whitelist;
}
-
練習題解答 5
-
小明想要架設一個交易多餘電力的合約,在合約中宣告一個 struct 表示當前加入此電力系統的會員們,並在內宣告一個 mapping 需要由 address映射 uint,代表他們當前擁有可販賣的多餘電力。
-
-
練習題解答 6 與 7
-
宣告一個 enum 分別代表三種會員電力系統的狀態:malfunction, insufficient, surplus。
- 在我們 Practice 5 的 struct 中再宣告一個 mapping 由 address 映射 uint,用於儲存每位會員的電力系統狀態。
-
pragma solidity ^0.8.11;
contract Practice{
enum status {malfunction, insufficient, surplus}
struct System {
mapping(address => bool) whitelist;
mapping(address => uint) electricity;
mapping(address => status) UserStatus;
}
}