Solidity学习::(8)数据位置特性

数据位置特性


引入 

1、在中心化的application中,代码被翻译成逻辑,从而操作数据,而数据一般都储存在数据库中。

2、在去中心化的Dapp中,区块链本身就是一个数据库,因此只要用一个属性来标识数据(变量),就可以让其永久地存储在区块链中。


 介绍

  • 数据位置,变量的存储位置属性。有三种类型,memorystoragecalldata

  • 最后一种数据位置比较特殊,一般只有外部函数的参数(不包括返回参数)被强制指定为calldata。这种数据位置是只读的,不会持久化到区块链。

  • 一般我们可以选择指定的是memorystorage

  • memory存储位置同我们普通程序的内存类似。即分配,即使用,越过作用域即不可被访问,等待被回收。

  • 而对于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标识的指针)

原因分析:

  1. 函数中,作为参数输入的S memory_temp,是默认为memory属性的变量
  2. 而函数中S tmp  是作为storage属性的变量
  3. 函数中定义的storage属性变量不能直接用memory属性的变量来赋值(函数之外的【合约的全局变量】变量则可以)。

解决方案: 

利用状态变量【合约中的变量(类似全局变量)】作为中间变量

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进行了赋值操作

原因分析:

  1. 在参数列表中使用storage,那么这个传参方式类似于引用,可以看作是传入一个地址
  2. 当我们把一个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类似于指针的赋值了。


总结: 

  • 【storage转storage】和【memory转memory】可以看作是:只是修改了它的指针,也就是这两个变量指向同一个地址,改变其中一个,都会使该地址内容改变。
  • 【storage转memory】和【memory转storage】可以看作是:简单的数据拷贝,随后修改变量不会影响另一个。

猜你喜欢

转载自blog.csdn.net/dieju8330/article/details/82876347