Solidity是一種靜態類型語言,這意味着需要指定每個變量(狀態和本地)的類型。Solidity提供了幾種基本類型,可以組合形成複雜類型。
此外,類型可以在包含運算符的表達式中相互交互。有關各種運算符的快速參考,請參閱運算符的優先順序。
值類型
以下類型也稱爲值類型,因爲這些類型的變量將始終按值傳遞,即它們在用作函數參數或賦值時始終被複制。
布爾
bool
:可能的值是常量true
和false
。
運營商:
!
(邏輯否定)&&
(邏輯連詞,“和”)||
(邏輯分離,“或”)==
(平等)!=
(不等式)
整數
int
/ uint
:各種大小的有符號和無符號整數。關鍵字uint8
以uint256
在步驟8
(無符號的8到256位)和int8
到int256
。uint
和int
是別名uint256
和int256
分別。
運營商:
- 比較:
<=
,<
,==
,!=
,>=
,>
(計算結果爲bool
) - 位運算符:
&
,|
,^
(按位異或),~
(按位取反) - 換班操作員:(
<<
左移),>>
(右移) - 算術運算符:
+
,-
,一元-
,*
,/
,%
(模),**
(冪)
比較
比較的值是通過比較整數值獲得的值。
位操作
位操作是在數字的二進制補碼錶示上執行的。這意味着,例如。~int256(0) == int256(-1)
轉移
移位操作的結果具有左操作數的類型。表達式相當於,對於正整數, 等價於。對於負數, 相當於將四捨五入的冪除以(向負無窮大)。按負數移動會引發運行時異常。x << y
x * 2**y
x >> y
x / 2**y
x
x >> y
2
警告
版本之前0.5.0
右移負等同於,用於向零舍入,而不是向負無窮舍即右移。x >> y
x
x / 2**y
加法,減法和乘法
加法,減法和乘法具有通常的語義。它們用二進制補碼錶示,例如。在設計安全的智能合約時,您必須考慮這些溢出。uint256(0) - uint256(1) == 2**256 - 1
表達式-x
等同於where 的類型。這意味着如果類型是無符號整數類型,則不會爲負數。此外,如果是否定的,可以是積極的。還有另一個警告也是由兩個補碼錶示:(T(0) - x)
T
x
-x
x
-x
x
int x = -2**255;
assert(-x == x);
這意味着即使數字爲負數,也不能假設它的否定是正數。
分部
由於操作結果的類型始終是其中一個操作數的類型,因此對整數的除法總是產生整數。在Solidity中,分部向零舍入。這意味着。
int256(-5) / int256(2) == int256(-2)
請注意,相反,文字除法會產生任意精度的小數值。
注意
除以零會導致斷言失敗。
模數
模運算產生操作數 除以操作數後的餘數,其中和。這意味着模數與左操作數(或零)產生相同的符號,並保持爲負數:
a % nranq = int(a / n)r = a - (n * q)a % n == -(abs(a) % n)a
int256(5) % int256(2) == int256(1)
int256(5) % int256(-2) == int256(1)
int256(-5) % int256(2) == int256(-1)
int256(-5) % int256(-2) == int256(-1)
注意
模數爲零會導致失敗的斷言。
指數
Exponentiation僅適用於未簽名類型。請注意您使用的類型足夠大以保存結果併爲潛在的包裝行爲做好準備。
注意
請注意,0**0
由EVM定義爲1
。
定點數
警告
Solidity尚未完全支持定點數。
fixed
/ ufixed
:各種大小的有符號和無符號定點數。關鍵字ufixedMxN
和fixedMxN
,其中M
表示類型佔用的位數,N
表示可用的小數點數。M
必須可被8整除,並從8位變爲256位。N
必須在0到80之間,包括0和80。 ufixed
和fixed
是別名ufixed128x18
和fixed128x18
分別。
運營商:
- 比較:
<=
,<
,==
,!=
,>=
,>
(計算結果爲bool
) - 算術運算符:
+
,-
,一元-
,*
,/
,%
(模)
注意
浮點(float
和double
許多語言,更準確地說是IEEE 754數字)和定點數之間的主要區別在於,用於整數的小數位數和小數部分(小數點後面的部分)在前者中是靈活的,而後者則嚴格定義。通常,在浮點中,幾乎整個空間用於表示數字,而只有少量位用於定義小數點的位置。
地址
地址類型有兩種形式,大致相同:
address
:保存一個20字節的值(以太坊地址的大小)。address payable
:相同address
,但附加成員transfer
和send
。
這種區別背後的想法是,您可以發送以太網的地址,而平原不能發送以太網。address payable
address
輸入轉化次數:
從隱式轉換到被允許的,而從轉換至是不可能的(執行這種轉換的唯一方法是通過使用中間轉換)。
address payableaddressaddressaddress payableuint160
地址文字可以隱式轉換爲。address payable
address
對於整數,整數文字bytes20
和契約類型,允許顯式轉換和轉換,但需要注意以下事項:不允許轉換表單。相反,表單轉換的結果 具有類型,如果是整數或固定字節類型,文字或具有應付回退函數的合約。如果是沒有應付回退功能的合約,那麼將是類型。在外部函數中,簽名用於和類型。
address payable(x)address(x)address payablexxaddress(x)addressaddressaddressaddress payable
注意
這很可能是,你並不需要關心的區別address
,並與只使用無處不在。例如,如果您正在使用提款模式,您可以(並且應該)將地址本身存儲爲,因爲您調用了該功能 ,這是一個。address payable
address
address
transfer
msg.sender
address payable
運營商:
<=
,<
,==
,!=
,>=
和>
警告
如果您將使用更大的字節大小所涉及的類型address
,例如bytes32
,然後address
被截斷。要減少編譯器強制轉換歧義版本0.4.24及更高版本,請在轉換中使截斷顯式化。以地址爲例0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC
。
你可以使用address(uint160(bytes20(b)))
,結果0x111122223333444455556666777788889999aAaa
,或者你可以使用address(uint160(uint256(b)))
,結果0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc
。
注意
版本0.5.0引入了address
和之間的區別。同樣從該版本開始,合約不是從地址類型派生的,但如果它們具有應付回退功能,則仍然可以顯式轉換。address payable
address
address payable
地址成員
有關所有地址成員的快速參考,請參閱地址類型的成員。
balance
和transfer
可以使用屬性查詢地址的餘額,balance
並使用以下transfer
函數將以太網(以wei爲單位)發送到應付地址:
address payable x = address(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
transfer
如果當前合同的餘額不夠大或接收帳戶拒絕以太網轉移,則該功能將失敗。該transfer
功能在故障時恢復。
注意
如果x
是合約地址,則其代碼(更具體地說:其後備功能,如果存在)將與transfer
呼叫一起執行(這是EVM的一項功能,無法阻止)。如果執行耗盡gas或以任何方式失敗,則以太網轉移將被恢復,當前合約將以例外停止。
send
發送是低級別的對應物transfer
。如果執行失敗,則當前合約不會因異常而停止,但send
會返回false
。
警告
使用中存在一些危險send
:如果調用堆棧深度爲1024(這可能始終由調用者強制執行),則傳輸失敗,並且如果接收方耗盡gas,它也會失敗。因此,爲了進行安全的以太傳輸,請始終檢查返回值send
,使用transfer
甚至更好:使用收件人提取資金的模式。
call
,delegatecall
和staticcall
爲了與不堅持ABI,或獲得過該編碼,功能更直接的控制合同接口call
,delegatecall
並staticcall
提供。它們都將一個參數作爲輸入並返回成功條件(作爲a )和返回的數據()。的功能,, 和可被用於編碼的結構化數據。bytes memory
bool
bytes memory
abi.encode
abi.encodePacked
abi.encodeWithSelector
abi.encodeWithSignature
例:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
警告
所有這些功能都是低級功能,應謹慎使用。具體來說,任何未知的合約都可能是惡意的,如果你調用它,你就可以將控制權移交給合約,而合約又可以回調你的合約,所以在調用返回時準備好改變你的狀態變量。與其他契約交互的常規方法是在契約對象(x.f()
)上調用函數。
注意
先前版本的Solidity允許這些函數接收任意參數,並且還可以處理bytes4
不同類型的第一個參數。在版本0.5.0中刪除了這些邊緣情況。
可以使用.gas()
修改器調整供應的gas:
namReg.call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));
同樣,也可以控制提供的Ether值:
nameReg.call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
最後,可以組合這些修飾符。他們的訂單無關緊要:
nameReg.call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
以類似的方式,delegatecall
可以使用該函數:不同之處在於僅使用給定地址的代碼,所有其他方面取自當前合同。目的delegatecall
是使用存儲在另一個合同中的庫代碼。用戶必須確保兩個合同中的存儲佈局都適合使用委託調用。
注意
在宅基地之前,只有一個有限的變體callcode
可用,不能提供對原始msg.sender
和msg.value
價值的訪問。此功能已在0.5.0版中刪除。
因爲staticcall
也可以使用。這基本上是相同的call
,但如果被調用的函數以任何方式修改狀態,它將恢復。
所有這三個功能call
,delegatecall
以及staticcall
非常低級別的功能,只能被用作最後的手段,因爲他們打破密實的類型安全。
該.gas()
選項適用於所有三種方法,但.value()
不支持該選項delegatecall
。
注意
所有合約都可以轉換爲address
類型,因此可以使用查詢當前合約的餘額address(this).balance
。
合約類型
每個合約都定義了自己的類型。您可以隱式地將合約轉換爲它們繼承的合約。合約可以明確地轉換爲所有其他合約類型和address
類型。
只有在合約類型具有應付回退功能時,才能進行與該類型的顯式轉換。轉換仍然使用而不是使用。您可以在有關地址類型的部分中找到更多信息。address payable
address(x)
address payable(x)
注意
如果聲明一個合約類型的局部變量(MyContract c),則可以調用該合約上的函數。注意從同一合約類型的某個地方分配它。
您還可以實例化合約(這意味着它們是新創建的)。您可以在“新合約” 部分中找到更多詳細信息。
合約的數據表示與address
類型的數據表示相同,並且此類型也在ABI中使用。
合約類型的成員是合約的外部功能,包括公共狀態變量。
固定大小的字節數組
的值類型bytes1
,bytes2
,bytes3
,...,bytes32
保持字節序列從一個到最多32 byte
是一個別名bytes1
。
運營商:
- 比較:
<=
,<
,==
,!=
,>=
,>
(計算結果爲bool
) - 位運算符:
&
,|
,^
(按位異或),~
(按位取反) - 移位操作員:(
<<
左移),>>
(右移) - 索引訪問:如果
x
是類型bytesI
,然後x[k]
對返回的第一個字節(只讀)。0 <= k < I
k
移位運算符使用任何整數類型作爲右操作數(但返回左操作數的類型),表示要移位的位數。以負數換算會導致運行時異常。
成員:
.length
產生字節數組的固定長度(只讀)。
注意
類型byte[]
是一個字節數組,但由於填充規則,每個元素浪費31個字節的空間(存儲除外)。最好使用該bytes
類型。
動態大小的字節數組
bytes
:
動態大小的字節數組,請參見數組。不是一種價值型!
string
:
動態大小的UTF-8編碼字符串,請參閱數組。不是一種價值型!
地址文字
通過地址校驗和測試的十六進制文字,例如 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
是類型。長度在39到41位之間並且未通過校驗和測試的十六進制文字產生警告,並被視爲常規有理數字文字。address payable
注意
混合大小寫地址校驗和格式在EIP-55中定義。
理性和整數文字
整數文字由0-9範圍內的一系列數字組成。它們被解釋爲小數。例如,69
意味着六十九。Solidity中不存在八進制文字,前導零無效。
小數分數文字由a形成,.
一側至少有一個數字。實例包括1.
,.1
和1.3
。
也支持科學記數法,其中基數可以有分數,而指數則不能。實例包括2e10
,-2e10
,2e-10
,2.5e1
。
下劃線可用於分隔數字文字的數字以幫助提高可讀性。例如,十進制123_000
,十六進制0x2eff_abde
,科學十進制表示法1_2e345_678
都是有效的。下劃線僅允許在兩位數之間,並且只允許一個連續的下劃線。沒有額外的語義含義添加到包含下劃線的數字文字中,下劃線被忽略。
數字文字表達式保留任意精度,直到它們轉換爲非文字類型(即通過將它們與非文字表達式一起使用或通過顯式轉換)。這意味着計算不會溢出,並且分割不會在數字文字表達式中截斷。
例如,雖然中間結果甚至不適合機器字大小,但結果是常量(類型)。此外,得到整數(儘管在它們之間使用非整數)。(2**800 + 1) - 2**800
1
uint8
.5 * 8
4
只要操作數是整數,任何可以應用於整數的運算符也可以應用於數字文字表達式。如果兩者中的任何一個是小數,則不允許位操作,如果指數是小數,則不允許取冪(因爲這可能導致非有理數)。
注意
Solidity對每個有理數都有一個數字類型。整數文字和有理數字文字屬於數字文字類型。此外,所有數字文字表達式(即僅包含數字文字和運算符的表達式)都屬於數字文字類型。所以數量字面表述,並都屬於相同數量的文本類型的有理數三人。1 + 2
2 + 1
警告
用於在版本0.4.0之前的Solidity中截斷的整數文字的除法,但它現在轉換爲有理數,即不等於,但是。5 / 2
2
2.5
注意
一旦將非文字表達式與非文字表達式一起使用,它們就會轉換爲非文字表達式。忽略類型,分配給b
下面的表達式的值求值爲整數。因爲a
是類型uint128
,表達式必須具有適當的類型。由於是的類型沒有普通型和,密實度編譯器不接受這樣的代碼。2.5 + a
2.5
uint128
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
字符串文字
字符串文字用雙引號或單引號("foo"
或'bar'
)編寫。它們並不像C中那樣暗示尾隨零; "foo"
代表三個字節,而不是四個字節 與整數文字一樣,它們的類型可以變化,但它們可以隱式轉換爲bytes1
...... bytes32
,如果它們適合,則可以轉換bytes
爲string
。
字符串文字支持以下轉義字符:
\<newline>
(逃避實際換行)\\
(反斜槓)\'
(單引號)\"
(雙引號)\b
(退格)\f
(換頁)\n
(新隊)\r
(回車)\t
(標籤)\v
(垂直標籤)\xNN
(十六進制逃脫,見下文)\uNNNN
(unicode逃逸,見下文)
\xNN
採用十六進制值並插入適當的字節,同時\uNNNN
採用Unicode代碼點並插入UTF-8序列。
以下示例中的字符串長度爲十個字節。它以換行符開頭,後跟雙引號,單引號爲反斜槓字符,然後(不帶分隔符)字符序列abcdef
。
"\n\"\'\\abc\
def"
任何不是換行符的unicode行終止符(即LF,VF,FF,CR,NEL,LS,PS)都被認爲是終止字符串文字。如果字符串文字前面沒有a,則換行符僅終止字符串文字\
。
十六進制文字
十六進制文字以關鍵字爲前綴,hex
並用雙引號或單引號(hex"001122FF"
)括起來。它們的內容必須是十六進制字符串,它們的值將是這些值的二進制表示形式。
十六進制文字的行爲類似於字符串文字,並具有相同的可轉換性限制。
枚舉
枚舉是在Solidity中創建用戶定義類型的一種方法。它們可以顯式轉換爲所有整數類型,但不允許隱式轉換。在運行時從整數檢查顯式轉換,該值位於枚舉範圍內,否則會導致失敗的斷言。枚舉至少需要一名成員。
數據表示與C中的枚舉相同:選項由後續的無符號整數值表示0
。
pragma solidity >=0.4.16 <0.6.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity. The integer type used is just
// large enough to hold all enum values, i.e. if you have more than 256 values,
// `uint16` will be used and so on.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
}
函數類型
函數類型是函數的類型。函數類型的變量可以從函數中分配,函數類型的函數參數可以用於將函數傳遞給函數調用並從函數調用返回函數。函數類型有兩種形式 - 內部和外部函數:
內部函數只能在當前合約內部調用(更具體地說,在當前代碼單元內部,也包括內部庫函數和繼承函數),因爲它們不能在當前合約的上下文之外執行。通過跳轉到其條目標籤來實現調用內部函數,就像在內部調用當前合約的函數一樣。
外部函數由地址和函數簽名組成,它們可以通過外部函數調用傳遞和返回。
函數類型標註如下:
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]
與參數類型相反,返回類型不能爲空 - 如果函數類型不返回任何內容,則 必須省略整個部分。returns (<return types>)
默認情況下,函數類型是內部函數,因此internal
可以省略關鍵字。請注意,這僅適用於函數類型。必須爲合同中定義的函數明確指定可見性,它們沒有默認值。
轉換:
可以顯式轉換外部函數類型的值,address
從而得到函數合約的地址。
函數類型A
可以隱式轉換爲函數類型,B
當且僅當它們的參數類型相同,它們的返回類型相同,它們的內部/外部屬性相同且狀態可變性A
不比狀態可變性更嚴格B
。特別是:
pure
功能可以被轉換爲view
與non-payable
功能view
函數可以轉換爲non-payable
函數payable
函數可以轉換爲non-payable
函數
函數類型之間不可能進行其他轉換。
關於payable
並且non-payable
可能有點混亂的規則,但實質上,如果函數是payable
,這意味着它也接受零以太的支付,所以它也是non-payable
。另一方面,non-payable
函數將拒絕發送給它的以太,因此non-payable
函數不能轉換爲payable
函數。
如果未初始化函數類型變量,則調用它會導致失敗的斷言。如果在使用後調用函數,也會發生同樣的情況delete
。
如果在Solidity上下文之外使用外部函數類型,則將它們視爲function
類型,它將函數標識符一起編碼爲單個bytes24
類型的地址。
請注意,當前合同的公共函數既可以用作內部函數,也可以用作外部函數。要f
用作內部函數,只需使用f
,如果要使用其外部表單,請使用this.f
。
成員:
公共(或外部)函數也有一個特殊的成員調用selector
,它返回ABI函數選擇器:
pragma solidity >=0.4.16 <0.6.0;
contract Selector {
function f() public pure returns (bytes4) {
return this.f.selector;
}
}
顯示如何使用內部函數類型的示例:
pragma solidity >=0.4.16 <0.6.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
另一個使用外部函數類型的示例:
pragma solidity >=0.4.22 <0.6.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // known contract
uint exchangeRate;
function buySomething() public {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(oracle),
"Only oracle can call this."
);
exchangeRate = response;
}
}
注意
Lambda或內聯函數已計劃但尚不支持。
參考類型
可以通過多個不同的名稱修改引用類型的值。將此與值類型進行對比,只要使用值類型的變量,您就可以獲得獨立的副本。因此,必須比值類型更謹慎地處理引用類型。目前,引用類型包括結構,數組和映射。如果使用引用類型,則必須顯式提供存儲類型的數據區域:( memory
其生命週期僅限於函數調用),storage
(存儲狀態變量的位置)或calldata
(包含特殊數據的位置)函數參數,僅適用於外部函數調用參數)。
更改數據位置的分配或類型轉換將始終產生自動複製操作,而同一數據位置內的分配僅在某些情況下複製存儲類型。
數據位置
每個引用類型(即數組和結構)都有一個附加註釋,即“數據位置”,關於它的存儲位置。有三個數據位置: memory
,storage
和calldata
。Calldata僅對外部合約函數的參數有效,並且是此類參數所必需的。Calldata是一個不可修改的非持久性區域,其中存儲了函數參數,其行爲大多類似於內存。
注意
在版本0.5.0之前,數據位置可以省略,並且根據變量的類型,函數類型等默認爲不同的位置,但是所有複雜類型現在必須給出明確的數據位置。
數據位置不僅與數據的持久性相關,而且與分配的語義相關:存儲和內存(或來自calldata)之間的分配始終創建獨立的副本。從內存到內存的分配僅創建引用。這意味着在引用相同數據的所有其他內存變量中也可以看到對一個內存變量的更改。從存儲到本地存儲變量的分配也僅分配引用。相反,存儲的所有其他分配始終複製。這種情況的示例是狀態變量的賦值或存儲結構類型的局部變量的成員,即使局部變量本身只是一個引用。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint[] x; // the data location of x is storage
// the data location of memoryArray is memory
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// This does not work either, since it would "reset" the pointer, but there
// is no sensible location it could point to.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
數組
數組可以具有編譯時固定大小,也可以是動態的。對元素的限制很少,它也可以是另一個數組,映射或結構。但是,對類型的一般限制適用於映射只能用於存儲,而公開可見的函數需要ABI類型的參數。
固定大小k
和元素類型的數組T
被寫爲T[k]
動態大小的數組T[]
。例如,5個動態數組的數組uint
是uint[][5]
(注意,與其他語言相比,符號反轉)。要訪問第三個動態數組中的第二個uint,您可以使用x[2][1]
(索引從零開始,訪問以與聲明相反的方式工作,即x[2]
從右側削減類型中的一個級別)。
在其末尾訪問數組會導致恢復。如果要添加新元素,則必須使用.push()
或增加該.length
成員(請參閱下文)。
類型的變量bytes
和string
特殊數組。A bytes
類似於byte[]
,但它在calldata和內存中緊密包裝。string
等於bytes
但不允許長度或索引訪問。所以bytes
應該總是優先考慮,byte[]
因爲它更便宜。根據經驗,使用bytes
任意長度的原始字節數據和string
任意長度的字符串(UTF-8)數據。如果你可以限制長到一定的字節數,總是用一個bytes1
來bytes32
,因爲他們是便宜得多。
注意
如果要訪問字符串的字節表示s
,請使用 bytes(s).length
/ 。請記住,您正在訪問UTF-8表示的低級字節,而不是單個字符!bytes(s)[7] = 'x';
可以標記數組public
並使Solidity創建一個getter。數字索引將成爲getter的必需參數。
分配內存數組
您可以使用該new
關鍵字在內存中創建具有運行時相關長度的數組。相對於存儲陣列,它是不能夠調整大小的存儲器陣列(例如,通過分配給.length
成員)。您必須提前計算所需的大小或創建新的內存陣列並複製每個元素。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f(uint len) public pure {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
assert(a.length == 7);
assert(b.length == len);
a[6] = 8;
}
}
數組文字/內聯數組
數組文字是作爲表達式編寫的數組,不會立即分配給變量。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f() public pure {
g([uint(1), 2, 3]);
}
function g(uint[3] memory) public pure {
// ...
}
}
數組文字的類型是固定大小的內存數組,其基類型是給定元素的通用類型。請注意,目前,固定大小的內存陣列無法分配給動態大小的內存陣列,即以下內容不可能:[1, 2, 3]
uint8[3] memory
uint8
uint
pragma solidity >=0.4.0 <0.6.0;
// This will not compile.
contract C {
function f() public {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] memory x = [uint(1), 3, 4];
}
}
計劃在將來消除這種限制,但由於數組如何在ABI中傳遞,目前會產生一些複雜性。
成員
長度:
數組的length
成員包含其元素數。一旦創建,內存數組的長度是固定的(但是動態的,即它可以取決於運行時參數)。對於動態大小的陣列(僅適用於存儲),可以指定此成員來調整陣列的大小。訪問當前長度之外的元素不會自動調整數組的大小,而是會導致失敗的斷言。增加長度會爲數組添加新的零初始化元素。減少長度delete
會對每個刪除的元素執行隱式:ref:。
推:
動態存儲陣列和bytes
(不string
)具有一個名爲的成員函數push
,可用於在數組末尾附加元素。該元素將被初始化爲零。該函數返回新的長度。
流行:
動態存儲陣列和bytes
(不string
)具有一個名爲的成員函數pop
,可用於從數組末尾刪除元素。這也隱式調用:ref:delete
on被刪除的元素。
警告
如果.length--
在空數組上使用它會導致下溢,從而將長度設置爲2**256-1
。
注意
增加存儲陣列的長度具有恆定的gas成本,因爲存儲被假定爲零初始化,而減小長度至少具有線性成本(但在大多數情況下比線性更差),因爲它包括明確清除被移除的元素類似於打電話:ref:delete
關於他們。
注意
在外部函數中不可能使用數組數組(但在公共函數中支持它們)。
注意
在Byzantium之前的EVM版本中,無法從函數調用中訪問動態數組返回。如果調用返回動態數組的函數,請確保使用設置爲Byzantium模式的EVM。
pragma solidity >=0.4.16 <0.6.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// Note that the following is not a pair of dynamic arrays but a
// dynamic array of pairs (i.e. of fixed size arrays of length two).
// Because of that, T[] is always a dynamic array of T, even if T
// itself is an array.
// Data location for all state variables is storage.
bool[2][] m_pairsOfFlags;
// newPairs is stored in memory - the only possibility
// for public contract function arguments
function setAllFlagPairs(bool[2][] memory newPairs) public {
// assignment to a storage array performs a copy of ``newPairs`` and
// replaces the complete array ``m_pairsOfFlags``.
m_pairsOfFlags = newPairs;
}
struct StructType {
uint[] contents;
uint moreInfo;
}
StructType s;
function f(uint[] memory c) public {
// stores a reference to ``s`` in ``g``
StructType storage g = s;
// also changes ``s.moreInfo``.
g.moreInfo = 2;
// assigns a copy because ``g.contents``
// is not a local variable, but a member of
// a local variable.
g.contents = c;
}
function setFlagPair(uint index, bool flagA, bool flagB) public {
// access to a non-existing index will throw an exception
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) public {
// if the new size is smaller, removed array elements will be cleared
m_pairsOfFlags.length = newSize;
}
function clear() public {
// these clear the arrays completely
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// identical effect here
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes memory data) public {
// byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]"
m_byteData = data;
m_byteData.length += 7;
m_byteData[3] = 0x08;
delete m_byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// Dynamic memory arrays are created using `new`:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Inline arrays are always statically-sized and if you only
// use literals, you have to provide at least one type.
arrayOfPairs[0] = [uint(1), 2];
// Create a dynamic byte array:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(uint8(i));
return b;
}
}
結構
Solidity提供了一種以結構形式定義新類型的方法,如以下示例所示:
pragma solidity >=0.4.11 <0.6.0;
contract CrowdFunding {
// Defines a new type with two fields.
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address payable beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// Creates new struct in memory and copies it to storage.
// We leave out the mapping type, because it is not valid in memory.
// If structs are copied (even from storage to storage), mapping types
// are always omitted, because they cannot be enumerated.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) public payable {
Campaign storage c = campaigns[campaignID];
// Creates a new temporary memory struct, initialised with the given values
// and copies it over to storage.
// Note that you can also use Funder(msg.sender, msg.value) to initialise.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
合約不提供衆籌合約的全部功能,但它包含了解結構所必需的基本概念。結構類型可以在映射和數組中使用,它們本身可以包含映射和數組。
雖然struct本身可以是映射成員的值類型,但它可以包含其類型的動態大小數組,但結構不可能包含其自己類型的成員。這種限制是必要的,因爲結構的大小必須是有限的。
請注意,在所有函數中,如何將結構類型分配給具有數據位置的局部變量storage
。這不會複製結構但只存儲引用,以便對局部變量成員的賦值實際寫入狀態。
當然,您也可以直接訪問結構的成員而不將其分配給局部變量,如。campaigns[campaignID].amount = 0
映射
使用語法聲明映射類型。用戶定義的或複雜的類型,例如合同類型,枚舉,映射,結構和除了和不允許的任何數組類型。 可以是任何類型,包括映射。mapping(_KeyType => _ValueType)
_KeyType
bytes
string
bytes
string
_ValueType
您可以將映射視爲散列表,它實際上是初始化的,這樣每個可能的鍵都存在並映射到一個值,其字節表示全爲零,即類型的默認值。相似性在那裏結束,關鍵數據不存儲在映射中,只有其keccak256
散列用於查找值。
因此,映射沒有設置密鑰或值的長度或概念。
映射只能具有數據位置,storage
因此允許用於狀態變量,作爲函數中的存儲引用類型,或作爲庫函數的參數。它們不能用作參數或返回公開可見的合同函數的參數。
您可以將映射類型的變量標記爲,public
並且Solidity 爲您創建一個 getter。它_KeyType
成爲getter的參數。如果_ValueType
是值類型或結構,則getter返回_ValueType
。如果_ValueType
是數組或映射,則getter對每個參數都有一個參數_KeyType
遞歸。例如,使用映射:
pragma solidity >=0.4.0 <0.6.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(address(this));
}
}
注意
映射不可迭代,但可以在它們之上實現數據結構。有關示例,請參閱可迭代映射。
刪除
delete a
爲類型指定初始值a
。即整數它相當於,但它也可以用於數組,它可以分配長度爲零的動態數組或相同長度的靜態數組,並重置所有元素。對於結構,它分配一個結構,重置所有成員。換句話說,after 的值與沒有賦值時聲明的值相同,但需要注意以下幾點:a = 0
a
delete a
a
delete
對映射沒有影響(因爲映射的鍵可能是任意的,通常是未知的)。因此,如果刪除結構,它將重置所有不是映射的成員,並且除非它們是映射,否則還會遞歸到成員中。但是,可以刪除單個鍵及其映射到的內容:如果a
是映射,則將刪除存儲在的值。delete a[x]
x
重要的是要注意,它的行爲類似於賦值,即它存儲一個新對象。當引用變量時,這種區別是可見的:它只會重置自身,而不是之前引用的值。delete a
a
a
a
a
pragma solidity >=0.4.0 <0.6.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // sets x to 0, does not affect data
delete data; // sets data to 0, does not affect x
uint[] storage y = dataArray;
delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
assert(y.length == 0);
}
}
基本類型之間的轉換
隱式轉換
如果運算符應用於不同類型,則編譯器會嘗試將其中一個操作數隱式轉換爲另一個操作數的類型(對於賦值也是如此)。一般來說,如果語義有意義並且沒有信息丟失,uint8
則值類型之間的隱式轉換是可能的:可轉換爲uint16
和轉換 int128
爲int256
,但int8
不可轉換爲uint256
(因爲uint256
不能保持例如-1
)。
有關詳細信息,請參閱有關類型本身的部分。
顯式轉換
如果編譯器不允許隱式轉換但您知道自己在做什麼,則有時可能會進行顯式類型轉換。請注意,這可能會給您一些意外的行爲並允許您繞過編譯器的某些安全功能,因此請務必測試結果是否符合您的要求!使用以下示例將負數int8
轉換爲uint
:
int8 y = -3;
uint x = uint(y);
在此代碼段的末尾,x
將具有值0xfffff..fd
(64個十六進制字符),在256位的二進制補碼錶示中爲-3。
如果將整數顯式轉換爲較小的類型,則會截斷高階位:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
如果將整數顯式轉換爲更大的類型,則將其填充在左側(即在更高的訂單端)。轉換結果將等於原始整數。
uint16 a = 0x1234; uint32 b = uint32(a); // b將是0x00001234現在斷言(a == b);
固定大小的字節類型在轉換期間表現不同。它們可以被認爲是單個字節的序列,轉換爲較小的類型會切斷序列:
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12
如果將固定大小的字節類型顯式轉換爲更大的類型,則將其填充在右側。以固定索引訪問字節將導致轉換前後的相同值(如果索引仍在範圍內):
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
由於整數和固定大小的字節數組在截斷或填充時表現不同,因此只允許整數和固定大小字節數組之間的顯式轉換(如果兩者具有相同的大小)。如果要在不同大小的整數和固定大小的字節數組之間進行轉換,則必須使用中間轉換,以使所需的截斷和填充規則顯式:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // d will be 0x12
文字和基本類型之間的轉換
整數類型
十進制和十六進制數字文字可以隱式轉換爲任何大小足以表示它而不截斷的整數類型:
uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456
固定大小的字節數組
十進制數字文字不能隱式轉換爲固定大小的字節數組。十六進制數字文字可以是,但僅當十六進制數字的數量完全符合字節類型的大小時。作爲例外,具有零值的十進制和十六進制文字都可以轉換爲任何固定大小的字節類型:
bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine
字符串文字和十六進制字符串文字可以隱式轉換爲固定大小的字節數組,如果它們的字符數與字節類型的大小相匹配:
bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed
地址
如地址文字中所述,通過校驗和測試的正確大小的十六進制文字屬於address
類型。沒有其他文字可以隱式轉換爲該address
類型。