Solidity學習::(7)資料位置特性
資料位置特性
引入
1、在中心化的application中,程式碼被翻譯成邏輯,從而操作資料,而資料一般都儲存在資料庫中。
2、在去中心化的Dapp中,區塊鏈本身就是一個數據庫,因此只要用一個屬性來標識資料(變數),就可以讓其永久地儲存在區塊鏈中。
介紹
-
資料位置,變數的儲存位置屬性。有三種類型,
memory
,storage
和calldata
。 -
最後一種資料位置比較特殊,一般只有外部函式的引數(不包括返回引數)被強制指定為
calldata
。這種資料位置是隻讀的,不會持久化到區塊鏈。 -
一般我們可以選擇指定的是
memory
和storage
。 -
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標識的指標)
原因分析:
- 函式中,作為引數輸入的S memory_temp,是預設為memory屬性的變數
- 而函式中S tmp 是作為storage屬性的變數
- 函式中定義的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進行了賦值操作
原因分析:
- 在引數列表中使用storage,那麼這個傳參方式類似於引用,可以看作是傳入一個地址
- 當我們把一個
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】可以看作是:簡單的資料拷貝,隨後修改變數不會影響另一個。