引用类型的数据位置
不同于之前值类型,引用类型占的空间更大,超过256字节,因为拷贝它们占用更多的空间。由此我们需要考虑将它们存储在什么位置内存(memory,数据不是永久存在的)或存储(storage,数据永久的保存在数据块上)
引用类型数据位置分类
- memory
- storage
- calldata
memory
存储位置同我们普通程序的内存类似。即分配,即使用,越过作用域即不可被访问,等待被回收。
storage
数据将永远存在于区块链上。
calldata
一般只有外部函数的参数(不包括返回参数)被强制指定为calldata。这种数据位置是只读的,不会持久化到区块链,效果跟 memory 差不多。
注意:
1. 函数参数、返回值、函数内局部变量,引用类需要强制指定存储位置
2. 返回值只能指定memory位置
3. 引用类型的状态变量只能存储在storage, 且不能指定
不同数据位置变量间相互赋值
storage与storage相互赋值
当我们把一个storage类型的变量赋值给另一个storage时,只是修改了它的指针(引用传递)。
pragma solidity ^0.5.0;
contract StorageConvertToStorage{
struct S{
string a;uint b;}
//默认是storage的
S public s;
function convertStorage(S storage _s) internal{
_s.a = "hello";
_s.b = 100;
}
function call() public returns (string memory){
convertStorage(s);
return s.a;//Test
}
}
在上面的代码中,我们将传入的storage变量,并修改_s.a = “hello”,最后我们发现合约的状态变量s也被修改了。
memory给storage赋值
因为局部变量和状态变量的类型都可能是storage。所以我们要分开来说这两种情况。
1.memory赋值给状态变量
将一个memory类型的变量赋值给一个状态变量时,实际是将内存变量拷贝到存储中。
pragma solidity ^0.5.0;
contract MemoryConvertToStateVariable{
struct S{
string a;uint b;}
//默认是storage的
S public s;
constructor()public {
}
function memoryToState() internal{
S memory m_s;
m_s.a = "hello";
m_s.b = 100;
s = m_s;
m_s.a = "world";
m_s.b = 99;
}
function call() public {
memoryToState();
}
}
通过上例,我们发现,在convertStorage()中,我们把m_s赋值给s后,再修改m_s值,并不能使s产生任何变化。赋值时,完成了值拷贝,后续他们不再有任何的关系。
2.memory赋值给局部变量
由于在区块链中,storage必须是静态分配存储空间的。局部变量虽然是一个storage的,但它仅仅是一个storage类型的指针。如果进行这样的赋值,实际会产生一个错误。
pragma solidity ^0.5.0;
contract MemoryToLocalVar{
struct S{
string a;uint b;}
S s;
constructor()public{
s.a = "hello";
s.b = 10;
}
function memoryToLocal(S memory _s) internal{
//Type struct MemoryToLocalVar.S memory is not implicitly convertible to expected type struct MemoryToLocalVar.S storage pointer.
S storage tmp = _s;
}
function call() view public {
memoryToLocal(s);
}
}
通过上面的代码,我们可以看到这样的赋值的确不被允许。
storage转为memory
将storage转为memory,实际是将数据从storage拷贝到memory中。
pragma solidity ^0.5.0;
contract StorageToMemory{
struct S{
string a;uint b;}
S public s = S("hello", 100);
function storageToMemory() view internal{
S memory tmp = s;
//memory的修改不影响storage
tmp.a = "world";
}
function call() view public{
storageToMemory();
}
}
在上面的例子中,我们看到,拷贝后对tmp变量的修改,完全不会影响到原来的storage变量。
memory转为memory
memory之间是引用传递,并不会拷贝数据。我们来看看下面的代码。