在Solidity中,有一些数据类型是引用类型,如:

  • 数组(string和bytes是特殊的数组,也是引用类型)
  • 结构体(struct)
  • 映射(mapping)

在Solidity中使用引用类型的时候,必须指定数据的位置。

存储位置

在合约中声明的变量都有一个存储位置,用于指明变量的值存储在哪里。

Solidity提供了三种类型的存储位置:

  • storage
  • memory
  • calldata

storage

链上存储空间。该存储位置用于存储永久数据,只要合约存在数据就一直有效。存储的 Gas 较高。

其中:

  • 状态变量的存储方式强制是 storage
  • 局部变量的存储方式可以声明成 storagememorycalldata
  • 映射(mapping)只能存储在 storage 中。

memory

内存存储。即数据存储在内存中,数据只在其生命周期内(函数调用期间)有效。

calldata

调用数据。一个特殊的只读数据位置,用来存储函数调用的参数(包括内部和外部函数),类似于 memory。使用 calldata 变量的好处是,它不用将数据的副本保存到内存中,并确保不会修改数据。

总结:

  1. 状态变量的存储方式都是 storage
  2. 局部变量的存储方式可以声明成 storagememorycalldata
  3. 公用函数(public)和外部函数(external)中,函数参数的存储方式只能是 memorycalldata
  4. 私有函数(private)和内部函数(internal)中,函数参数的存储方式还可以是 storage
  5. 映射(mapping)只能存储在 storage 中,且不能在函数内部定义,映射一般声明为状态变量;
  6. 一般建议将函数参数和局部变量的存储方式声明为 memory

合约例子

下面是一个合约例子,用来演示数据存储位置的用法。

// SPDX-License-Identifier: MITpragma solidity ^0.8.13;// 变量存储位置和作用域contract DataStorages {// 状态变量强制为 storagestring public str;bytes public bs;uint[] public arr;mapping(address => uint) map;struct Student {string name;uint score;}Student[] public students;// 公共函数(public)和外部函数(external)中,存储位置可以声明成 meomory 和 calldata// 私有函数(private)和内部函数(internal)中,存储位置还可以声明成 storage// 字符串的存储位置function setString(string memory _str) public {str = _str;}// 字节的存储位置function setBytes(bytes memory _bs) public {bs = _bs;}// 数组的存储位置function setArray(uint[] memory _arr) public { for(uint i = 0; i < _arr.length; i++) {arr.push(_arr[i]);}}// 结构体的存储位置function setStruct(Student calldata _student) public {students.push(_student);}// 在内部函数(internal)中,变量可以声明成 storagefunction getLength(uint[] storage _arr) internal view returns(uint) {return(_arr.length);}// 调用内部函数function test() public view returns(uint) {return getLength(arr);}}

**输出:**我们在Remix中编译、部署和运行这个合约例子,执行结果如下图: