在上篇文章中,我們可以看到的資料位置是如何工作的(傳送門:區塊鏈研究實驗室 | 使用Solidity開發智慧合約(三)),這一篇我們將繼續學習Solidity中的變數的過程。這次,我們將重點放在引用型別上,該引用型別應顯式指定資料位置,正如我們在前幾篇文章中提到的那樣。我們還將看到如何定義對映,列舉和常量。
陣列
在Solidity中,我們有兩種型別的陣列:儲存陣列和記憶體陣列。
1.儲存陣列:
這些陣列被宣告為狀態變數,並且可以具有固定長度或動態長度。
可以調整具有動態長度的儲存陣列的大小,這意味著它們可以訪問push()和pop()方法。
pragma solidity ^0.7.0;
contract A {
uint256[] public numbers;// dynamic length array
address[10] private users; // fixed length array
uint8 users_count;
functionaddUser(address _user)external{
require(users_count < 10, "number of users is limited to 10");
users[users_count] = _user;
users_count++;
}
functionaddNumber(uint256 _number)external{
numbers.push(_number);
}
}
2.記憶體陣列:
這些陣列memory以其資料位置宣告。它們也可以具有固定長度或動態長度,但是動態大小的記憶體陣列無法調整大小(即,不能呼叫push()和pop()方法)。陣列的大小必須預先計算。
使用new關鍵字宣告動態大小的記憶體陣列,如下所示:
Type[] memory a = new Type[](size)
pragma solidity ^0.7.0;
contract B {
functioncreateMemArrays() externalview{
uint256[20] memory numbers;
numbers[0] = 1;
numbers[1] = 2;
uint256 users_num = numbers.length;
address[users_num] memory users1; // ERROR : expected integer literal
// or constant expression
address[] memory users2 = new address[](users_num);
users2[0] = msg.sender; // OK
users2.push(msg.sender); // ERROR : member push is not available
}
}
這裡要提到的另一點是關於何時使用記憶體陣列並編寫如下內容:
uint256[] memory array;
array[0] = 1;
您不會收到任何警告,但最終將獲得無效的操作碼,因為array根據記憶體中佈局的說明,該操作碼將指向零插槽,因此切勿將其寫入。請記住,在使用陣列之前,請務必先對其進行初始化,以便獲取有效的地址。
陣列切片
陣列切片只能與calldata陣列一起使用,並寫為x[start:end]。切片的第一個元素為x[start],最後一個元素為x[end - 1]。
兩個start和end是可選的:start預設為0與end預設為陣列的長度。
特殊的動態尺寸陣列
1.byte[] 或者 bytes
這些陣列可以儲存任意長度的原始位元組資料。兩者之間的區別在於byte[]遵循陣列型別的規則,並且如本部分文件所述, Solidity中的記憶體陣列中的元素始終佔據32位元組的倍數。這意味著,如果元素的長度小於32位元組的倍數,則將對其進行填充,直到其適合所需的大小為止。
在byte陣列的情況下,這將浪費每個元素31個位元組,而對於bytes或則不然string。我會提醒您,從記憶體中讀取或寫入一個字(32位元組)會消耗3氣,這就是為什麼建議使用bytes而不是的原因byte[]。
2.string
string是UTF-8資料的動態陣列。與其他語言相反,stringSolidity不提供獲取字串長度或執行兩個字串的串聯或比較的功能(需要使用庫)。
可以使用將字串轉換為位元組陣列bytes(<string>)。這將返回字串的UTF-8表示形式的低階位元組。
注意:可以將一個字元編碼為一個以上的位元組,這意味著位元組陣列的長度不一定是字串的長度。
3.字串與 bytes
該文件的大多數示例都使用bytes32代替string,並且還很明確地使用值型別bytes1來bytes32限制字串的位元組數,因為它便宜得多。
結構
與C和C ++一樣,結構允許您定義自己的型別,如下所示:
struct Donation {
uint256 value;
uint256 date;
}
一旦定義了結構,就可以開始將其用作狀態變數或在函式中使用。
為了初始化一個結構,我們有兩種方法:
1.使用位置引數:
Donation donation = Donation(
msg.value,
block.timestamp
);
2.使用關鍵字:
Donation donation = Donation(
msg.value,
block.timestamp
);
第二種方法將避免我們必須記住結構成員的順序,因此它可能比第一種有用。
該結構的成員使用點來訪問:
uint256 donationDate = myDonation.date;
“雖然結構本身可以是對映成員的值型別,也可以包含其型別的動態大小的陣列,但結構不可能包含其自身型別的成員。這種限制是必要的,因為結構的大小必須是有限的。”
對應關係
您可以將對映視為大量的鍵/值儲存,其中每個可能的鍵都存在,並且可以使用該鍵一鍵設定或檢索任何值。
對映宣告如下:
mapping( KeyType => ValueType) VariableName
該KeyType可以是任何內建值型別(我們看到的那些部分1),位元組或字串,或任何合約或列舉型別。的ValueType可以是任何型別的,包括對映,陣列和結構。
這裡要提到的一件事是,對映變數唯一允許的資料位置是storage,這意味著您只能將它們宣告為狀態變數,儲存指標或庫函式的引數。
列舉
列舉可讓您將自定義型別下的相關值分組,如以下示例所示:
enum Color { green , blue, red }
enum使用以下語法可以訪問值:
Color defaultColor = Color.green;
注意:也可以在合同或庫定義之外的檔案級別上宣告列舉。
常量和不可變狀態變數
狀態變數可以宣告為constant或immutable。在這兩種情況下,在構造合同之後都無法修改變數。對於constant變數,該值必須在編譯時固定,而對於immutable,它仍可以在構造時分配。
編譯器不會為這些變數保留儲存槽,並且每次出現都會由相應的值替換。
常量使用關鍵字宣告constant:
uint256 constant maxParticipants = 10;
對於不可變狀態變數,使用關鍵字宣告它們immutable:
contractC {
addressimmutable owner = msg.sender;
uint256immutable maxBalance;
constructor(uint256_maxBalance){
maxBalance = _maxbalance;
}
}
您可以在本節的文件中找到有關常量和不可變狀態變數的更多詳細資訊。
注意:也可以constant在檔案級別定義變數。
刪除關鍵字
我要補充的最後一件事是delete在Solidity中的使用。
它用於將變數設定為其初始值,這意味著該語句的delete a行為如下:
對於整數,它等效於a = 0。
對於陣列,它分配長度為零的動態陣列或長度相同的靜態陣列,並將所有元素設定為其初始值。
delete a[x]刪除x陣列索引處的專案,並保持所有其他元素和陣列長度不變。這尤其意味著它在陣列中留有間隙。
對於結構,它將為所有成員重置分配一個結構。
delete 對對映沒有影響(因為對映的鍵可能是任意的,並且通常是未知的)。
練習時間:簡單
在本練習中,我們將建立一個用於管理使用者的合同。
以下是說明:
建立一個新檔案並新增一個名為Crud的合同。
建立一個名為User的結構,其中包含使用者的ID和名稱。
新增兩個狀態變數並將其公開:1)動態的使用者陣列;2)每次建立新使用者時將增加的id。
下一步是建立Crud函式,但是由於我沒有向您介紹Solidity函式,因此我將為您提供宣告函式的語法。在下一篇文章中,我們將對它們進行詳細的討論:
函式function_name(<param_type><param_name>)<可見性><狀態可變性> [returns(<return_type>)] {...}
可見性可以是:公開,私有,內部,外部。
狀態可變性可以是:檢視,純淨,應付。
這是您將建立的功能的描述。
1.新增
可見性:公共
狀態可變性:空
該函式將使用者名稱作為引數,建立一個具有新ID的User例項(每次新增新使用者時ID都會自動遞增),並將新建立的使用者新增到陣列中。
2.閱讀
可見性:公共
狀態可變性:檢視
此函式獲取要查詢的使用者的ID,如果找到則返回使用者名稱,否則返回使用者名稱(有關稍後的異常處理)。
3.更新
可見性:公共
狀態可變性:空
此函式將獲取使用者的ID和新名稱,然後在找到相應使用者時對其進行更新,或者在不存在該使用者的情況下還原該事務。
4.銷燬
可見性:公共
狀態可變性:空
此函式將使用者的ID刪除,如果找到,則將其從陣列中刪除;如果使用者不存在,則還原事務。
提示:由於最後三個函式都需要查詢使用者,因此您將需要建立一個私有函式,該函式將獲取使用者的ID並在找到時返回其在陣列中的索引,以避免重複相同的程式碼。
課後聯絡答案:
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
contract Crud {
structUser {
uint256 id;
string name;
}
User[] public users;
uint256 public nextId = 1;
function add(string memory name)public{
User memory user = User({id : nextId, name : name});
users.push(user);
nextId++;
}
function read(uint256 id)public view returns(string memory){
uint256 i = find(id);
return users[i].name;
}
function update(uint256 id, string memory newName)public{
uint256 i = find(id);
users[i].name = newName;
}
function destroy(uint256 id)public{
uint256 i = find(id);
delete users[i];
}
function find(uint256 id)private view returns(uint256){
for(uint256 i = 0; i< users.length; i++) {
if(users[i].id == id)
return i;
}
revert("User not found");
}
}
至此,我們對變數的討論結束了。下次,我們將研究功能以及如何在Solidity中使用它們。
作者:鏈三豐,來源:區塊鏈研究實驗室