基礎學習使用
remix:ide
Remix - Ethereum IDE
evm:ethreum? virtual machine? evm字節碼
強類型腳本語言? ?compile? =>evm? bytescode =>evm
hello的樣例
聲明的關鍵字:contract
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
//pragma solidity ^0.8.0;//大于8.0版本
//pragma solidity >=0.8.0 <0.9.0;//大于等于8.0版本,小于9.0版本contract helloDto {string public hello="hello 3.0";
}
一、EVM 的數據結構
-
基于棧的架構:EVM 使用棧來存儲數據,最大支持 1024 個棧元素,每個元素是 256 位的字(word),所有計算都在棧上完成。
-
三類存儲結構:
- 程序代碼存儲區(ROM)(棧):1024個solt,每個solt是32個字節>=256bit,不可變,存儲智能合約的字節碼。
- 內存(Memory):可變,臨時存儲執行期間的數據,隨調用結束而清除。
- 存儲(Storage):持久化存儲,每個智能合約都有一個唯一的存儲區,存儲合約狀態。
二、 EVM 中的兩種值類型
-
基本類型(Value Types)
- 定義:直接存儲值的數據類型,變量之間賦值是復制值本身。
- 常見類型:
uint
,?int
,?bool
,?address
,?bytes1
?到?bytes32
- 存儲特性:
- 分配在固定的?
storage slot
- 賦值是值復制(copy by value)
- 分配在固定的?
-
引用類型(Reference Types)
-
定義:存儲的是對數據的“引用”或“指針”,賦值傳的是地址。
-
常見類型:
array
(數組),mapping
(映射),struct
(結構體) -
存儲特性:
-
變量本身保存的是對實際數據的引用
-
賦值是引用復制(copy by reference)
-
需要?
keccak256(slot)
?來定位實際數據地址(尤其是動態結構)
-
-
evm特性
交易流程圖
1. 用戶發起交易|↓
2. 創建新的 EVM 實例|↓
3. 加載合約字節碼|↓
4. 分配 Stack 空間(1024 slots)|↓
5. 執行合約代碼|↓
6. 更新區塊鏈狀態(如果需要)|↓
7. 銷毀 EVM 實例
詳細說明
-
用戶發起交易
- 用戶簽名交易
- 設定 gas limit 和 gas price
- 指定目標合約地址和調用數據
-
創建新的 EVM 實例
- 為每筆交易創建獨立的 EVM 環境
- 初始化執行上下文
- 準備內存和存儲空間
-
加載合約字節碼
- 從區塊鏈狀態中讀取合約字節碼
- 將字節碼加載到 EVM 中
- 準備執行環境
-
分配 Stack 空間
- 分配 1024 個 slots
- 每個 slot 256 位
- 用于存儲臨時計算結果
-
執行合約代碼
- 逐條執行操作碼
- 進行狀態檢查和 gas 計算
- 處理函數調用和返回值
-
更新區塊鏈狀態
- 寫入存儲變更
- 更新賬戶余額
- 觸發事件日志
-
銷毀 EVM 實例
- 清理內存
- 釋放資源
- 返回執行結果
注意事項
- 整個過程是原子性的:要么全部成功,要么全部失敗
- Gas 限制貫穿整個執行過程
- 狀態變更只在交易成功執行后才會提交
EVM 存儲結構
1. Stack (棧)
+----------------+
| 基本類型的值 | <- 函數內的局部變量
| 引用的地址 | <- 指向其他存儲位置
+----------------+2. Memory (內存)
+----------------+
| 臨時數據 | <- 函數執行期間的臨時數據
| 函數參數 | <- memory 類型的參數
| 返回數據 | <- 函數返回值
+----------------+3. Storage (存儲)
+----------------+
| 狀態變量 | <- 合約的永久存儲數據
| 映射數據 | <- mapping 數據
| 數組數據 | <- storage 數組
+----------------+
EVM Stack (固定大小的棧空間)
+------------------+
| 空閑空間 | <- 1024 個槽位(slots)
| ? | 每個槽位 32 字節(256 位)
+------------------+
| 當前使用空間 | <- 隨函數執行壓入/彈出
+------------------+
Memory: 存在于 EVM 執行環境中 臨時性的,交易執行完就清除 線性尋址(0x00, 0x20, 0x40...)
Storage: 存在于區塊鏈狀態中 永久性的,寫入區塊 使用 slot 和 keccak256 哈希定位
基本類型
在 Solidity 中,直接存儲在棧(Stack)中的基本類型包括:
1. 整型(Integer)
contract StackTypes {function integerTypes() public pure {// 所有整型都存儲在棧中uint256 a = 1;uint8 b = 2;int256 c = -1;int8 d = -2;} }
2. 布爾型(Boolean)
function booleanTypes() public pure {bool isTrue = true;bool isFalse = false; }
3. 地址(Address)
function addressTypes() public pure {address addr = 0x123...;address payable payableAddr = payable(0x123...); }
- 固定大小字節數組(Fixed-size Bytes)
function bytesTypes() public pure {bytes1 b1 = 0x12;bytes32 b32 = 0x123...; }
- 枚舉(Enum)
enum Status { Active, Inactive }function enumTypes() public pure {Status status = Status.Active; // 實際存儲為 uint8 }
重要說明:
- 這些類型在函數(相關文檔:數據位置修飾符)調用時:
- 作為參數傳遞時是值傳遞
- 不需要指定 memory 等位置修飾符
// ? 正確:不需要 memoryfunction example(uint256 num, bool flag, address addr)public pure {// ...}// ? 錯誤:不能為基本類型指定 memoryfunction wrong(uint256 memory num) public pure {// ...}
- 大小限制:
function stackLimits() public pure {// EVM 棧深度限制為 1024// 每個值占用一個棧槽(32字節) }
- 與引用類型對比:
contract TypeComparison {// 基本類型:直接存儲在棧中uint256 public stackVar = 123;// 引用類型:存儲引用在棧中,數據在其他位置string public stringVar = "hello"; // 數據在存儲中uint256[] public arrayVar; // 數據在存儲中 }
- 在函數(文檔地址:數據位置修飾符)中的使用:
contract StackUsage {function calculate() public pure returns (uint256) {// 這些變量都在棧中uint256 a = 1;uint256 b = 2;uint256 c = a + b;return c;} // 函數結束時棧變量自動清除 }
注意事項:
- 棧變量的生命周期僅限于函數執行期間
- 棧變量不需要手動管理內存
- 棧操作的 gas 成本較低
- 需要注意棧深度限制(1024層)
這些類型的特點:
- 大小固定
- 值類型(非引用)
- 操作簡單直接
- gas 成本低
主要的引用類型:
特點: 在棧中:只存儲引用(地址/指針) 實際數據:存儲在 Memory 或 Storage 中
- 數組(Array)
contract ArrayExample {// Storage 數組(狀態變量)uint[] public storageArray; // 存儲在鏈上function example() public {// Memory 數組(局部變量)uint[] memory memoryArray = new uint[](3);// 棧中只存儲數組的引用(指針)// 實際數據在 Memory 或 Storage 中} }
- 字符串(String)
contract StringExample {// Storage 字符串string public storageStr = "hello"; // 存儲在鏈上function example() public pure {// Memory 字符串string memory memoryStr = "world";// 棧中存儲字符串的引用// 實際字符串數據在 Memory 或 Storage} }
- 結構體(Struct)
contract StructExample {struct Person {string name;uint age;}// Storage 結構體Person public storagePerson;function example() public {// Memory 結構體Person memory memoryPerson = Person("Alice", 20);// 棧中存儲結構體的引用// 實際數據在 Memory 或 Storage} }
- 映射(Mapping)
contract MappingExample {// 只能聲明為 Storagemapping(address => uint) public balances;function example() public {// ? 不能創建 Memory 映射// mapping(address => uint) memory memoryMap; // 錯誤// 只能在 Storage 中使用balances[msg.sender] = 100;} }
- 不固定長度的字節數組(bytes)
// 引用類型示例 bytes public dynamicBytes; // 動態字節數組,存儲在存儲區(storage) bytes32 public fixedBytes; // 固定長度字節數組,是值類型function example() public {// 動態分配內存dynamicBytes = new bytes(2);dynamicBytes[0] = 0x12;dynamicBytes[1] = 0x34;// 可以改變長度dynamicBytes.push(0x56); }
存儲位置總結:
- Storage(鏈上存儲)
- 狀態變量
- 永久存儲
- Gas 成本高 string public storageStr; // 狀態變量自動存儲在 Storage
- Memory(臨時內存)
- 函數參數
- 函數(相關文檔:數據位置修飾符)內的臨時變量
- 函數返回值 function example(string memory param) public { string memory temp = "temp"; }
- 棧(Stack)
- 只存儲引用(指針)
- 指向 Memory 或 Storage 的實際數據 // 棧中存儲的是引用,指向實際數據 uint[] memory arr = new uint;
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
//pragma solidity ^0.8.0;//大于8.0版本
//pragma solidity >=0.8.0 <0.9.0;//大于等于8.0版本,小于9.0版本contract helloDto {//string public hello="hello 3.0";int public a = 1 * 2 ** 255-1;uint public b =1 *2**256 - 1; // u =>unsinged 沒有符號 + -bool public c =false;address public add =錢包地址; //16字節bytes32 public d=hex"1000";enum Status{Active,Bob}int[] public arr;struct Person{int8 Age;bool Sex;string Name;}Person public lihua =Person(18,false,"lihua");Person public Tom =Person({Age:18,Sex :false,Name:"Tom"});}
?Solidity 函數調用
函數的基本定義
- 函數定義語法:
- 在 Solidity 中,函數的定義形式如下:
function 函數名(< 參數類型 > < 參數名 >) < 可見性 > < 狀態可變性 > [returns(< 返回類型 >)] { // 函數體 }
string private hello ="hello";function say (string memory name) public view returns (string memory) {return string.concat(hello,name);}
function cancat (string memory base,string memory name) public pure returns (string memory){return string.concat(base,name);}function setHello(string memory str) public {hello=str;}
-
函數可以包含輸入參數、輸出參數、可見性修飾符、狀態可變性修飾符和返回類型。
-
自由函數:
- 函數不僅可以在合約內部定義,還可以作為自由函數在合約外部定義。
- 自由函數的使用可以幫助分離業務邏輯,使代碼更具模塊化。
函數參數的使用
- 參數聲明:
- 函數參數與變量聲明類似,輸入參數用于接收調用時傳入的值,輸出參數用于返回結果。
- 未使用的參數可以省略其名稱。
- 示例代碼:
pragma solidity >0.5.0;contract Simple {function taker(uint _a, uint _b) public pure {// 使用 _a 和 _b 進行運算} }
- 返回值:
- Solidity 函數可以返回多個值,這通過元組(tuple)來實現。
- 返回值可以通過兩種方式指定:
- 使用返回變量名:
function arithmetic(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) { o_sum = _a + _b; o_product = _a * _b; }
- 直接在return語句中提供返回值:
function arithmetic(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) {return (_a + _b, _a * _b); }
- 元組與多值返回:
- Solidity 支持通過元組返回多個值,并且可以同時將這些值賦給多個變量。
- 示例代碼:
pragma solidity >0.5.0;contract C { function f() public pure returns (uint, bool, uint) { return (7, true, 2); }function g() public {(uint x, bool b, uint y) = f(); // 多值賦值 } }
函數可見性修飾符
-
Solidity中的函數可見性修飾符有四種,決定了函數在何處可以被訪問:
- private?(私有):
- 只能在定義該函數的合約內部調用。
- internal?(內部):
- 可在定義該函數的合約內部調用,也可從繼承該合約的子合約中調用。
- external?(外部):
- 只能從合約外部調用。如果需要從合約內部調用,必須使用
this
關鍵字。
- 只能從合約外部調用。如果需要從合約內部調用,必須使用
- public?(公開):
- 可以從任何地方調用,包括合約內部、繼承合約和合約外部。
- private?(私有):
-
示例代碼:
contract VisibilityExample {function privateFunction() private pure returns (string memory) {return "Private";}function internalFunction() internal pure returns (string memory) {return "Internal";}function externalFunction() external pure returns (string memory) {return "External";}function publicFunction() public pure returns (string memory) {return "Public";} }
狀態可變性修飾符
- 狀態可變性修飾符:
- Solidity 中有三種狀態可變性修飾符,用于描述函數是否會修改區塊鏈上的狀態:
- view:
- 聲明函數只能讀取狀態變量,不能修改狀態。
- 示例:
function getData() external view returns(uint256) { return data; }
- pure:
- 聲明函數既不能讀取也不能修改狀態變量,通常用于執行純計算。
- 示例:
function add(uint _a, uint _b) public pure returns (uint) {return _a + _b; }
- payable:
- 聲明函數可以接受以太幣,如果沒有該修飾符,函數將拒絕任何發送到它的以太幣。
- 示例:
function deposit() external payable { // 接收以太幣 }
payable 是 Solidity 中的一個函數修飾符,用于標識一個函數可以接收以太幣(ETH)。當一個函數被標記為 payable 時,它允許在調用該函數時向合約發送以太幣。這是合約接收以太幣的唯一方式。
- 接收以太幣:只有被標記為 payable 的函數才能接收以太幣。如果嘗試向一個沒有 payable 修飾符的函數發送以太幣,交易將會失敗。
- 合約余額:通過 payable 函數接收到的以太幣會增加合約的余額。
- 使用場景:通常用于實現合約的支付功能,比如購買、捐贈等。
contract PayableDemo {function deposit() public payable {// 函數可以接收 ETH} }
- 完整示例:
contract SimpleStorage {uint256 private data;function setData(uint256 _data) external {data = _data; // 修改狀態變量}function getData() external view returns (uint256) {return data; // 讀取狀態變量}function add(uint256 a, uint256 b) external pure returns (uint256) {return a + b; // 純計算函數}function deposit() external payable {// 接收以太幣} }
什么是 StateDB?
StateDB狀態數據庫是區塊鏈系統中用于存儲每個賬戶(或合約)的最新狀態的組件。它記錄了包括賬戶余額、合約存儲變量、nonce 等所有與當前區塊狀態相關的數據。
?它解決了什么問題?
在區塊鏈中,每個新區塊的生成都伴隨著系統狀態的更新(比如賬戶余額變化、合約變量修改等),因此必須有一個機制來追蹤和存儲這些變化。這就是 StateDB 的作用:
- 保存所有賬戶和合約的當前狀態。
- 提供查詢和更新狀態的能力。
- 支持區塊的回滾與狀態快照,便于處理鏈的重組(reorg)或回退。
StateDB 通常包含哪些內容?
以以太坊為例,StateDB 中包含:
項目 | 說明 |
---|---|
地址(Address) | 每個用戶或合約的唯一標識 |
余額(Balance) | 賬戶中持有的 ETH 數量 |
Nonce | 賬戶已發送交易的數量,用于防止重放攻擊 |
存儲(Storage) | 合約中的變量(鍵值對),通過 Merkle Patricia Tree 組織 |
代碼(Code) | 智能合約的字節碼 |
這些數據通過一種叫做?Merkle Patricia Trie?的數據結構存儲,從而實現高效的查找、驗證與同步。
數據結構與存儲形式
在如以太坊這樣的系統中,StateDB 不是直接存在于普通的數據庫中,而是被組織為:
- 賬戶狀態樹(State Trie):每個賬戶為一個節點
- 每個合約的存儲又構成一個獨立的 trie(合約存儲 Trie)
整個狀態樹的根哈希(stateRoot)會被寫入區塊頭中,確保狀態不可篡改且可驗證。
舉個例子
假如 Alice 向 Bob 轉賬 1 ETH,StateDB 會:
- 查找 Alice 和 Bob 的賬戶狀態
- 減少 Alice 的余額,增加 Bob 的余額
- 更新這些狀態在 Merkle Trie 中的位置
- 生成新的根哈希,寫入區塊頭
整型
整數類型用 int/uint 表示有符號和無符號的整數。關鍵字 int/uint 的末尾接上一個數字表示數據類型所占用空間的大小,這個數字是以 8 的倍數,最高為 256,因此,表示不同空間大小的整型有:uint8、uint16、uint32 ... uint256,int 同理,無數字時 uint 和 int 對應 uint256 和 int56。
因此整數的取值范圍跟不同空間大小有關, 比如 uint32 類型的取值范圍是 0 到 2^32-1(2 的 32 次方減 1)。
如果整數的某些操作,其結果不在取值范圍內,則會被溢出截斷。 數據被截斷可能引發嚴重后果,稍后舉例。
整型支持以下幾種運算符:
比較運算符: <=(小于等于)、<(小于) 、==(等于)、!=(不等于)、>=(大于等于)、>(大于)
位操作符: &(和)、|(或)、^(異或)、~(位取反)
算術操作符:+(加號)、-(減)、-(負號)、* (乘法)、/ (除法), %(取余數), **(冪)
移位: <<(左移位)、 >>(右移位)
這里略作說明:
① 整數除法總是截斷的,但如果運算符是字面量(字面量稍后講),則不會截斷。
② 整數除 0 會拋出異常。
③ 移位運算結果的正負取決于操作符左邊的數。x << y 和 x * (2**y) 是相等的,x >> y 和 x / (2*y) 是相等的。
④ 不能進行負移位,即操作符右邊的數不可以為負數,否則會在運行時拋出異常。
這里提供一段代碼來讓大家熟練一不同操作符的使用,運行之前,先自己預測一下結果,看是否和運行結果不一樣。
pragma solidity >0.5.0; contract testInt { int8 a = -1; int16 b = 2; uint32 c = 10; uint8 d = 16; function add(uint x, uint y) public pure returns (uint z) {z = x + y; }function divide(uint x, uint y ) public pure returns (uint z){z = x / y; } function leftshift(int x, uint y) public pure returns (int z){z = x << y; } function rightshift(int x, uint y) public pure returns (int z){z = x >> y; } function testPlusPlus() public pure returns (uint ) { uint x = 1; uint y = ++x; // c = ++a; return y; } }
整型溢出問題 在使用整型時,要特別注意整型的大小及所能容納的最大值和最小值,如 uint8 的最大值為 0xff(即:255),最小值是 0,可以通過 Type(T).min 和 Type(T).max 獲得整型的最小值與最大值。
下面這段合約代碼用來演示整型溢出的情況,大家可以預測 3 個函數分別的結果是什么?然后運行看看。
pragma solidity ^0.5.0; contract testOverflow { function add1() public pure returns (uint8) { uint8 x = 128; uint8 y = x * 2; return y; } function add2() public pure returns (uint8) { uint8 i = 240; uint8 j = 16; uint8 k = i + j; return k; } function sub1() public pure returns (uint8) { uint8 m = 1; uint8 n = m - 2; return n; } }
揭曉一下上述代碼的運行結果:add1()的結果是 0,而不是 256,add2() 的結果同樣是 0,sub1 是 255,而不是-1。
溢出就像時鐘一樣,當秒針走到 59 之后,下一秒又從 0 開始。
業界名氣頗大的 BEC,就曾經因發生溢出問題被交易所暫停交易,損失慘重。
防止整型溢出問題,一個方法是對加法運算的結果進行判斷,防止出現異常值,例如:
function add(uint256 a, uint256 b) internal pure returns (uint256){ uint256 c = a + b; require(c >= a); // 做溢出判斷,加法的結果肯定比任何一個元素大。return c; }
數組的基本概念
-
概述:
- 在 Solidity 中,數組是一種用于存儲相同類型元素的集合;數組類型可以通過在數據類型后添加 [] 來定義。
- Solidity 支持兩種數組類型:靜態數組(Fixed-size Arrays)和動態數組(Dynamic Arrays)。
-
靜態數組:
- 長度固定,數組的大小在定義時確定,之后無法改變。
- 語法示例: uint[10] tens; // 一個長度為 10 的 uint 類型靜態數組 string[4] adaArr = ["This", "is", "an", "array"]; // 初始化的靜態數組
-
動態數組:
- 長度可變,可以根據需要動態調整。
- 語法示例: uint[] many; // 一個動態長度的uint類型數組 pop push uint[] public u = [1, 2, 3]; // 動態數組的初始化
-
通過new關鍵字聲明數組:
- 動態數組可以使用 new 關鍵字在內存中創建,大小基于運行時確定。
- 語法示例: new uint; // 創建一個長度為 7 的動態內存數組 new string; // 創建一個長度為 4 的動態字符串數組
-
數組元素訪問:
- 使用下標訪問數組元素,序號從0開始。
- 語法示例: tens[0] = 1; // 對第一個元素賦值 uint element = u[2]; // 訪問第三個元素
數組的內存(Memory)與存儲(Storage)
- 存儲(Storage)數組:
- 存儲在區塊鏈上,生命周期與合約生命周期相同。
- 語法示例: uint[] public storageArray; // selfdestruct storageArray.push(10); // 修改存儲數組
- 內存(Memory)數組:
- 臨時存在于函數調用中,生命周期與函數相同,函數執行完畢后銷毀。
- 語法示例: function manipulateArray() public pure returns (uint[] memory) { uint[] memory tempArray = new uint; // 內存中創建長度為3的動態數組 tempArray[0] = 10; //sload return tempArray; }
特殊數組類型: bytes 和 string
-
bytes 類型:
- bytes 是一個動態分配大小的字節數組,類似于 byte[],但 gas 費用更低。
- 語法示例: bytes bs = "abc\x22\x22"; // 通過十六進制字符串初始化 bytes public _data = new bytes(10); // 創建一個長度為 10 的字節數組
-
string 類型:
- string 用于存儲任意長度的字符串(UTF-8編碼),對字符串進行操作時用到。
- 語法示例: string str0; string str1 = "TinyXiong\u718A"; // 使用Unicode編碼值
-
注意:
- string 不支持使用下標索引進行訪問。bytes 可以通過下標索引進行讀訪問。
- 使用長度受限的字節數組時,建議使用 bytes1 到 bytes32 類型,以減少 gas 費用。
數組成員與常用操作
-
數組成員屬性和函數:
- length 屬性:返回數組當前長度(只讀),動態數組的長度可以動態改變。
- push():用于動態數組,在數組末尾添加新元素并返回元素引用。
- pop():從數組末尾刪除元素,并減少數組長度。
-
代碼示例: contract ArrayOperations { uint[] public dynamicArray; function addElement(uint _element) public { dynamicArray.push(_element); // 向數組添加元素 // ArrayList.add(ele) } function removeLastElement() public { dynamicArray.pop(); // 刪除數組最后一個元素 } function getLength() public view returns (uint) { return dynamicArray.length; // 獲取數組長度 } }
多維數組與數組切片
-
多維數組:
- 支持多維數組,可以使用多個方括號表示,例如 uint[][5] 表示長度為 5 的變長數組的數組。
- 語法示例: uint[][5] multiArray; // 一個元素為變長數組的靜態數組 uint element = multiArray[2][1]; // 訪問第三個動態數組的第二個元素
-
數組切片:
- 數組切片是數組的一段連續部分,通過 [start:end] 的方式定義。
- 語法示例: bytes memory slice = bytesArray[start:end]; // 創建數組切片
-
應用示例: function sliceArray(bytes calldata _payload) external { bytes4 sig = abi.decode(_payload[:4], (bytes4)); // 解碼函數選擇器 address owner = abi.decode(_payload[4:], (address)); // 解碼地址 }
循環的基本類型
Solidity 支持三種基本的循環結構:
- for 循環
- while 循環
- do-while 循環
for 循環
-
基本語法: contract ForLoopExample { function basicFor() public pure returns(uint) { uint sum = 0;
for(uint i = 0; i < 10; i++) {sum += i;}return sum;
} }
-
for 循環的變體: contract ForLoopVariants { // 無初始化語句 function forWithoutInit() public pure { uint i = 0; for(; i < 10; i++) { // 循環體 } }
// 無條件語句 function forWithoutCondition() public pure { for(uint i = 0;; i++) { if(i >= 10) break; // 循環體 } }
// 無遞增語句 function forWithoutIncrement() public pure { for(uint i = 0; i < 10;) { // 循環體 i++; } } }
while 循環
-
基本語法: contract WhileLoopExample { function basicWhile() public pure returns(uint) { uint sum = 0; uint i = 0;
while(i < 10) {sum += i;i++;}return sum;
} }
do-while 循環
-
基本語法: contract DoWhileExample { function basicDoWhile() public pure returns(uint) { uint sum = 0; uint i = 0;
do {sum += i;i++;} while(i < 10);return sum;
} }
循環控制語句
-
break 語句: contract BreakExample { function findFirstEven(uint[] memory numbers) public pure returns(uint) { for(uint i = 0; i < numbers.length; i++) { if(numbers[i] % 2 == 0) { return numbers[i]; } } return 0; } }
-
continue 語句: contract ContinueExample { function sumOddNumbers(uint[] memory numbers) public pure returns(uint) { uint sum = 0;
for(uint i = 0; i < numbers.length; i++) {if(numbers[i] % 2 == 0) {continue;}sum += numbers[i];}return sum;
} }
Gas 優化考慮
-
避免無限循環: contract GasOptimization { // 不推薦:可能導致 gas 耗盡 function riskyLoop() public pure { uint i = 0; while(true) { i++; if(i >= 10) break; } }
// 推薦:明確的循環邊界 function safeLoop() public pure { for(uint i = 0; i < 10; i++) { // 循環體 } } }
-
優化數組循環: contract ArrayLoopOptimization { // 不推薦:每次循環都要讀取數組長度 function inefficientLoop(uint[] memory arr) public pure { for(uint i = 0; i < arr.length; i++) { // 循環體 } }
// 推薦:緩存數組長度 function efficientLoop(uint[] memory arr) public pure { uint length = arr.length; for(uint i = 0; i < length; i++) { // 循環體 } } }
- 映射(Mapping) 1.1 什么是映射? 映射(Mapping)是 Solidity 中的一種特殊數據結構,類似于哈希表(或字典),用于存儲鍵值對。 語法:
mapping(KeyType => ValueType) visibility variableName;
- KeyType:鍵的類型,支持 uint、address、bytes32 等,不支持 struct 或 mapping。
- ValueType:值的類型,可以是任何 Solidity 變量類型,包括 struct 和 mapping。
- visibility:存儲變量的可見性,如 public、private 等。 1.2 示例:賬戶余額存儲
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract BalanceTracker {mapping(address => uint256) public balances;function setBalance(uint256 amount) public {balances[msg.sender] = amount;}function getBalance(address user) public view returns (uint256) {return balances[user];} }
- balances 映射 address 到 uint256,用于存儲用戶的余額。
- setBalance 允許用戶設置自己的余額。
- getBalance 獲取指定用戶的余額。
多級映射(嵌套映射)
contract MultiMapping {mapping(address => mapping(string => uint256)) public userBalances;function setUserBalance(string memory currency, uint256 amount) public {userBalances[msg.sender][currency] = amount;}function getUserBalance(address user, string memory currency) public view returns (uint256) {return userBalances[user][currency];} }
- 這里 userBalances 是一個 嵌套映射,存儲用戶對不同幣種的余額。
映射的特點
- 默認值: 未初始化的映射鍵會返回 ValueType 的默認值(例如 uint256 默認 0)。
- 不可遍歷: Solidity 不支持遍歷 mapping,只能通過 key 訪問特定 value。
- 可修改但不可刪除: 可以修改 mapping 中的值,但不能刪除整個 mapping。
- 結構體(Struct) 2.1 什么是結構體? 結構體(Struct)是一種自定義數據類型,用于存儲多個不同類型的數據。 語法:
struct StructName {DataType1 variable1;DataType2 variable2;... }
?示例:用戶信息存儲
contract UserManager {struct User {string name;uint256 age;address wallet;}mapping(address => User) public users;function setUser(string memory name, uint256 age) public {users[msg.sender] = User(name, age, msg.sender);}function getUser(address userAddress) public view returns (string memory, uint256, address) {User memory user = users[userAddress];return (user.name, user.age, user.wallet);} }
- User 結構體包含 name、age、wallet 三個字段。
- users 是一個 mapping,將 address 映射到 User 結構體。
- setUser 允許用戶存儲他們的信息。
- getUser 允許查詢用戶信息。
結構體數組 如果要存儲多個 User 結構體,可以使用數組:
contract UserList {struct User {string name;uint256 age;}User[] public users;function addUser(string memory name, uint256 age) public {users.push(User(name, age));} }
- users 數組存儲 User 結構體。
- addUser 添加新用戶。
結構體的應用場景
- 組織和存儲復雜數據
- 結合 mapping 創建去中心化存儲
- 用于 NFT、DAO、投票等應用
- 結合映射和結構體 映射和結構體經常結合使用來構建復雜的數據存儲。 示例:去中心化用戶管理
contract UserRegistry {struct User {string name;uint256 age;bool isRegistered;}mapping(address => User) public users;function registerUser(string memory name, uint256 age) public {require(!users[msg.sender].isRegistered, "User already registered");users[msg.sender] = User(name, age, true);}function getUser(address user) public view returns (string memory, uint256, bool) {require(users[user].isRegistered, "User not registered");User memory u = users[user];return (u.name, u.age, u.isRegistered);} }
- 這里 User 結構體增加了 isRegistered 標志位。
- mapping(address => User) 記錄已注冊的用戶。
- registerUser 確保用戶只能注冊一次。
- getUser 只允許查詢已注冊用戶的信息總結
?映射和結構體是 Solidity 合約開發中最重要的數據結構之一,合理結合兩者可以構建高效的數據存儲模型。