Mapping

发布于:2025-07-18 ⋅ 阅读:(14) ⋅ 点赞:(0)

本节是《Solidity by Example》的中文翻译与深入讲解,专为零基础或刚接触区块链开发的小白朋友打造。我们将通过“示例 + 解说 + 提示”的方式,带你逐步理解每一段 Solidity 代码的实际用途与背后的逻辑。

Solidity 是以太坊等智能合约平台使用的主要编程语言,就像写网页要用 HTML 和 JavaScript,写智能合约就需要会 Solidity。

如果你从没写过区块链代码也没关系,只要你了解一点点编程概念,比如“变量”“函数”“条件判断”,我们就能从最简单的例子开始,一步步建立你的 Solidity 编程思维。

Mapping

映射使用语法 mapping(keyType => valueType) 创建。

键类型(keyType)可以是任何内置值类型、字节、字符串或任何合约。

值类型(valueType)可以是任何类型,包括另一个映射或数组。

映射不可迭代。

  • 什么是映射(Mapping)?
    • 映射是 Solidity 中的一种数据结构,类似于其他编程语言中的哈希表或字典,用于存储键值对(key-value pairs)。
    • 语法为mapping(keyType => valueType),其中:
      • keyType:键的类型,可以是内置类型(如 uintaddress)、bytesstring 或合约类型。
      • valueType:值的类型,可以是任何类型,包括基本类型(uintbool)、复杂类型(数组、结构体)甚至另一个映射。
  • 映射的特点:
    • 键值存储:每个键对应一个值,访问时通过键快速查找值。
    • 默认值:如果某个键未被设置,访问时返回类型的默认值(例如,uint 的默认值是 0,bool 的默认值是 false)。
    • 不可迭代:映射不能像数组一样遍历所有键值对(没有内置的迭代方法)。
    • 存储在区块链:映射是状态变量,存储在区块链的 storage 中,修改需要 Gas。
  • 用途:
    • 映射常用于:
      • 存储用户数据(例如,地址到余额的映射)。
      • 记录状态(例如,地址到权限的映射)。
      • 实现复杂数据结构(例如,嵌套映射)。
  • 不可迭代的限制:
    • 无法直接获取映射中的所有键或值,需要额外维护一个数组来存储键列表(如果需要迭代)。
// SPDX-License-Identifier: MIT
// 声明代码采用 MIT 开源许可证,允许自由使用、修改和分发代码。

pragma solidity ^0.8.26;
// 指定 Solidity 编译器版本必须大于或等于 0.8.26 并且小于 0.9.0。
// `pragma` 指令确保合约使用兼容的编译器版本,`^0.8.26` 表示支持 0.8.26 或更高版本(但不超过 0.9.0)。

contract Mapping {
    // 定义一个名为 `Mapping` 的智能合约。
    // 合约是一个运行在以太坊区块链上的程序,包含数据(状态变量)和逻辑(函数)。
    // 这个合约的目的是展示如何使用简单的映射(mapping)来存储和操作键值对。

    // Mapping from address to uint
    // 从地址到无符号整数的映射
    mapping(address => uint256) public myMap;
    // 声明一个名为 `myMap` 的映射,键类型是 `address`(以太坊地址),值类型是 `uint256`(无符号整数)。
    // `public` 表示映射可以被外部访问,Solidity 会自动生成一个 getter 函数(`myMap(address)`),返回对应地址的值。
    // 存储在区块链上,未初始化的键返回默认值 0。

    function get(address _addr) public view returns (uint256) {
        // 定义一个名为 `get` 的公共函数,接受一个参数 `_addr`(类型为 `address`)。
        // `public` 表示函数可以被外部调用。
        // `view` 表示函数只读取区块链状态,不修改,链下调用免费。
        // 返回值类型为 `uint256`,表示对应地址在 `myMap` 中的值。

        // Mapping always returns a value.
        // If the value was never set, it will return the default value.
        // 映射总是返回一个值。
        // 如果该值从未被设置,将返回默认值。
        return myMap[_addr];
        // 返回 `myMap` 中键 `_addr` 对应的值。
        // 如果 `_addr` 未设置值,返回 `uint256` 的默认值 0。
    }

    function set(address _addr, uint256 _i) public {
        // 定义一个名为 `set` 的公共函数,接受两个参数:
        // `_addr`(类型为 `address`):映射的键。
        // `_i`(类型为 `uint256`):要设置的值。
        // `public` 表示函数可以被外部调用。
        // 没有 `view` 或 `pure`,表示函数会修改区块链状态,需消耗 Gas。

        // Update the value at this address
        // 更新该地址的值
        myMap[_addr] = _i;
        // 将 `myMap` 中键 `_addr` 对应的值更新为 `_i`。
        // 修改映射会更新区块链存储,消耗 Gas。
    }

    function remove(address _addr) public {
        // 定义一个名为 `remove` 的公共函数,接受一个参数 `_addr`(类型为 `address`)。
        // `public` 表示函数可以被外部调用。
        // 没有 `view` 或 `pure`,表示函数会修改区块链状态,需消耗 Gas。

        // Reset the value to the default value.
        // 将值重置为默认值。
        delete myMap[_addr];
        // 使用 `delete` 操作符将 `myMap` 中键 `_addr` 对应的值重置为默认值(`uint256` 的默认值 0)。
        // 修改区块链状态,消耗 Gas。
    }
}
contract NestedMapping {
    // 定义一个名为 `NestedMapping` 的智能合约。
    // 这个合约的目的是展示嵌套映射(mapping 嵌套 mapping)的使用。

    // Nested mapping (mapping from address to another mapping)
    // 嵌套映射(从地址到另一个映射的映射)
    mapping(address => mapping(uint256 => bool)) public nested;
    // 声明一个名为 `nested` 的嵌套映射:
    // - 外层映射的键是 `address`(以太坊地址)。
    // - 内层映射的键是 `uint256`(无符号整数),值是 `bool`(布尔值)。
    // `public` 表示映射可以被外部访问,Solidity 会生成 getter 函数(`nested(address, uint256)`)。
    // 存储在区块链上,未初始化的键返回默认值 `false`。

    function get(address _addr1, uint256 _i) public view returns (bool) {
        // 定义一个名为 `get` 的公共函数,接受两个参数:
        // `_addr1`(类型为 `address`):外层映射的键。
        // `_i`(类型为 `uint256`):内层映射的键。
        // `public` 表示函数可以被外部调用。
        // `view` 表示函数只读取区块链状态,不修改,链下调用免费。
        // 返回值类型为 `bool`,表示嵌套映射中的值。

        // You can get values from a nested mapping
        // even when it is not initialized
        // 你可以从嵌套映射中获取值,即使它未被初始化
        return nested[_addr1][_i];
        // 返回 `nested` 中键 `_addr1` 的内层映射中键 `_i` 对应的值。
        // 如果 `_addr1` 或 `_i` 未设置值,返回 `bool` 的默认值 `false`。
    }

    function set(address _addr1, uint256 _i, bool _boo) public {
        // 定义一个名为 `set` 的公共函数,接受三个参数:
        // `_addr1`(类型为 `address`):外层映射的键。
        // `_i`(类型为 `uint256`):内层映射的键。
        // `_boo`(类型为 `bool`):要设置的值。
        // `public` 表示函数可以被外部调用。
        // 没有 `view` 或 `pure`,表示函数会修改区块链状态,需消耗 Gas。

        nested[_addr1][_i] = _boo;
        // 将 `nested` 中键 `_addr1` 的内层映射中键 `_i` 对应的值更新为 `_boo`。
        // 修改区块链状态,消耗 Gas。
    }

    function remove(address _addr1, uint256 _i) public {
        // 定义一个名为 `remove` 的公共函数,接受两个参数:
        // `_addr1`(类型为 `address`):外层映射的键。
        // `_i`(类型为 `uint256`):内层映射的键。
        // `public` 表示函数可以被外部调用。
        // 没有 `view` 或 `pure`,表示函数会修改区块链状态,需消耗 Gas。

        delete nested[_addr1][_i];
        // 使用 `delete` 操作符将 `nested` 中键 `_addr1` 的内层映射中键 `_i` 对应的值重置为默认值(`bool` 的默认值 `false`)。
        // 修改区块链状态,消耗 Gas。
    }
}

MappingNestedMapping 是两个简单的智能合约,展示了 Solidity 中的映射(mapping)和嵌套映射的使用:

  • Mapping 合约:实现一个简单的映射,从以太坊地址 (address) 映射到无符号整数 (uint256),并提供获取、设置和删除值的函数。
  • NestedMapping 合约:实现一个嵌套映射,从地址 (address) 映射到另一个映射(从 uint256bool),同样提供获取、设置和删除值的函数。

代码做什么?

Mapping 合约
  • 映射 myMap
    • 存储一个从 addressuint256 的键值对。
    • 例如,可以记录每个地址的余额(如 myMap[0x123...] = 100)。
    • 未设置的键(例如 myMap[0x456...])返回默认值 0。
  • 函数 get
    • 输入一个地址 _addr,返回 myMap 中对应的值。
    • 因为是 view 函数,链下调用免费。
  • 函数 set
    • 输入地址 _addr 和值 _i,更新 myMap 中对应键的值。
    • 修改区块链状态,消耗 Gas。
  • 函数 remove
    • 输入地址 _addr,将 myMap 中对应键的值重置为 0。
    • 修改区块链状态,消耗 Gas。
NestedMapping 合约
  • 嵌套映射 nested
    • 存储一个从 address 到另一个映射的键值对,内层映射从 uint256bool
    • 例如,可以记录每个地址的某些编号是否被标记(nested[0x123...][1] = true)。
    • 未设置的键返回默认值 false
  • 函数 get
    • 输入地址 _addr1 和编号 _i,返回 nested 中对应的布尔值。
    • 因为是 view 函数,链下调用免费。
  • 函数 set
    • 输入地址 _addr1、编号 _i 和布尔值 _boo,更新 nested 中对应键的值。
    • 修改区块链状态,消耗 Gas。
  • 函数 remove
    • 输入地址 _addr1 和编号 _i,将 nested 中对应键的值重置为 false
    • 修改区块链状态,消耗 Gas。

关键点:

  • 映射的特点:
    • 映射是键值对存储,类似于字典,键快速定位值。
    • 未设置的键返回类型的默认值(uint256 为 0,boolfalse)。
    • 映射存储在区块链的 storage 中,修改需要 Gas。
  • 嵌套映射:
    • 允许更复杂的数据结构,例如多层键值关系。
    • 语法为 mapping(keyType1 => mapping(keyType2 => valueType))
    • 同样不可迭代,未初始化的键返回默认值。
  • 不可迭代:
    • 无法直接遍历映射的所有键值对。
    • 如果需要迭代,需额外维护一个数组存储键。
  • Gas 成本:
    • 部署合约时,初始化映射(即使为空)需要 Gas(存储合约代码)。
    • 调用 setremove 修改映射,消耗 Gas(更新区块链存储)。
    • 调用 getview 操作,链下调用免费。
  • 用途:
    • 映射常用于:
      • 存储用户余额(address => uint256)。
      • 记录权限(address => bool)。
      • 管理复杂关系(嵌套映射,例如 address => uint256 => bool)。

映射的注意事项

  • 默认值:
    • 映射总是返回一个值,未初始化的键返回类型默认值(uint256 为 0,boolfalse)。
    • 无需显式初始化,节省存储空间。
  • 不可迭代:
    • 无法遍历映射的所有键值对,需额外维护键列表(例如数组)。
    • 迭代需求可能增加 Gas 成本(数组存储和遍历)。
  • Gas 优化:
    • 写入映射(set)和删除(remove)消耗 Gas,尽量减少不必要的操作。
    • 嵌套映射的每次写入涉及多层存储,Gas 成本更高。
    • 读取映射(get)链下免费,适合频繁查询。
  • 安全性:
    • 确保键的正确性(例如,检查地址是否有效)。
    • public 映射对所有人可见,敏感数据需谨慎存储。
    • 避免在循环中频繁写入映射,可能导致 Gas 超支。
  • 嵌套映射:
    • 适合复杂数据结构,但增加代码复杂度和 Gas 成本。
    • 确保内层映射的键类型适合用例(例如 uint256 用于编号)。

网站公告

今日签到

点亮在社区的每一天
去签到