1、在中心化的application中,代码被翻译成逻辑,从而操作数据,而数据一般都储存在数据库中。
2、在去中心化的Dapp中,区块链本身就是一个数据库,因此只要用一个属性来标识数据(变量),就可以让其永久地存储在区块链中。
数据位置,变量的存储位置属性。有三种类型,memory
,storage
和calldata
。
最后一种数据位置比较特殊,一般只有外部函数的参数(不包括返回参数)被强制指定为calldata
。这种数据位置是只读的,不会持久化到区块链。
一般我们可以选择指定的是memory
和storage
。
memory
存储位置同我们普通程序的内存类似。即分配,即使用,越过作用域即不可被访问,等待被回收。
而对于storage
的变量,数据将永远存在于区块链上。
默认属性
- 默认的函数参数,包括返回的参数,他们是memory。而默认的局部变量是storage的。
- 状态变量,合约内声明的公有变量。默认的数据位置是storage。
- 注意区分函数中的storage属性的局部变量,与合约中的共有变量(状态变量),虽然都是storage属性的,但是有所不同,局部变量虽然是一个storage的,但它仅仅是一个storage类型的指针。
对于结构体变量 struct S{string a;uint b;}
【若函数中tmp这样定义:S tmp=S("memory", 0); 那么这个tmp是memory属性的】,需要加memory才能编译
【若在函数外用S tmp=S("memory", 0); 那么,这个tmp还是stroage属性的】
一、当我们尝试编译下边一个合约时,会编译失败:
这段代码中,assign函数,尝试将参数memort_temp赋值给函数内的局部变量temp
pragma solidity ^0.4.0;
contract SimpleAssign{
struct S{string a;uint b;}
function assign(S memory_temp) internal{
S tmp = memory_temp;
}
}
报错如下:
TypeError: Type struct SimpleAssign.S memory is not implicitly convertible to expected type struct SimpleAssign.S storage pointer (memory标识的 结构体变量 不能隐式转换成 期望的storage标识的指针)
原因分析:
解决方案:
利用状态变量【合约中的变量(类似全局变量)】作为中间变量
pragma solidity ^0.4.0;
contract SimpleAssign{
struct S{string a;uint b;}
S s; //状态变量(合约中的全局变量)【默认也为storage属性】
function assign(S memory_temp) internal{
s=memory_temp;//(利用状态变量作为中间变量)
}
}
另:当然我们也可以直接将参数中的 S memory_temp 改成 S storage memory_temp,指定传入变量也是storage属性的。
二、storge属性的变量赋值给storge属性的变量【storage转storage】
先看下面给出的一段合约代码:
- 这段代码的converStorage函数就是上面所述的将参数指定为storage属性的,从而实现传参。
- 然后在converStorage函数内部定义了一个局部变量S tmp,并将传入的变量赋值到tmp。最后对tmp.a进行赋值
- call函数则是调用converStorage函数,传入变量为合约的全局变量s,然后返回s.a
pragma solidity ^0.4.0;
contract StorageConvertToStorage{
struct S{string a;uint b;}
//默认是storage的
S s;
function convertStorage(S storage stor) internal{
S tmp = stor;//将参数值传入tmp变量
tmp.a = "Test";//修改tmp变量
}
function call() returns (string){
convertStorage(s);//修改tmp变量同时也修改了传入的参变量s
//那么在参数列表中使用storage,那么这个传参方式类似于引用,可以看作是传入一个地址
return s.a;//这里返回s.a,而不是tmp.a
//返回信息可以看见修改tmp也会修改合约中的全局变量s
}
}
顺利编译通过后,部署。
最后点击call进行调用。
调用返回结果:
call函数中,最后返回的是s.a ,但在整个合约中我们并没有对s.a进行赋值操作,只有对tmp.a进行了赋值操作
原因分析:
storage
类型的变量赋值给另一个storage
时,我们只是修改了它的指针,也就是这两个变量指向同一个地址,改变其中一个,都会使该地址内容改变。
三、现在,我们回到第一个问题,memory属性的变量赋值给storage属性变量,此时修改memory属性变量的值,storage属性的变量会跟着改变吗?【memory转storage】
测试代码:
代码中:
- 在合约中初始化一个全局变量 S s=S("this is storage", 0);
- 在call函数中,定义memory属性变量s2,并初始化
- 在call函数中,s2作为参数调用assign()
- assign函数中,memory属性的参数赋值给全局变量s,然后修改参数的值
- 最后在call函数中,修改s2的值,返回s.a
pragma solidity ^0.4.0;
contract SimpleAssign{
struct S{string a;uint b;}
S s=S("this is storage", 0);
function assign(S memory memory_temp) internal{
s=memory_temp;//s2的值给了s
memory_temp.a="test";//修改memory_temp的值
}
function call() returns(string){
S memory s2=S("this is memory", 0);
assign(s2);
s2.a="s2 is changed";//修改s2的值
//操作表明,在函数中memory的的值赋值给stroage变量,只会把值复制过去
//随后在再修改s2或者memory_temp的值,s的值都不会随之改变
return s.a;
}
}
测试结果:
从结果可以看见,s.a的值为s2初始化的值,也就是说:将一个memory类型的变量赋值给一个状态变量时,实际是将内存变量拷贝到存储中。在函数中memory的的值赋值给stroage变量,只会把值复制过去,随后在再修改s2或者memory_temp的值,s的值都不会随之改变。
四、【storage转memory】
测试代码:
pragma solidity ^0.4.0;
contract StorageToMemory{
struct S{string a;uint b;}
S s = S("storage", 1);
function storageToMemory(S storage x) internal{
S memory tmp = x;//由Storage拷贝到memory中
//memory的修改不影响storage
tmp.a = "Test";
}
function call() returns (string){
storageToMemory(s);
return s.a;
}
}
测试结果:
结果表明:将storage转为memory,实际是将数据从storage拷贝到memory中。 拷贝后对tmp变量的修改,完全不会影响到原来的storage变量。
五、最后一个:【memory转memory】
代码中:
- 在call函数中定义memory属性的变量mem,并初始化
- 然后mem作为参数,调用memoryToMemory,参数赋值给新的memory属性变量tmp
- 随后修改tmp的值
- 最后call函数返回mem.a
测试代码 :
pragma solidity ^0.4.0;
contract MemoryToMemory{
struct S{string a;uint b;}
//默认参数是memory
function memoryToMemory(S s) internal{
S memory tmp = s;
//引用传递
tmp.a = "other memory";
}
function call() returns (string){
S memory mem = S("memory", 1);
memoryToMemory(mem);
return mem.a;//other memory
}
}
测试结果:
从结果看:这个结果类似于【storage转storage】的结果,表明 memory之间赋值,不是数据的拷贝,而是引用传值。memoryToMemory()传递进来了一个memory类型的变量,在函数内将之赋值给tmp,修改tmp的值,发现外部的memory也被改为了other memory。
再看一个例子:
pragma solidity ^0.4.0;
contract MemoryToMemory{
struct S{string a;uint b;}
function call() returns (string){
S memory mem = S("memory", 1);
S memory mem2=mem;
mem2.a="changed";
return mem.a;//other memory
}
}
返回结果:
结果同样表明,memory转memory类似于指针的赋值了。
contract SandwichFactory {
struct Sandwich {
string name;
string status;
}
Sandwich[] sandwiches;
function eatSandwich(uint _index) public {
// Sandwich mySandwich = sandwiches[_index];
// ^ 看上去很直接,不过 Solidity 将会给出警告
// 告诉你应该明确在这里定义 `storage` 或者 `memory`。
// 所以你应该明确定义 `storage`:
Sandwich storage mySandwich = sandwiches[_index];
// ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针
// 在存储里,另外...
mySandwich.status = "Eaten!";
// 这里也把sandwiches[_index] 改成了"Eaten!"
// ...这将永久把 `sandwiches[_index]` 变为区块链上的存储
// 如果你只想要一个副本,可以使用`memory`:
Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了
// 另外
anotherSandwich.status = "Eaten!";
// ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响
// 不过你可以这样做:
sandwiches[_index + 1] = anotherSandwich;
// ...如果你想把副本的改动保存回区块链存储
}
}