有三種類型,memory,storage和calldata,一般只有外部函數的參數(不包括返回參數)被強制指定為calldata。這種數據位置是只讀的,不會持久化到區塊鏈
storage存儲或memory內存
memory存儲位置同我們普通程序的內存類似,即分配,即使用,動態分配,越過作用域即不可被訪問,等待被回收。
而對於storage的變量,數據將永遠存在於區塊鏈上。
總結¶
強制指定的數據位置:
• 外部函數的參數(不包括返回參數): calldata,效果跟 memory 差不多
• 狀態變量: storage
默認數據位置:
• 函數參數(包括返回參數): memory
• 所有其它局部變量: storage
下面舉例說明賦值行為:
1.memory = storage (值傳遞,互不影響)
pragma solidity ^0.4.24;
contract Person {
int public _age;
constructor (int age) public {
_age = age;
}
function f() public view{
modifyAge(_age);
}
function modifyAge(int age) public pure{
age = 100;
}
}
在這里一開始deploy合約時,傳入的age值為30,此時_age的值為30
然后運行f()函數,在這里使用了為storage類型的_age作為函數modifyAge的參數,相當於創建了一個臨時變量age(memory類型),將storage類型的變量_age賦值給memory類型的變量age,是值傳遞,所以在modifyAge函數中,age變量的值的變化並不會影響到_age變量的值
所以再查看_age的值,還是為30
2.storage = memory
當storage是狀態變量(即全局變量時),為值傳遞
當storage為局部變量時,該賦值會出錯,解決方法是將storage的局部變量聲明為memory即可
1)當storage為局部變量時:
如下面的例子:
pragma solidity ^0.4.24; contract Person { string public _name; constructor() public { _name = "liyuechun"; } function f() public view{ modifyName(_name); } function modifyName(string name) public pure{ string memory name1 = name; bytes(name1)[0] = 'L'; } }
調用f()函數,將storage類型的狀態變量_name作為參數賦值給函數modifyName(string) memory類型的name形參,為memory = storage,為值傳遞
然后在函數modifyName(string)中,還將memory類型的name形參賦值給memory類型的name1局部變量,memory = memory,為引用傳遞,改變一個另一個也跟着改變,但是因為先是進行了值傳遞,name與_name之間已經互不影響了,所以不會跟着改變_name
2)當storage為狀態變量時:
pragma solidity ^0.4.24; contract Person { string public _name; string public changedName; constructor() public { _name = "liyuechun"; } function f() public{//不能在聲明為view modifyName(_name); } function modifyName(string name) public{//不能在聲明為view changedName = name; bytes(name)[0] = 'L'; } }
warning:function declared as view,but this expression(potentially) modifies the state and thus requires non-payable(the default) or payable.
因為函數modifyName(string)改變了值changedName的狀態,所以不能聲明為view了
調用f()函數,將storage類型的狀態變量_name作為參數賦值給函數modifyName(string) memory類型的name形參,為memory = storage,為值傳遞
然后memory類型的name形參賦值給storage類型的狀態變量changedName,storage = memory,為值傳遞,因此name的值的改變不會導致changedName的值的改變,更不要說_name了
調用f()后為:
3.storage = storage
是引用傳遞,所以一個值的變化一定會導致另一個值的變化
pragma solidity ^0.4.24; contract Person { string public _name; constructor() public { _name = "liyuechun"; } function f() public{ modifyName(_name); } function modifyName(string storage name) internal { string storage name1 = name; bytes(name1)[0] = 'L'; } }
⚠️:如果modifyName(string)函數不聲明為internal會報錯:
TypeError:Location has to be memory for publicly visible functions(remove the "storage" keyword)
這是因為形參是默認為memory類型的,這里聲明為storage,那么函數的類型就必須聲明為internal或者private
調用f()函數,首先會將為storage類型的_name變量賦值給modifyName(string)函數storage類型的name形參,storage = storage,為引用傳遞
然后在modifyName(string)函數中,將storage類型的name變量賦值給storage類型的name1變量,storage = storage,為引用傳遞
都為引用傳遞,所以最后name1值的變化會導致_name的值的變化
調用f()后:
其實在這里如果將modifyName(string)函數改成如下,也是能夠成功的,因為其實沒必要進行兩次引用傳遞:
function modifyName(string storage name) internal { bytes(name)[0] = 'L'; }
4.memory = memory
是引用傳遞,所以一個值的變化一定會導致另一個值的變化
pragma solidity ^0.4.24; contract Person { function modifyName(string name) public pure returns(string){ string memory name1 = name; bytes(name1)[0] = 'L'; return name; } }
這里調用modifyName(string)函數,將memory類型的形參賦值給memory類型的局部變量name1,memory = memory,為引用傳遞
這時候改變name1的值,從return 的name可以看到,它的值也隨之改變