映射 mapping

本章学习 Solidity 映射类型的定义和使用方法。

视频Bilibili  |  Youtube
官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

Solidity 中的映射类型 mapping,用来以键值对的形式存储数据。它的主要作用是提供高效的查找功能,类似于其它编程语言中的哈希表或者字典。

例如,在使用 白名单 的场景中,我们可以通过 映射类型 将用户地址映射到一个布尔值,以标识哪些地址是被允许的,哪些地址是不被允许的,从而高效地控制访问权限。

映射类型是智能合约中最常用的数据类型之一,我们必须熟练掌握。

1. 定义

定义一个映射类型使用 mapping 关键字,它的语法如下:

mapping(key_type => value_type)

mapping 类型是将一个键 (key) 映射到一个值 (value)。 

其中:key_type 可以是任何基本数据类型,比如:整型、地址型、布尔型、枚举型,以及 bytesstring,但是部分复杂对象不允许使用,比如:动态数组、结构体、映射。

value_type 可以是任何数据类型。

例如,在合约中声明一个 mapping 类型的变量:

  struct MyStruct { uint256 value; } // 定义一个结构体

  mapping(address => uint256) a; // 正确
  mapping(string => bool[]) b; // 正确
  mapping(int => MyStruct) c; // 正确
  mapping(address => mapping(address => uint)) d; // 正确

  // mapping(uint[] => uint) public e;  // 错误
  // mapping(MyStruct => addrss) public f; // 错误
  // mapping(mapping(string=>int)) => uint) g; // 错误

2. 使用方法

在智能合约中,mapping 类型的使用非常普遍的。比如,在 ERC20 代币合约中,经常会使用 mapping 类型的变量作为一个内部账本,用来记录每一个钱包地址拥有的代币余额。

下面,我们模拟一个稳定币 USDT 的合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract USDT {
  mapping(address => uint256) balances; // 保存所有持有 USDT 账户的余额
  
  // 构造函数,合约部署时自动调用
  constructor() {
    balances[msg.sender] = 100; // 初始设定合约部署者的账户余额为 100 USDT
  }

  // 查询某一个账户的USDT余额
  function balanceOf(address account) public view returns(uint256) {
    return balances[account];
  }
}

在这个合约中,首先定义了一个保存所有持有 USDT  账户余额的 mapping 类型的变量 balances,用来作为一个内部账本。

mapping(address => uint256) balances; // 保存所有持有 USDT 账户的余额

获取某个账号地址 0x5B38...ddC 持有 USDT  的数量:

uint256 value = balances[0x5B38...ddC];

设置某个账号地址 0x5B38...ddC 持有 USDT  的数量:

balances[0x5B38...ddC4] = 100 ;

我们把上面例子中的合约代码复制到 Remix,进行编译,并部署到区块链上:

 

将上方的合约部署者地址 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,填写到函数 balanceOf 的参数位置,点击 balanceOf 后,返回的结果为 100。

我们使用另外一个地址作为参数,比如 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,点击 balanceOf 后,返回的调用结果为 0。

注意:mapping 类型的变量中获取一个不存在的键的值,并不会报错,而是会返回值类型的默认值。比如,整型会返回 0,布尔型会返回 false 等。

3. 优缺点

优点

mapping 可以用来存储数据集,并且提供了高效的查找功能。它可以根据“键”快速定位到特定元素。

我们知道,数组也可以用来存储数据集,但是在数组里面查找特定元素,就必须通过循环语句,遍历整个数组进行匹配,所以查找效率非常低,使用也不方便。

在数据集很大的情况下,通过 mapping 查找数据,效率要比数组高很多。

缺点

mapping 最大的问题是无法直接遍历。你不能使用 for 循环遍历 mapping 中的键值对,它也没有提供获取全部 key 或者 value 的功能。因此,在合约中遍历或迭代 mapping 类型的数据时,通常需要设计其他数据结构来辅助实现。

我们在后面的课程中,会单独讲解这方面的内容。