在Solidity中,数据位置非常重要,因为它可以影响变量、函数参数和返回值的行为。本文将详细探讨Solidity中的三种数据位置:storage、memory和calldata。
Storage是永久性存储位置,存储在以太坊区块链上。在合同中定义的变量、在结构体和数组中的元素及其映射键/值对都在storage中存储。对storage的访问是最昂贵的,并且只能使用整数类型、地址类型、定长数组和结构体。
下面是一个例子,演示了如何在storage中存储和读取变量:
pragma solidity ^0.8.0;
contract Storage {
uint256 number; // stored in storage
function setNumber(uint256 _number) public {
number = _number;
}
function getNumber() public view returns (uint256) {
return number;
}
}
在这里,number变量存储在合同的storage中。setNumber函数将_uint256_类型的参数存储到number变量中,而getNumber函数从storage中读取number变量的值并返回它。
Memory是临时性存储位置,它不会持久化在以太坊区块链中。在函数调用期间,函数参数和局部变量都存储在memory中。对memory的访问较便宜,但是只能使用整数类型、地址类型、定长数组、结构体、枚举和字符串。
下面是一个演示如何在memory中存储和读取数据的例子:
pragma solidity ^0.8.0;
contract Memory {
function doSomething(uint256[] memory values) public pure returns (uint256) {
uint256 sum;
for (uint256 i =0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
}
在这里,函数doSomething接收一个uint256类型的动态数组values。在函数执行期间,values数组存储在memory中。函数计算values数组中所有元素的总和,然后将其作为返回值返回。
Calldata是一种特殊的存储位置,它用于存储在函数调用期间传递给合同的参数和数据。与memory相似,calldata的访问较便宜,但是只能使用定长字节数组。
下面是一个演示如何在calldata中读取数据的例子:
pragma solidity ^0.8.0;
contract Calldata {
function doSomething(bytes memory data) public pure returns (bytes memory) {
return data;
}
}
在这里,函数doSomething接收一个bytes类型的参数data。data参数传递给函数在calldata中存储。函数返回接收到的参数data,然后返回它。
在Solidity中,数据位置非常重要。Storage用于在区块链上永久存储数据,它很昂贵,只能使用基本类型和结构体。Memory用于在函数调用期间存储临时数据,它相对便宜,但不能存储动态数组。Calldata用于在函数调用期间传递参数和数据,它相对便宜,但只能使用定长字节数组。了解这些位置对于编写高效和经济的代码至关重要。
变量声明为存储(Storage)、内存(Memory)或调用数据(Calldata),以显式指定数据的位置。
storage指向存储状态变量。 memory和calldata用于只读参数以避免修改状态。
Storage - 存储在区块链上的数据。存储是持久化的,函数调用结束后数据仍会保留。所有状态变量都存储在这里(变量是一个状态变量(存储在区块链上))
Memory - 存储临时数据,函数调用结束后会被清除。不会持久化存储在区块链上。主要用于存储函数的参数和局部变量。(变量位于内存中,并且在调用函数时存在)
Calldata - 与Memory类似,用于临时存储函数调用参数。但Calldata是只读的,不可修改。Calldata通常用于外部函数调用,可以减少gas fee。(包含函数参数的特殊数据位置)
简单来说:
Storage - 永久存储,存储状态变量
Memory - 临时存储,函数局部变量
Calldata - 临时只读存储,外部函数调用参数
memory和calldata都只是存在于function中的修饰符, struct/mapping/Array这三种类型的变量做名义参数时,需要在前面加上memory或者calldata
当你需要状态持久化时,使用Storage。当你需要临时空间存储数据时,使用Memory。外部函数调用参数建议使用Calldata节省gas。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract DataLocations {
uint[] public arr;
mapping(uint => address) map;
struct MyStruct {
uint foo;
}
mapping(uint => MyStruct) myStructs;
function f() public {
// 具有状态变量的 call _ f
_f(arr, map, myStructs[1]);
// 从映射中获取结构
MyStruct storage myStruct = myStructs[1];
// 在内存中创建结构
MyStruct memory myMemStruct = MyStruct(0);
}
function _f(
uint[] storage _arr,
mapping(uint => address) storage _map,
MyStruct storage _myStruct
) internal {
// 对存储变量进行处理
}
// 可以返回内存变量
function g(uint[] memory _arr) public returns (uint[] memory) {
// 用内存数组做一些事情
}
function h(uint[] calldata _arr) external {
// 使用 Calldata 数组做一些事情
}
}
internal关键字的作用是将_f函数设置为内部函数,意味着_f函数只能被contract内的其他函数调用,外部无法调用。
同时要注意: internal函数与private函数不同的是:
- private函数只能在定义它的contract内调用。
- internal函数可以在定义它的contract内以及继承该contract的子contract中调用。