映射(Mapping)
说到映射(Mapping)
,有过其它编程语言经验的您,对这是再熟悉不过了。Solidity 中的映射(Mapping)
与Java
的Map
、Go
里的Map
以及javascript
的JSON
等众多编程语言中的Map
一样,是用来存储一组键(Key)
值(Value)
,可通过键(Key)
来获取对应的值
。
Solidity 声明映射(Mapping)
格式为:
mapping(KeyType => ValueType) //KeyType 键(key)类型 ValueType 值(Value)类型
mapping(uint => address) public addr;
映射中对键、值类型限制
- 1、mapping(KeyType => ValueType),其中
KeyType
可以是除了变长数组
、合约
、枚举
以及结构体的所有类型。然而
ValueType对类型没有任何限制,甚至可以包括
映射类型`。 - 2、映射的
存储位置
只能是存储(storage)
,因此便只允许作为状态变量
、函数内的存储(storage)引用
以及作为库函数的参数
。但不能用于public
函数的参数或返回结果中,因为mapping
记录的是一种关系 (key - value pair)。 - 3、可将
映射类型的状态变量
声明为public
,我们知道,在 Solidity 中,凡是将状态变量
声明为public
,Solidity 便会自动生成一个getter
函数,可通过mapping
中的键(key)
获取对应的值(value)
。
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(this);
}
}
下面是ERC20 Token
的简单版本. _allowances
是一个嵌套mapping的例子. _allowances
用来记录其他的账号,可以允许从其账号使用多少数量的币.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract MappingExample {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");
_allowances[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
require(_balances[sender] >= amount, "ERC20: Not enough funds.");
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
}
可迭代的映射
Solidity 中的映射本身是无法遍历的,即获取每一组键值对。不过,可通过结构体来实现迭代。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
struct itmap {
mapping(uint => IndexValue) data;
KeyFlag[] keys;
uint size;
}
type Iterator is uint; //自定义一个新类型
library IterableMapping {
function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
uint keyIndex = self.data[key].keyIndex;
self.data[key].value = value;
if (keyIndex > 0)
return true;
else {
keyIndex = self.keys.length;
self.keys.push();
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;
return false;
}
}
function remove(itmap storage self, uint key) internal returns (bool success) {
uint keyIndex = self.data[key].keyIndex;
if (keyIndex == 0)
return false;
delete self.data[key];
self.keys[keyIndex - 1].deleted = true;
self.size --;
}
function contains(itmap storage self, uint key) internal view returns (bool) {
return self.data[key].keyIndex > 0;
}
function iterateStart(itmap storage self) internal view returns (Iterator) {
return iteratorSkipDeleted(self, 0);
}
function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {
return Iterator.unwrap(iterator) < self.keys.length;
}
function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {
return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
}
function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {
uint keyIndex = Iterator.unwrap(iterator);
key = self.keys[keyIndex].key;
value = self.data[key].value;
}
function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
keyIndex++;
return Iterator.wrap(keyIndex);
}
}
// 如何使用
contract User {
// Just a struct holding our data.
itmap data;
// Apply library functions to the data type.
using IterableMapping for itmap;
// Insert something
function insert(uint k, uint v) public returns (uint size) {
// This calls IterableMapping.insert(data, k, v)
data.insert(k, v);
// We can still access members of the struct,
// but we should take care not to mess with them.
return data.size;
}
// Computes the sum of all stored data.
function sum() public view returns (uint s) {
for (
Iterator i = data.iterateStart();
data.iterateValid(i);
i = data.iterateNext(i)
) {
(, uint value) = data.iterateGet(i);
s += value;
}
}
}