Solidity智能合約基礎

基礎學習使用

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),所有計算都在棧上完成。

  • 三類存儲結構:

    1. 程序代碼存儲區(ROM)(棧):1024個solt,每個solt是32個字節>=256bit,不可變,存儲智能合約的字節碼。
    2. 內存(Memory):可變,臨時存儲執行期間的數據,隨調用結束而清除。
    3. 存儲(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 實例

詳細說明

  1. 用戶發起交易

    • 用戶簽名交易
    • 設定 gas limit 和 gas price
    • 指定目標合約地址和調用數據
  2. 創建新的 EVM 實例

    • 為每筆交易創建獨立的 EVM 環境
    • 初始化執行上下文
    • 準備內存和存儲空間
  3. 加載合約字節碼

    • 從區塊鏈狀態中讀取合約字節碼
    • 將字節碼加載到 EVM 中
    • 準備執行環境
  4. 分配 Stack 空間

    • 分配 1024 個 slots
    • 每個 slot 256 位
    • 用于存儲臨時計算結果
  5. 執行合約代碼

    • 逐條執行操作碼
    • 進行狀態檢查和 gas 計算
    • 處理函數調用和返回值
  6. 更新區塊鏈狀態

    • 寫入存儲變更
    • 更新賬戶余額
    • 觸發事件日志
  7. 銷毀 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...);
}
  1. 固定大小字節數組(Fixed-size Bytes)
function bytesTypes() public pure {bytes1 b1 = 0x12;bytes32 b32 = 0x123...;
}
  1. 枚舉(Enum)
enum Status { Active, Inactive }function enumTypes() public pure {Status status = Status.Active;  // 實際存儲為 uint8
}

重要說明:

  1. 這些類型在函數(相關文檔:數據位置修飾符)調用時:
  • 作為參數傳遞時是值傳遞
  • 不需要指定 memory 等位置修飾符
  // ? 正確:不需要 memoryfunction example(uint256 num, bool flag, address addr)public pure {// ...}// ? 錯誤:不能為基本類型指定 memoryfunction wrong(uint256 memory num) public pure {// ...}
  1. 大小限制:
function stackLimits() public pure {// EVM 棧深度限制為 1024// 每個值占用一個棧槽(32字節)
}
  1. 與引用類型對比:
contract TypeComparison {// 基本類型:直接存儲在棧中uint256 public stackVar = 123;// 引用類型:存儲引用在棧中,數據在其他位置string public stringVar = "hello";  // 數據在存儲中uint256[] public arrayVar;          // 數據在存儲中
}
  1. 在函數(文檔地址:數據位置修飾符)中的使用:
contract StackUsage {function calculate() public pure returns (uint256) {// 這些變量都在棧中uint256 a = 1;uint256 b = 2;uint256 c = a + b;return c;} // 函數結束時棧變量自動清除
}

注意事項:

  1. 棧變量的生命周期僅限于函數執行期間
  2. 棧變量不需要手動管理內存
  3. 棧操作的 gas 成本較低
  4. 需要注意棧深度限制(1024層)

這些類型的特點:

  1. 大小固定
  2. 值類型(非引用)
  3. 操作簡單直接
  4. gas 成本低

主要的引用類型:

特點: 在棧中:只存儲引用(地址/指針) 實際數據:存儲在 Memory 或 Storage 中

  1. 數組(Array)
contract ArrayExample {// Storage 數組(狀態變量)uint[] public storageArray;  // 存儲在鏈上function example() public {// Memory 數組(局部變量)uint[] memory memoryArray = new uint[](3);// 棧中只存儲數組的引用(指針)// 實際數據在 Memory 或 Storage 中}
}
  1. 字符串(String)
contract StringExample {// Storage 字符串string public storageStr = "hello";  // 存儲在鏈上function example() public pure {// Memory 字符串string memory memoryStr = "world";// 棧中存儲字符串的引用// 實際字符串數據在 Memory 或 Storage}
}
  1. 結構體(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}
}
  1. 映射(Mapping)
contract MappingExample {// 只能聲明為 Storagemapping(address => uint) public balances;function example() public {// ? 不能創建 Memory 映射// mapping(address => uint) memory memoryMap;  // 錯誤// 只能在 Storage 中使用balances[msg.sender] = 100;}
}
  1. 不固定長度的字節數組(bytes)
// 引用類型示例
bytes public dynamicBytes;     // 動態字節數組,存儲在存儲區(storage)
bytes32 public fixedBytes;     // 固定長度字節數組,是值類型function example() public {// 動態分配內存dynamicBytes = new bytes(2);dynamicBytes[0] = 0x12;dynamicBytes[1] = 0x34;// 可以改變長度dynamicBytes.push(0x56);
}

存儲位置總結:

  1. Storage(鏈上存儲)
  • 狀態變量
  • 永久存儲
  • Gas 成本高 string public storageStr; // 狀態變量自動存儲在 Storage
  1. Memory(臨時內存)
  • 函數參數
  • 函數(相關文檔:數據位置修飾符)內的臨時變量
  • 函數返回值 function example(string memory param) public { string memory temp = "temp"; }
  1. 棧(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中的函數可見性修飾符有四種,決定了函數在何處可以被訪問:

    1. private?(私有)
      • 只能在定義該函數的合約內部調用。
    2. internal?(內部)
      • 可在定義該函數的合約內部調用,也可從繼承該合約的子合約中調用。
    3. external?(外部)
      • 只能從合約外部調用。如果需要從合約內部調用,必須使用this關鍵字。
    4. public?(公開)
      • 可以從任何地方調用,包括合約內部、繼承合約和合約外部。
  • 示例代碼

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 會:

  1. 查找 Alice 和 Bob 的賬戶狀態
  2. 減少 Alice 的余額,增加 Bob 的余額
  3. 更新這些狀態在 Merkle Trie 中的位置
  4. 生成新的根哈希,寫入區塊頭

    整型

    整數類型用 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 支持三種基本的循環結構:

  1. for 循環
  2. while 循環
  3. 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;
    

    } }


循環控制語句

  1. 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; } }

  2. 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 優化考慮

  1. 避免無限循環: 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++) { // 循環體 } } }

  2. 優化數組循環: 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++) { // 循環體 } } }

  1. 映射(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。
  1. 結構體(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 合約開發中最重要的數據結構之一,合理結合兩者可以構建高效的數據存儲模型。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92590.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92590.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92590.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity跨平臺超低延遲的RTSP/RTMP播放器技術解析與實戰應用

?? 引言&#xff1a;為什么說 Unity 中的視頻能力是“可視化神經元”&#xff1f; 隨著“可視化 實時性”成為工業數字化的關鍵支撐&#xff0c;Unity 正從傳統游戲引擎&#xff0c;演進為數字孿生系統、智能機器人中控、虛擬交互平臺、XR 可視引擎等領域的底層核心。它不再…

python學智能算法(三十三)|SVM-構建軟邊界拉格朗日方程

【1】引用 在前序學習進程中&#xff0c;我們初步了解了SVM軟邊界&#xff0c;今天就更進一步&#xff0c;嘗試構建SVM軟邊界的拉格朗日函數。 【2】基本問題 在SVM軟邊界中&#xff0c;我們已經獲得此時的最優化幾何距離的表達式&#xff1a; fmin?12∣∣w∣∣2C∑i1nξif…

【YOLOv5】

Focus模塊&#xff1a;早期再yolov5版本提出&#xff0c;后期被常規卷積替換&#xff0c;作用是圖像進入主干網絡之前&#xff0c;進行隔行隔列采樣&#xff0c;把空間維度堆疊到通道上&#xff0c;減少計算量。 SPPF:SPP的改進版本&#xff0c;把SPP的不同池化核改變為K 5 的…

Pytest項目_day05(requests加入headers)

headers 由于每個請求都需要加入一些固定的參數&#xff0c;例如&#xff1a;cookies、user-agent&#xff0c;那么將這些固定參數放入URL或params中會顯得很臃腫&#xff0c;因此一般將這些參數放在request headers中headers的反爬作用 在豆瓣網站中&#xff0c;如果我們不加入…

安全引導功能及ATF的啟動過程(四)

安全引導功能及ATF的啟動過程&#xff08;四&#xff09; ATF中bl31的啟動 在bl2中觸發安全監控模式調用后會跳轉到bl31中執行&#xff0c;bl31最主要的作用是建立EL3運行態的軟件配置&#xff0c;在該階段會完成各種類型的安全監控模式調用ID的注冊和對應的ARM核狀態的切換&am…

從手工到智能決策,ERP讓制造外貿企業告別“數據孤島“降本增效

在全球化競爭加劇的當下&#xff0c;制造型外貿企業正面臨訂單碎片化、供應鏈復雜化、合規風險上升等多重挑戰。數字化轉型已成為企業突破增長瓶頸、構建核心競爭力的必選項。然而&#xff0c;許多企業在推進過程中因選型不當陷入“系統孤島”“數據失真”“流程低效”等困境。…

DMETL簡單介紹、安裝部署和入門嘗試

一、DMETL的介紹1.1 概述我們先來簡單了解一下DMETL。DMETL是什么&#xff1f;說的簡單一點&#xff0c;DMETL一款數據處理與集成平臺&#xff1b;從功能來說&#xff0c;那DMETL就是對數據同步、數據處理以及數據交換共享提供一站式支持的平臺&#xff1b;從它的意義來說&…

NLP 人工智能 Seq2Seq、K-means應用實踐

基于Java和人工智能的Web應用 以下是基于Java和人工智能的Web應用實例,涵蓋自然語言處理、計算機視覺、數據分析等領域。這些案例結合了沈七星AI或其他開源框架(如TensorFlow、Deeplearning4j)的實現思路,供開發參考: 自然語言處理(NLP) 1. 智能客服系統 使用Java的Op…

Docker 從入門到實戰(一):全面解析容器化革命 | 2025 終極指南

2025 年,全球容器市場規模突破 200 億美元,超過 80% 的企業生產環境運行在容器之上。掌握 Docker 已成為開發、運維乃至架構師的核心競爭力。本文帶你徹底搞懂 Docker 的底層邏輯與核心價值! 一、Docker 是什么?為什么它能改變世界? 想象一下:你開發時運行完美的 Pytho…

Lazada東南亞矩陣營銷破局:指紋手機如何以“批量智控+數據中樞”重構運營生態

在Lazada以“超級APP”戰略滲透東南亞6國市場的進程中&#xff0c;商家正陷入一個結構性矛盾&#xff1a;如何用有限人力高效管理10個國家賬號&#xff0c;卻不被數據孤島拖垮營銷效率&#xff0c;更不因賬號關聯風險引發平臺封禁&#xff1f;傳統多賬號運營依賴“人手一臺設備…

操作系統: 線程(Thread)

目錄 什么是線程&#xff08;Thread&#xff09;&#xff1f; 線程與進程之間的關系 線程調度與并發執行 并發&#xff08;Concurrency&#xff09;與并行&#xff08;Parallelism&#xff09; 多線程編程的四大核心優勢&#xff08;benefits of multithreaded programmin…

Uber的MySQL實踐(一)——學習筆記

MySQL 是Uber數據基礎設施的核心支柱&#xff0c;支撐著平臺上大量關鍵操作。Uber 擁有一套龐大的 MySQL 集群&#xff0c;如何構建一個控制平面來管理如此大規模的 MySQL 集群&#xff0c;并同時確保零宕機、零數據丟失是一個十分有挑戰性的問題。下面重點介紹 Uber 的 MySQL …

騰訊云EdgeOne產品深度分析報告

一、產品概述騰訊云EdgeOne是騰訊云推出的新一代邊緣安全加速平臺&#xff0c;集成內容分發網絡&#xff08;CDN&#xff09;、Web應用防火墻&#xff08;WAF&#xff09;、DDoS防護、Bot管理、API安全及邊緣計算能力&#xff0c;致力于為企業提供一站式安全加速解決方案。該平…

Spring Boot 優雅配置InfluxDB3客戶端指南:@Configuration + @Bean + yml實戰

前言 想用Java玩轉InfluxDB 3?要是還靠寫main函數硬編碼配置,那就像穿著睡衣開正式會議,實在有點不靠譜。現代Spring開發套路講究配置和代碼分離,講究優雅和靈活。用@Configuration配合@Bean注解,再加上yml配置文件集中管理連接信息,簡直是為代碼打扮一身西裝,既整潔又…

記錄:rk3568適配開源GPU驅動(panfrost)

rk3568采用的GPU是Mali-G52&#xff0c;該型號的GPU已在5.10內核的panfrost驅動中被支持。下面記錄下移植過程。 1.內核dts修改&#xff1a; kernel 5.10: arch/arm64/boot/dts/rockchip/rk3568.dtsigpu: gpufde60000 {compatible "rockchip,rk3568-mali", "ar…

SMBIOS詳解:系統管理BIOS的工作原理與實現

1. SMBIOS概述 SMBIOS&#xff08;System Management BIOS&#xff09;是由DMTF&#xff08;分布式管理任務組&#xff09;制定的行業標準&#xff0c;旨在為計算機系統提供統一的硬件信息描述框架。它定義了計算機硬件組件&#xff08;如處理器、內存、主板等&#xff09;的標…

8.5 CSS3多列布局

多列布局 CSS3之多列布局columns CSS3中新出現的多列布局(multi-column)是傳統HTML網頁中塊狀布局模式的有力擴充。這種新語法能夠讓WEB開發人員輕松的讓文本呈現多列顯示。 設置列寬 column-width&#xff1a; | auto 設置對象的寬度&#xff1b;使用像素表示。 auto&#…

Chrome插件快速上手

目錄 前言 一、瀏覽器插件的主要功能 二、插件的工作原理 插件結構 manifest.json icons background.js content-scripts 三、插件例子 popup popup.html popup.js styles.css background.js content-script.js manifest.json 四、其它 前言 本文不做特殊說明…

moment和dayjs

一&#xff1a;moment和dayjs 區別moment 大且可變、維護模式&#xff1b;dayjs 小且不可變、插件化、tree?shaking 友好。antd v4 用 moment&#xff1b;antd v5 用 dayjs。請在同一項目中統一其一&#xff0c;避免混用導致組件報錯。二&#xff1a; antd 4.24.16&#xff08…

Flutter Packge - 組件應用

一、組件創建1. 在工程根目錄創建 packages 目錄。mkdir packages #創建文件夾 cd packages 2. 創建純 Dart Package&#xff08;適合工具類/UI組件&#xff09;。flutter create --templatepackage common_network二、組件配置1. 在 common_network 的 pubspec.yaml 中添加…