solidity|solidity 基本语法 - 引用类型

引用类型 引用类型和值类型相对,在传递时,并不拷贝一份新的数据,而是传递是指向同一份数据的引用。下面看一个例子:

pragma solidity ^0.4.23; contract RefTypeTest { event PrintValue(uint value1, uint value2); function testRefType() public { uint[] x; x.push(1); x.push(2); x.push(3); uint[] y = x; emit PrintValue( x[0],y[0]); y[0] = 0; emit PrintValue( x[0],y[0]); } }

上述代码输出的结果为:
[ { "event": "PrintValue", "args": { "0": "1", "1": "1", "value1": "1", "value2": "1", "length": 2 } }, { "event": "PrintValue", "args": { "0": "0", "1": "0", "value1": "0", "value2": "0", "length": 2 } } ]

从结果中可以看出,x 和y 指向了同一份数据。solidity中的引用类型包括,动态数组和结构体两种。
存储位置 solidity中对于复杂的类型(array,struct)有一个额外的概念用于定义变量(成员)的存储位置。solidity 中存储位置分别有memorystoragecalldata. storage是持久化的存储,memorycalldata类似是非持久化的存储。
对于不同的类型变量,都拥有一个默认的存储位置,可以通过memory或是storage关键字改变。对于某些类型的变量,solidity会使用一个强制的存储位置,具体的规则如下:
  • Forced data location:
    • parameters (not return) of external functions: calldata
    • state variables: storage
  • Default data location:
    • parameters (also return) of functions: memory
    • all other local variables: storage
复杂类型 动态数组(Arrays)
动态数组是指在运行期间确定元素个数的为主,solidity中的格式为:
T[] array;

动态数组有两个成员:
  • push 向数组追加元素
  • length 获得数组中元素的个数
对于存储在memory中的动态数组,push方法将无法使用,这是由于EVM(solidity虚拟机)在组织不同位置的数据采用了不同的数据结构导致,后续post会详细介绍这些数据结构。
byte[] storageArray; function pushToArray(byte value) public { storageArray.push(value); byte[] memory memoryArray = new byte[](15); // memoryArray.push(value); compile error }

bytes 和 string是两种特殊的数组,bytes 和 byte[]类似,string 是特殊的bytes类型,但是无法访问push 和length元素。
结构体(struct)
struct 是solidity中用于定义新的数据结构的方法,其语法和c语言中的结构体类似。
struct Operation { bytes31 hash; uint8 status; }Operation[] operations; function addOperation(bytes31 opHash,uint8 status) public { Operation memory op; op.hash = opHash; op.status = status; operations.push(op); }

存储位置对复杂类型的影响 复杂类型存储在不同位置的变量,相互赋值的行为会有差异,下面通过一个例子来介绍:
pragma solidity ^0.4.23; contract Contract{ uint[] x; // the data location is storage uint[] y; // the data location is storageevent PrintUint(uint value1, uint value2); // Test memory assigin to storage function m2s(uint[] memoryArray) public { x = memoryArray; // works, copies the whole array to storage x[0] = 1; emit PrintUint(x[0], memoryArray[0]); }// Test assigin storage to storage function s2s() public { x[0] = 0; y = x; y[0] = 1; emit PrintUint(x[0], y[0]); }// Test assigin storage to memory function s2m() public { y[0] = 0; uint[] memory memoryArray = y; memoryArray[0] = 1; emit PrintUint(memoryArray[0], y[0]); }// Test memoryArray assigin to memoryArray function m2m(uint[] memoryArray) public { // default local variable is in storage // uint[] memoryArray1 = memoryArray; uint[] memory memoryArray1 = memoryArray; memoryArray1[0] = 1; emit PrintUint(memoryArray[0], memoryArray1[0]); } }

分别测试上述四个函数,结果如下:
  • m2s
[ { "event": "PrintUint", "args": { "0": "1", "1": "0", "value1": "1", "value2": "0", "length": 2 } } ]

  • m2m
    输入 [0]
    输出:
[ { "event": "PrintUint", "args": { "0": "1", "1": "1", "value1": "1", "value2": "1", "length": 2 } } ]

  • s2m
[ { "event": "PrintUint", "args": { "0": "1", "1": "0", "value1": "1", "value2": "0", "length": 2 } } ]

  • s2s
[ { "event": "PrintUint", "args": { "0": "0", "1": "1", "value1": "0", "value2": "1", "length": 2 } } ]

从上面的输出结果可以看出,对于存储在不同位置的复杂类型,赋值操作的表现会有所不同,总结如下:
  • 【solidity|solidity 基本语法 - 引用类型】storage to storage 拷贝数据, 表现为值类型
  • storage to memory 拷贝数据,表现为值类型
  • memory to storage 拷贝数据,表现为值类型
  • memory to memory 拷贝引用,表现为引用类型

    推荐阅读