映射 mapping
本章学习 Solidity
映射类型的定义和使用方法。
Solidity
中的映射类型 mapping
,用来以键值对的形式存储数据。它的主要作用是提供高效的查找功能,类似于其它编程语言中的哈希表或者字典。
例如,在使用 白名单
的场景中,我们可以通过 映射类型
将用户地址映射到一个布尔值,以标识哪些地址是被允许的,哪些地址是不被允许的,从而高效地控制访问权限。
映射类型是智能合约中最常用的数据类型之一,我们必须熟练掌握。
1. 定义
定义一个映射类型使用 mapping
关键字,它的语法如下:
mapping(key_type => value_type)
mapping
类型是将一个键 (key) 映射到一个值 (value)。
其中:key_type 可以是任何基本数据类型,比如:整型、地址型、布尔型、枚举型,以及 bytes
和 string
,但是部分复杂对象不允许使用,比如:动态数组、结构体、映射。
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
类型的数据时,通常需要设计其他数据结构来辅助实现。
我们在后面的课程中,会单独讲解这方面的内容。