Solidity中的引用類型?
引用類型(Reference Type):包括數組(array
)和結構體(struct
),由于這類變量比較復雜,占用存儲空間大,我們在使用時必須要聲明數據存儲的位置。
數據位置?
Solidity數據存儲位置有三類:storage
,memory
和calldata
。不同存儲位置的gas
成本不同。storage
類型的數據存在鏈上,類似計算機的硬盤,消耗gas
多;memory
和calldata
類型的臨時存在內存里,消耗gas
少。大致用法:
-
storage
:合約里的狀態變量默認都是storage
,存儲在鏈上。 -
memory
:函數里的參數和臨時變量一般用memory
,存儲在內存中,不上鏈。尤其是如果返回數據類型是變長的情況下,必須加memory修飾,例如:string, bytes, array和自定義結構。 -
calldata
:和memory
類似,存儲在內存中,不上鏈。與memory
的不同點在于calldata
變量不能修改(immutable
),一般用于函數的參數。例子:function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){//參數為calldata數組,不能被修改// _x[0] = 0 //這樣修改會報錯return(_x); }
數據位置和賦值規則?
? ? ? ?在不同存儲類型相互賦值時候,有時會產生獨立的副本(修改新變量不會影響原變量),有時會產生引用(修改新變量會影響原變量)。規則如下:
-
賦值本質上是創建引用指向本體,因此修改本體或者是引用,變化可以被同步:
storage
(合約的狀態變量)賦值給本地storage
(函數里的)時候,會創建引用,改變新變量會影響原變量。例子:
uint[] x = [1,2,3]; // 狀態變量:數組 xfunction fStorage() public{//聲明一個storage的變量 xStorage,指向x。修改xStorage也會影響xuint[] storage xStorage = x;xStorage[0] = 100;
}
memory
賦值給memory
,會創建引用,改變新變量會影響原變量。-
其他情況下,賦值創建的是本體的副本,即對二者之一的修改,并不會同步到另一方
變量的作用域?
Solidity
中變量按作用域劃分有三種,分別是狀態變量(state variable),局部變量(local variable)和全局變量(global variable)
1. 狀態變量?
狀態變量是數據存儲在鏈上的變量,所有合約內函數都可以訪問,gas
消耗高。狀態變量在合約內、函數外聲明:
contract Variables {uint public x = 1;uint public y;string public z;
}
我們可以在函數里更改狀態變量的值:
function foo() external{// 可以在函數里更改狀態變量的值x = 5;y = 2;z = "0xAA";
}
2. 局部變量?
局部變量是僅在函數執行過程中有效的變量,函數退出后,變量無效。局部變量的數據存儲在內存里,不上鏈,gas
低。局部變量在函數內聲明:
function bar() external pure returns(uint){uint xx = 1;uint yy = 3;uint zz = xx + yy;return(zz);
}
3. 全局變量?
全局變量是全局范圍工作的變量,都是solidity
預留關鍵字。他們可以在函數內不聲明直接使用:
function global() external view returns(address, uint, bytes memory){address sender = msg.sender;uint blockNum = block.number;bytes memory data = msg.data;return(sender, blockNum, data);
}
在上面例子里,我們使用了3個常用的全局變量:msg.sender
,block.number
和msg.data
,他們分別代表請求發起地址,當前區塊高度,和請求數據。下面是一些常用的全局變量,更完整的列表請看這個鏈接:
blockhash(uint blockNumber)
: (bytes32
) 給定區塊的哈希值 – 只適用于256最近區塊, 不包含當前區塊。block.coinbase
: (address payable
) 當前區塊礦工的地址block.gaslimit
: (uint
) 當前區塊的gaslimitblock.number
: (uint
) 當前區塊的numberblock.timestamp
: (uint
) 當前區塊的時間戳,為unix紀元以來的秒gasleft()
: (uint256
) 剩余 gasmsg.data
: (bytes calldata
) 完整call datamsg.sender
: (address payable
) 消息發送者 (當前 caller)msg.sig
: (bytes4
) calldata的前四個字節 (function identifier)msg.value
: (uint
) 當前交易發送的?wei
?值block.blobbasefee
: (uint
) 當前區塊的blob基礎費用。這是Cancun升級新增的全局變量。blobhash(uint index)
: (bytes32
) 返回跟當前交易關聯的第?index
?個blob的版本化哈希(第一個字節為版本號,當前為0x01
,后面接KZG承諾的SHA256哈希的最后31個字節)。若當前交易不包含blob,則返回空字節。這是Cancun升級新增的全局變量。
4. 全局變量-以太單位與時間單位?
以太單位?
Solidity
中不存在小數點,以0
代替為小數點,來確保交易的精確度,并且防止精度的損失,利用以太單位可以避免誤算的問題,方便程序員在合約中處理貨幣交易。
wei
: 1gwei
: 1e9 = 1000000000ether
: 1e18 = 1000000000000000000
function weiUnit() external pure returns(uint) {assert(1 wei == 1e0);assert(1 wei == 1);return 1 wei;
}function gweiUnit() external pure returns(uint) {assert(1 gwei == 1e9);assert(1 gwei == 1000000000);return 1 gwei;
}function etherUnit() external pure returns(uint) {assert(1 ether == 1e18);assert(1 ether == 1000000000000000000);return 1 ether;
}
時間單位?
可以在合約中規定一個操作必須在一周內完成,或者某個事件在一個月后發生。這樣就能讓合約的執行可以更加精確,不會因為技術上的誤差而影響合約的結果。因此,時間單位在Solidity
中是一個重要的概念,有助于提高合約的可讀性和可維護性。
seconds
: 1minutes
: 60 seconds = 60hours
: 60 minutes = 3600days
: 24 hours = 86400weeks
: 7 days = 604800
function secondsUnit() external pure returns(uint) {assert(1 seconds == 1);return 1 seconds;
}function minutesUnit() external pure returns(uint) {assert(1 minutes == 60);assert(1 minutes == 60 seconds);return 1 minutes;
}function hoursUnit() external pure returns(uint) {assert(1 hours == 3600);assert(1 hours == 60 minutes);return 1 hours;
}function daysUnit() external pure returns(uint) {assert(1 days == 86400);assert(1 days == 24 hours);return 1 days;
}function weeksUnit() external pure returns(uint) {assert(1 weeks == 604800);assert(1 weeks == 7 days);return 1 weeks;
}
測試代碼:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;contract DataStorage {// The data location of x is storage.// This is the only place where the// data location can be omitted.uint[] public x = [1,2,3];function fStorage() public{//聲明一個storage的變量xStorage,指向x。修改xStorage也會影響xuint[] storage xStorage = x;xStorage[0] = 100;}function fMemory() public view{//聲明一個Memory的變量xMemory,復制x。修改xMemory不會影響xuint[] memory xMemory = x;xMemory[0] = 100;xMemory[1] = 200;uint[] memory xMemory2 = x;xMemory2[0] = 300;}function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){//參數為calldata數組,不能被修改// _x[0] = 0 //這樣修改會報錯return(_x);}
}contract Variables {uint public x = 1;uint public y;string public z;function foo() external{// 可以在函數里更改狀態變量的值x = 5;y = 2;z = "0xAA";}function bar() external pure returns(uint){uint xx = 1;uint yy = 3;uint zz = xx + yy;return(zz);}function global() external view returns(address, uint, bytes memory){address sender = msg.sender;uint blockNum = block.number;bytes memory data = msg.data;return(sender, blockNum, data);}function weiUnit() external pure returns(uint) {assert(1 wei == 1e0);assert(1 wei == 1);return 1 wei;}function gweiUnit() external pure returns(uint) {assert(1 gwei == 1e9);assert(1 gwei == 1000000000);return 1 gwei;}function etherUnit() external pure returns(uint) {assert(1 ether == 1e18);assert(1 ether == 1000000000000000000);return 1 ether;}function secondsUnit() external pure returns(uint) {assert(1 seconds == 1);return 1 seconds;}function minutesUnit() external pure returns(uint) {assert(1 minutes == 60);assert(1 minutes == 60 seconds);return 1 minutes;}function hoursUnit() external pure returns(uint) {assert(1 hours == 3600);assert(1 hours == 60 minutes);return 1 hours;}function daysUnit() external pure returns(uint) {assert(1 days == 86400);assert(1 days == 24 hours);return 1 days;}function weeksUnit() external pure returns(uint) {assert(1 weeks == 604800);assert(1 weeks == 7 days);return 1 weeks;}
}