第17节:存储位置-memory、storage、calldata
小白入门:https://github.com/dukedaily/solidity-expert ,欢迎star转发,文末加V入群。
职场进阶: https://dukeweb3.com
solidity中的存储位置分为三种,使用memory、storage、calldata来进行区分:
- storage:属于状态变量,数据会存储在链上,仅适用于所有引用类型:string,bytes,数组,结构体,mapping等;
- memory:仅存储在内存中,供函数使用,数据不上链,适用于所有类型,包括:
- 值类型(int,bool,bytes8等)
- 引用类型(string,bytes,数组,结构体,mapping)
- calldata:存储函数的参数的位置,是只读的(只有calldata支持数组切片,状态变量不可以直接使用切片,需要new新数组,然后使用for循环解决)
- 其他:Solidity 变量中 memory 、calldata 2 个表示作用非常类似,都是函数内部临时变量,它们最大的区别就是 calldata 是不可修改的,在某些只读的情况比较省 Gas.
- 局部变量(此处指引用类型)默认是Storage类型的,只能将使用storage类型赋值,不能使用memory类型来赋值。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract DataLocations {
uint[] public arr = [1, 2, 3];
mapping(uint => address) public map;
struct MyStruct {
uint foo;
}
mapping(uint => MyStruct) public myStructs;
function test() public {
// get a struct from a mapping
MyStruct storage myStruct = myStructs[1];
// create a struct in memory
MyStruct memory myMemStruct = MyStruct(0);
// call _f with state variables
_f(arr, map, myStruct);
//invalid convertion, failed to call
// _f(arr, map, myMemStruct);
_g(arr);
this._h(arr);
}
function _f(
uint[] storage _arr,
mapping(uint => address) storage _map,
MyStruct storage _myStruct
) internal {
// do something with storage variables
_arr.push(100);
_map[20] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
_myStruct.foo = 20;
}
// You can return memory variables
function _g(uint[] memory _arr) public returns (uint[] memory) {
// do something with memory array
_arr[0] = 100;
}
function _h(uint[] calldata _arr) external {
// do something with calldata array
// calldata is read-only
// _arr[2] = 200;
}
}
memory和storage进阶
// SPDX-License-Identifier: MIT
pragma solidity =0.8.10;
import "hardhat/console.sol";
contract Test{
constructor() {
personArrayGlobal.push(Person("Lily", 20, true));
personArrayGlobal.push(Person("James", 30, false));
}
struct Person {
string name;
uint256 age;
bool married;
}
Person[] public personArrayGlobal;
// remix: [["Lily", 20, true]]
function changeTestMemory(Person[] memory _psersonArray) public {
Person memory pTmp = _psersonArray[0];
// error, 不能基于memory对象创建storage对象
// Person storage pTmp = _psersonArray[0];
_innerChangeMemory(pTmp);
console.log(pTmp.name); // David memory
console.log(_psersonArray[0].name); // David memory,居然改变了!虽然在memory中,但是实际上也是传递的指针
uint256 tmpInt = 200;
_innerChangeInt(tmpInt);
console.log(tmpInt); // 200,指类型的变量,总是直接复制一份
}
function _innerChangeInt(uint _newValue) internal pure {
_newValue = 100;
}
function _innerChangeMemory(Person memory _p) internal pure {
_p.name = "David memory";
_p.age = 30;
_p.married = false;
}
function _innerChangeStorage(Person storage _p) internal {
_p.name = "David Storage";
_p.age = 30;
_p.married = false;
}
// run before changeTestGlobalWithStorage
function changeTestGlobalWithMemory() public {
Person memory pTmp = personArrayGlobal[0];
_innerChangeMemory(pTmp);
// error,memory 不能赋值给storage
// _innerChangeStorage(pTmp);
console.log(pTmp.name); // David memory,memory中的变量改变了
console.log(personArrayGlobal[0].name); // Lily,原storage中数据未改变
}
// run after changeTestGlobalWithMemory
function changeTestGlobalWithStorage() public {
Person storage pTmp = personArrayGlobal[0];
// storage 赋值给memory,完全拷贝
_innerChangeMemory(pTmp);
console.log(pTmp.name); // Lily
console.log(personArrayGlobal[0].name); // Lily
// storage 赋值给storage,指针传递
_innerChangeStorage(pTmp);
console.log(pTmp.name); // David
console.log(personArrayGlobal[0].name); // David
}
}