引用类型
引用类型和值类型相对,在传递时,并不拷贝一份新的数据,而是传递是指向同一份数据的引用。下面看一个例子:
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 中存储位置分别有memory,storage和calldata. storage是持久化的存储,memory和calldata类似是非持久化的存储。
对于不同的类型变量,都拥有一个默认的存储位置,可以通过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 storage
event 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
}
}
]
从上面的输出结果可以看出,对于存储在不同位置的复杂类型,赋值操作的表现会有所不同,总结如下:
storage to storage 拷贝数据, 表现为值类型
storage to memory 拷贝数据,表现为值类型
memory to storage 拷贝数据,表现为值类型
memory to memory 拷贝引用,表现为引用类型