1. 程式人生 > >Solidity學習::(7)資料位置特性

Solidity學習::(7)資料位置特性

資料位置特性

引入 

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】可以看作是:簡單的資料拷貝,隨後修改變數不會影響另一個。