基础学习使用

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)

    • 定义:直接存储值的数据类型,变量之间赋值是复制值本身。
    • 常见类型uintintbooladdressbytes1 到 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 中添加…