翻譯原文
date:20170617
Solidity是靜態(tài)類型語言,這意味著每個變量的類型必須在編譯的時候指定(或者知曉炬称,查看以下類型推斷章節(jié))變量類型汁果。Solidity提供了多種簡單的類型,可以通過簡單類型組合成復雜的類型玲躯。
另外据德,類型可以與包含操作符的表達式互動。瀏覽不同的操作符跷车,參看操作符優(yōu)先級章節(jié)棘利。
值類型
以下的類型通常稱為值類型,因為這些類型的變量通常傳入的是值朽缴。例如在給函數(shù)傳入?yún)?shù)的時候或者在賦值的時候善玫,傳入的是值的拷貝。
布爾類型(Booleans)
bool
:可能的值只有true
或者false
.
操作符:
-
!
邏輯非 -
&&
邏輯與 -
||
邏輯或 -
==
等 -
!=
不等
操作符||
和&&
實行的是短路規(guī)則不铆。這意味著:在表達式f(x)||g(y)
中蝌焚,如果f(x)
的值是true
,g(y)
將不會執(zhí)行誓斥。
整形(Intergers)
int
/uint
:多種大小的有符號整數(shù)或者無符號整數(shù)只洒。關(guān)鍵詞uint8
到unint256
每8位為一步(無符號從8位到256位)和int8
到int256
。特別的劳坑,uint
和int
是uint256
和int256
的別名毕谴。
操作符:
- 比較符:
<=
,<
,==
,!=
,>=
,>
(返回值是bool
類型) - 位操作符:
&
,|
,^
(按位或),~
(按位非) - 算術(shù)符:
+
,-
,一元-
,一元+
,*
,/
,%
(求余),**
(?exponentiation),<<
(左移),>>
(右移動)
除法通常是會被截斷的(它只是編譯成EVM的DIV操作碼)涝开,但是如果兩個操作數(shù)都是字面量或者字面表達式.(?Division always truncates (it just is compiled to the DIV opcode of the EVM), but it does not truncate if both operators are literals (or literal expressions).)
被0除或者對0取模會拋出運行時異常循帐。
移位操作符的返回值的類型通常是左邊操作數(shù)的類型。表達式x<<y
等價于x * 2**y
,x>>y
等價于x / 2**y
舀武。這意味著負數(shù)的移位符號位會不變(ps:-2 << 2 = -8)拄养。目前移動負數(shù)位將會拋出運行時異常。(ps:2<<-2就會異常)
警告:Solidity負數(shù)的向右移位和其他的語言是不一樣的银舱。在Solidity中瘪匿,右移對應的是除法,所以右移負數(shù)的結(jié)果趨向于0(被截斷了)寻馏。其他語言中棋弥,右移負數(shù)的值是除法不截斷(趨向于負無窮大)(?In other programming languages the shift right of negative values works like division with rounding down (towards negative infinity).)
地址類(Address)
address
是一個20字節(jié)的值(也是以太坊地址的長短)诚欠。Address也有成員變量顽染,是所有合約的基礎(chǔ)。
操作符:
-
<=
,<
,==
,!=
,>=
和>
地址類的成員變量
-
balance
和transfer
快速瀏覽轰绵,參看地址類相關(guān)信息.
可以通過地址的balance
屬性查看賬號余額粉寞,可以通過transfer
將以太幣發(fā)送給其他賬號。
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
注意:如果x
是合約地址左腔,合約代碼(更具體些:是回調(diào)函數(shù))將會于transefer
函數(shù)一同執(zhí)行(這是EVM的限制仁锯,而且不能被阻止)。如果執(zhí)行過程中翔悠,gas消耗完畢了业崖,或者執(zhí)行失敗,轉(zhuǎn)賬就會被退回蓄愁,并拋出異常双炕。
-
send
發(fā)送是于transfer
相對應的。如果執(zhí)行失敗撮抓,合約并不馬上停止妇斤,拋出異常屁倔,而是會返回false
.
警告:使用send
會些風險:如果堆棧深度超過1024(該問題通常是調(diào)用端產(chǎn)生的)局雄,gas不足徙垫,會導致轉(zhuǎn)賬失敗藤违。所以為了使以太幣能夠安全轉(zhuǎn)賬,使用send
就得檢查返回結(jié)果强霎∷涸埽或者直接使用transfer
會更好:使用接受者取回錢幣的模式扁藕。(咬像?use a pattern where the recipient withdraws the money)
-
call
,callcode
和delegatecall
另外算撮,為了給那些沒有依附到ABI(Application Binary Interface)的合約提供接口生宛,call
函數(shù)可以接受任意多個不同類型的參數(shù)。這些參數(shù)會擴展到32位肮柜,并且串聯(lián)起來陷舅。但是第一個參數(shù)是編碼為4個字節(jié)的。审洞?In this case, it is not padded to allow the use of function signatures here.
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call
函數(shù)返回一個布爾量莱睁,用來說明調(diào)用的函數(shù)是正確執(zhí)行(返回true)還是發(fā)生異常(返回false)。所以我們不可能得到真實的數(shù)據(jù)返回芒澜。(為了得到真實數(shù)據(jù)缩赛,我們需要了解編碼和大小。撰糠?for this we would need to know the encoding and size in advance)
一個類似的方法,函數(shù)delegatecall
與call
不同的是辩昆,只是用了指定地址的地址碼阅酪,其他的參數(shù)(storage,余額等)用的都是當前合約的汁针。使用delegatecall
的目的是使用保存在其他合約中的庫术辐。用戶必須確認確認兩個合約的storage必須合適才行。在homestead版本之前施无,只有一個callcode
參數(shù)可用辉词,但是不能獲取到msg.sender
和msg.value
.
所有這三個參數(shù)call
,delegatecall
和callcode
都是非常低級的函數(shù),并且只在迫不得已的情況下使用猾骡。因為他們會危害Solidity的類型安全瑞躺。
以上三個函數(shù)都可以使用.gas()
函數(shù),但是delegatecall
并不支持.value()
函數(shù)兴想。
注意:所有合約都繼承了address的成員變量幢哨,所以可以通過this.balance
來訪問當前合約的余額
警告:這些函數(shù)都是底層函數(shù),并小心使用嫂便。任意未知的合約可能是惡意的捞镰。如果你調(diào)用它,你就把控制權(quán)交給了它毙替,它可以反過來再調(diào)用你岸售。所以執(zhí)行完畢之后,你的變量可能已經(jīng)發(fā)生改變了厂画。
固定大小的字節(jié)數(shù)組
bytes1
,bytes2
,bytes3
,...bytes32
凸丸。byte
是bytes1
的別名。
操作:
- 比較:
<=
,<
,==
,!=
,>=
,>
(返回bool
) - 位操作:
&
,|
,^
(按位異或),~
(按位取反),<<
(左移),>>
(右移) - 索引:如果
x
的類型是bytesI
袱院,那么x[k]
中甲雅,有0 <= k <= I
解孙,返回第k
位(只讀)。
移位操作符的右操作數(shù)可以是任意整數(shù)類型(但是返回做操作數(shù)的類型)抛人。右操作數(shù)表示的是移位的個數(shù)弛姜。如果右操作數(shù)是負數(shù),那么會引起運行時異常妖枚。
成員變量:
-
.length
返回字節(jié)數(shù)組的長度(只讀)
動態(tài)大小的字節(jié)數(shù)組
bytes
:動態(tài)大小的字節(jié)數(shù)組廷臼,參看Arrays,不是一種值類型。
string
:動態(tài)大小的UTF-8編碼的字符串绝页,參看Arrays,不是一種值類型荠商。
根據(jù)經(jīng)驗來說,如果需要用到任意長度的字節(jié)數(shù)據(jù)续誉,那么使用bytes
莱没;如果需要用到任意長度的字符串(UTF-8)數(shù)據(jù),那么使用string
.如果你可以確定長度酷鸦,那么使用bytes1
到bytes32
饰躲,因為它們會便宜些。
固定長度的數(shù)字
//todo 待完善
地址字面量
通過傳遞地址檢驗和測試的十六進制字面量是address
類型臼隔,例如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
嘹裂。39位到41位長度的十六進制字面量,沒有通過檢驗和測試的會產(chǎn)生警告摔握,并且作為平常的有理數(shù)字面量寄狼。
有理數(shù)整形字面量
整形變量是由0-9組成的一串數(shù)字。它們是十進制的氨淌。例如69
就是69泊愧。Solidity中并沒有八進制,以0開頭的是不合法的盛正。
十進制小數(shù)拼卵,以.
隔開,左右必須至少有一個字符蛮艰,例如1.
,.1
和1.3
腋腮。
科學表示法也是支持的,基數(shù)可以是小數(shù)壤蚜,但是指數(shù)不可以即寡。例如2e10
,-2e10
,2e-10
,2.5e1
袜刷。
數(shù)字字面量表達式保持精確的值聪富。直到類型變?yōu)榉亲置媪款愋停ɡ绾头亲置媪康闹狄黄鹗褂茫_@意味著數(shù)字字面量的計算不會溢出或者除法的時候不會截斷著蟹。
例如墩蔓,(2**800 + 1) - 2**800
的結(jié)果是常量1
(類型是uint8
類型)梢莽。盡管中間值超過了機器字的大小。(奸披? although intermediate results would not even fit the machine word size)另外昏名,.5*8
的結(jié)果是4
(因為表達式中沒有用到非整數(shù))。
如果結(jié)果不是整數(shù)阵面,會選擇合適的ufixed
或者fixed
類型來表示它們轻局。大小會計算出足夠它們使用的大小。(大概有理數(shù)中最差的情況样刷。)
在表達式var x = 1/4;
中仑扑,x
會使用ufixed0x8
類型,但是在var x = 1/3
中置鼻,將會使用ufixed0x256
類型镇饮。因為1/3
在二進制中無法精確表示。所以會是一個精確值箕母。
可以用在整數(shù)中的任何操作符储藐,也可以用在數(shù)字字面量表達式中,只要操作數(shù)是整數(shù)司蔬。如果有操作數(shù)是小數(shù),位操作符是不可以使用的姨蝴。指數(shù)如果是小數(shù)俊啼,指數(shù)運算也是不允許的。(因為會產(chǎn)生物理數(shù))左医。
注意:Solidity對于每種有理數(shù)授帕,都有一個數(shù)字字面量類型。整數(shù)字面量和有理數(shù)字面量都屬于數(shù)字字面量類型浮梢。另外跛十,所有的數(shù)字字面量表達式(例如只包含數(shù)字字面量和操作符的表達式)都是數(shù)字字面量類型。所以數(shù)字字面量表達式1+2
和2+1
都是屬于相同的數(shù)字字面量類型——有理數(shù)——3
注意:很多有盡十進制小數(shù)秕硝,如5.3743
芥映,在二進制表示法中是無盡的。5.3743
的正確類型是ufixed8x248
因為這樣可以更加精確的表示該數(shù)远豺。如果你想用其他類型奈偏,如ufixed(像ufixed128x128
),你必須要指明期望的精度:x + ufixed(5.3743)
警告:在早期版本中躯护,整數(shù)字面量的除法會有截斷惊来。但是現(xiàn)在會轉(zhuǎn)換為有理數(shù)。例如5 / 2
并不等于2
,而是2.5
注意:當數(shù)字字面量表達式與非字面量類型一起使用的時候棺滞,會轉(zhuǎn)變?yōu)榉亲置媪款愋筒靡稀T谌缦滤镜睦又惺冈ǎM管我們知道賦給b
的值是整形,但是在表達式中間枉证,仍然使用固定大小的浮點類型(非有理數(shù)字面量)矮男,所以代碼不會編譯。
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
字符串字面量
字符串字面量由單引號或者雙引號包裹(“foo”或者'bar')刽严。?They do not imply trailing zeroes as in C; "foo"
代表3個字節(jié)昂灵,不是四個。舞萄?As with integer literals, their type can vary, but they are implicitly convertible to bytes1
, ..., bytes32
, if they fit, to bytes
and to string
.
字符串字面量支持換碼符(escape characters)眨补,例如\n
,\xNN
和\uNNNN
倒脓。\xNN
使用十六進制的值撑螺,并插入合適的字節(jié)數(shù)據(jù)(?\xNN takes a hex value and inserts the appropriate byte,)崎弃。\uNNNN
使用的是unicode碼甘晤,插入的是UTF-8的字符串。(饲做?while \uNNNN takes a Unicode codepoint and inserts an UTF-8 sequence)
十六進制字面量
十六進制數(shù)據(jù)以hex
關(guān)鍵字打頭线婚,并且通過雙引號或者單引號包裹(hex"001122FF"
)。該字面量內(nèi)容必須是十六進制數(shù)據(jù)的字符串盆均,值必須是代表這些字符的二進制數(shù)據(jù)塞弊。(?Their content must be a hexadecimal string and their value will be the binary representation of those values.)
十六進制字面量有點像字符串字面量泪姨,并且也有一些轉(zhuǎn)換限制游沿。
枚舉類型
枚舉類型為用戶提過了一種在Solidity中自定義類型的方法。他們可以方便的轉(zhuǎn)換為整形肮砾,或者從整形轉(zhuǎn)換而來诀黍。但是隱式變換是不允許的。顯式變換在代碼執(zhí)行的時候檢查值的范圍仗处,執(zhí)行失敗會產(chǎn)生一個異常眯勾。枚舉類型至少需要一個數(shù)字。
pragma solidity ^0.4.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() {
choice = ActionChoices.GoStraight;
}
// 由于枚舉類型不是ABI的一部分婆誓。getChoice函數(shù)會自動的變換為“getChoice() returns (uint8)”咒精。整數(shù)類型的大小為能夠保存所有enum值的大小。例如:如果你有更大的數(shù)值旷档,就會使用uint16等模叙。
function getChoice() returns (ActionChoices) {
return choice;
}
function getDefaultChoice() returns (uint) {
return uint(defaultChoice);
}
}
函數(shù)類型
函數(shù)類型是函數(shù)的類型。函數(shù)類型的變量可以通過函數(shù)賦值鞋屈,函數(shù)類型的參數(shù)可以傳遞給函數(shù)范咨。函數(shù)也可以通過函數(shù)返回故觅。函數(shù)類型分為兩種-內(nèi)部函數(shù)和外部函數(shù)。
內(nèi)部函數(shù)只能在當前合約的內(nèi)部使用(更加準確的說渠啊,是當前代碼單元里输吏,代碼單元中包含內(nèi)部庫函數(shù)和繼承函數(shù)),因為他們不能在合約外部使用替蛉。調(diào)用內(nèi)部函數(shù)就像跳轉(zhuǎn)到函數(shù)的標簽處贯溅,就像在合約內(nèi)部調(diào)用函數(shù)。(躲查?Calling an internal function is realized by jumping to its entry label, just like when calling a function of the current contract internally.)
外部函數(shù)由地址和函數(shù)簽名組成它浅。他們可以從外部函數(shù)調(diào)用中被傳遞或者返回。(镣煮?External functions consist of an address and a function signature and they can be passed via and returned from external function calls.)
函數(shù)類型的格式如下:
function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]
與參數(shù)類型做對比姐霍,返回類型不能為空,如果函數(shù)沒有返回典唇,那么returns (<return types>)
都應該刪除镊折。
默認情況下,函數(shù)類型是內(nèi)部的介衔,所以internal
關(guān)鍵字可以刪除恨胚。
在當前合約中可以由兩種方法調(diào)用函數(shù):要么直接通過函數(shù)名稱,f
或者使用this.f
炎咖。第一種是內(nèi)部函數(shù)調(diào)用赃泡,后一種是外部函數(shù)。
如果函數(shù)類型變量沒有初始化塘装,那么調(diào)用就會引發(fā)異常急迂。如果你對函數(shù)delete
之后影所,再調(diào)用蹦肴,同樣會引發(fā)這個錯誤。
如果外部函數(shù)類型在Solidity外部調(diào)用猴娩,他們會被當作function
類型阴幌,該類型將地址和函數(shù)識別碼一同以bytes24
類型編碼。
需要注意的是卷中,當前合約的公有函數(shù)既可以用作內(nèi)部函數(shù)矛双,也可以用作外部函數(shù)。如果想用作內(nèi)部函數(shù)蟆豫,那么直接通過f
(函數(shù)名稱)來調(diào)用议忽,或者如果想用作外部函數(shù),通過this.f
來調(diào)用十减。
以下的例子顯示了如何使用內(nèi)部函數(shù)類型:
pragma solidity ^0.4.5;
library ArrayUtils {
// 內(nèi)部函數(shù)可以在內(nèi)部庫內(nèi)部使用栈幸,因為他們的執(zhí)行上下文一致
function map(uint[] memory self, function (uint) returns (uint) f)
internal
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) returns (uint) f
)
internal
returns (uint)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal 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) returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal returns (uint) {
return x + y;
}
}
如下例子愤估,說明如何使用外部函數(shù):
pragma solidity ^0.4.11;
contract Oracle {
struct Request {
bytes data;
function(bytes memory) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes data, function(bytes memory) external callback) {
requests.push(Request(data, callback));
NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) {
// 這里需要確認返回的數(shù)據(jù)來自受信任的源
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // 已知合約
function buySomething() {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) {
require(msg.sender == address(oracle));
// 使用數(shù)據(jù)
}
}
lambda表達式或者行內(nèi)函數(shù)還在計劃開發(fā)進程中,尚未支持速址。
引用類型
復雜的類型玩焰,例如,某種類型芍锚,總是不能很好的適應256位大小昔园,相比較我們之前的類型,需要更加小心處理并炮。因為拷貝它們可能會產(chǎn)生很大的花費默刚。我們必須考慮將它們保存在內(nèi)存(非連續(xù)的)中還是在storage(狀態(tài)變量保存的地方)中。
數(shù)據(jù)位置(Data location)
每種復雜的類型渣触,例如數(shù)組和結(jié)構(gòu)體羡棵,有一種另外的注釋,數(shù)據(jù)位置(data location)嗅钻,關(guān)于是否保存在內(nèi)存中皂冰,還是在Storage里。根據(jù)上下文养篓,會有個默認的類型秃流。但是我們也可以通過添加storage
或者memory
關(guān)鍵字來改變這個類型。函數(shù)的參數(shù)(包括返回值)都是保存在memory
中的柳弄,本地變量舶胀,狀態(tài)變量默認是storage
。
還有第三種數(shù)據(jù)位置碧注,“calldata”嚣伐,這是一個保存函數(shù)參數(shù)的非連續(xù)區(qū),不可修改區(qū)萍丐。外部函數(shù)的參數(shù)(不包含返回參數(shù))強制的保存在"calldata"中,和memory差不多轩端。
理解數(shù)據(jù)位置是非常重要的,因為它改變了賦值的行為:storage和內(nèi)存間的賦值逝变,以及賦值給轉(zhuǎn)臺變量(或者從其他變量賦值)總是會生成一個獨立的拷貝基茵。賦值給本地變量只是賦值了一個引用。并且這個引用總是指向狀態(tài)變量壳影,即使這個狀態(tài)變量在程序運行期間發(fā)生了變化拱层。換句話說,在內(nèi)存數(shù)據(jù)之間的賦值不會生成一個數(shù)據(jù)拷貝宴咧。
pragma solidity ^0.4.0;
contract C {
uint[] x; // 該數(shù)據(jù)保存在storage中
// memoryArray的數(shù)據(jù)保存在memory中
function f(uint[] memoryArray) {
x = memoryArray; // 沒毛病根灯,將整個數(shù)組保存到storage中
var y = x; // 沒毛病,給指針賦值,y的數(shù)據(jù)位置為storage
y[7]; // 可以的烙肺,返回第8個元素
y.length = 2; // 可以的芥驳,通過y來改變x
delete x; // 可以的,清空array茬高,并且也改變了y
// 下面的語句是錯誤的兆旬,本該在storage中生成一個暫時的,沒有命名的Array /
// 但是storage已經(jīng)靜態(tài)的被分配了:
// y = memoryArray;
// 以下語句也是錯誤的怎栽,因為它會重置指針丽猬,但是它并沒有實質(zhì)的指向。
// delete y;
g(x); // 調(diào)用g函數(shù)熏瞄,參數(shù)為x的引用
h(x); // 調(diào)用h函數(shù)脚祟,并且在memory中生成一個單獨的,暫時的强饮,x的拷貝
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) {}
}
總結(jié)
強制數(shù)據(jù)位置:
- 外部函數(shù)的參數(shù)(不包括返回值):calldata
- 狀態(tài)變量:storage
默認數(shù)據(jù)位置:
- 函數(shù)的參數(shù)(包括返回值):內(nèi)存
- 所有其他本地變量:storage
數(shù)組
數(shù)組可以在編譯的時候確定其長度由桌,或者它們可以動態(tài)變化。對于storage數(shù)組邮丰,元素類型可以是任意的(例如行您,其他數(shù)組,映射或者結(jié)構(gòu)體)剪廉;對于內(nèi)存數(shù)組娃循,它不能成為映射,而且如果它是公有函數(shù)的參數(shù)斗蒋,那么只能是ABI類型捌斧。
一個具有k
長度的,元素類型為T
的數(shù)組表示為T[k]
泉沾,動態(tài)長度的數(shù)組為T[]
捞蚂。例如,一個包含有五個動態(tài)數(shù)組的數(shù)組跷究,可以表示為uint[][5]
(注意這里和其他的語言的記法相反)姓迅。獲取到第三個數(shù)組中的第二個元素,可以用x[2][1]
(索引從0開始揭朝,這里獲取數(shù)據(jù)和定義是兩種相反的方式队贱,如x[2]
shaves off one level in the type from the right)
bytes
和string
是特殊的數(shù)組色冀。bytes
就相當于是byte[]
潭袱,但是它位于calldata,而且比較緊湊(锋恬?but it is packed tightly in calldata)屯换。string
相當于是bytes
,但是目前為止不能修改長度和字符索引。
所以bytes
和byte[]
相比彤悔,會優(yōu)先選擇byte[]
嘉抓,因為更加便宜。
注意:如果你想要獲取string的byte表達晕窑,可以使用bytes(s).length
/bytes(s)[7] = x;
抑片。你要注意的是,你訪問的是UTF-8的低級字節(jié)表達杨赤,不是單獨的一個字符敞斋。
數(shù)組也可以是public
的,solidity會生成它的getter函數(shù)疾牲。調(diào)用getter函數(shù)必須要有一個索引參數(shù)植捎。
分配內(nèi)存數(shù)組
在內(nèi)存中創(chuàng)建一個可變長度的數(shù)組可以通過new
關(guān)鍵字來實現(xiàn)。為了與storage數(shù)組相抵制阳柔,內(nèi)存數(shù)組不能通過改變.length
來改變長度焰枢。
pragma solidity ^0.4.0;
contract C {
function f(uint len) {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
// 這里,a.length == 7舌剂,b.length == len
a[6] = 8;
}
}
數(shù)組字面量/線性數(shù)組
數(shù)組字面量济锄,是寫作表達式一樣的數(shù)組,它不是馬上被賦值到一個變量中霍转。
pragma solidity ^0.4.0;
contract C {
function f() {
g([uint(1), 2, 3]);
}
function g(uint[3] _data) {
// ...
}
}
數(shù)組字面量的類型是固定長度的內(nèi)存數(shù)組拟淮,元素類型為給出元素的公共類型。[1,2,3]
的類型是uint[8] memory
谴忧,因為所有元素的類型是uint8
很泊。正因為如此,在上面例子中沾谓,第一個元素就很有必要轉(zhuǎn)換為uint
委造。需要注意的是,當前固定長度的內(nèi)存數(shù)組不能賦值給可變長度的內(nèi)存數(shù)組均驶,所以如下的代碼是不可行的:
program solidity ^0.4.0
contract C {
function f() {
//
uint[] x = [uint(1),3,4];
}
}
未來會把這個限制移除昏兆,但是現(xiàn)在有些難題尚未解決,比如如何在ABI中傳遞數(shù)組妇穴。
成員變量
length:
數(shù)組有一個length
成員變量來指示數(shù)組的長度爬虱。動態(tài)長度數(shù)組保存在storage中(不是在內(nèi)存中),能夠動態(tài)變化長度腾它。程序不會自動的訪問長度以外的元素跑筝。內(nèi)存數(shù)組一旦創(chuàng)建,它的長度是固定的(但是是動態(tài)的瞒滴,例如可以在運行的時候決定長度)
push:
動態(tài)storage數(shù)組和bytes
(不是string
)有一個成員函數(shù)曲梗,稱為push
赞警,可以用來在數(shù)組末尾添加元素。該函數(shù)返回新的長度虏两。
警告:在外部函數(shù)中還不能使用二維數(shù)組
**警告:由于EVM的限制愧旦,不能在外部函數(shù)調(diào)用中返回動態(tài)內(nèi)容。在contract C { function f() returns (uint[]) { ... } }
中的函數(shù)f
定罢,如果在web3中調(diào)用笤虫,將會返回數(shù)據(jù),但是在solidity中調(diào)用祖凫,不會返回數(shù)據(jù)耕皮。
現(xiàn)在一個變通的方法是返回一個靜態(tài)的足夠大的數(shù)組。
**
pragma solidity ^0.4.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// 注意這里定義的不是兩個動態(tài)數(shù)組蝙场,而是一個動態(tài)數(shù)組凌停,其元素為長度為2的數(shù)組。
bool[2][] m_pairsOfFlags;
// newPairs會保存在memory中 - 函數(shù)參數(shù)的默認類型售滤。
function setAllFlagPairs(bool[2][] newPairs) {
// 賦值給storage數(shù)組罚拟,替換整個數(shù)組。
m_pairsOfFlags = newPairs;
}
function setFlagPair(uint index, bool flagA, bool flagB) {
// 越界訪問將會產(chǎn)生異常
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) {
// 如果數(shù)組的長度變小了完箩,那么多余的元素將會被刪除
m_pairsOfFlags.length = newSize;
}
function clear() {
// 清空數(shù)組
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// 清空數(shù)組
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes data) {
// byte 數(shù)組 ("bytes") 是不同的赐俗,因為它們保存時沒有保留空余的空間
// 但是以"uint8[]"的類型來對待
// ?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] = 8;
delete m_byteData[2];
}
function addFlag(bool[2] flag) returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) returns (bytes) {
// 動態(tài)的內(nèi)存數(shù)組通過new關(guān)鍵字生成。
uint[2][] memory arrayOfPairs = new uint[2][](size);
// 生成一個動態(tài)byte數(shù)組
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(i);
return b;
}
}
結(jié)構(gòu)體
Solidity提供了定義類型的方法:通過結(jié)構(gòu)體弊知。例子如下所示:
pragma solidity ^0.4.11;
contract CrowdFunding {
// 定義新的類型阻逮,包含兩個數(shù)據(jù)字段
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID 是返回值變量
// 生成一個新的結(jié)構(gòu)體數(shù)據(jù),并保存在storage中秩彤。 We leave out the mapping type.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) payable {
Campaign c = campaigns[campaignID];
// 生成一個新的內(nèi)存數(shù)據(jù)叔扼。通過給定的值進行初始化,并拷貝到storage中漫雷。
// 你也可以通過 Funder(msg.sender, msg.value) 進行初始化.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) returns (bool reached) {
Campaign c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
合約并不提供眾籌合約的所有函數(shù)瓜富,但是它包含了理解結(jié)構(gòu)體的基本概念。結(jié)構(gòu)體類型可以用在數(shù)組和映射中降盹,并且它本身可以有映射和數(shù)組与柑。
結(jié)構(gòu)體類型包含本身的類型的字段是不可以的,盡管這個結(jié)構(gòu)體是映射的值類型(蓄坏?although the struct itself can be the value type of a mapping member.)价捧。這個限制是很有必要的藐吮,因為結(jié)構(gòu)體的大小應該有限遗增。
要注意在所有函數(shù)中,結(jié)構(gòu)體類型是怎樣賦值給本地變量的(默認是storage數(shù)據(jù)位置)涕俗。它并沒有復制結(jié)構(gòu)體妹蔽,而是保存了他的引用椎眯,所以通過本地變量,直接給所引用的結(jié)構(gòu)體成員賦值也會改變數(shù)據(jù)胳岂。
當然编整,你也可以直接訪問結(jié)構(gòu)體的字段,不需要將它賦值給一個本地變量乳丰,例如campaigns[campaignID].amount = 0
.
映射
映射類型通過這樣的形式定義:mapping(_KeyType => _ValueType)
掌测。這里_KeyType
可以是除了映射,動態(tài)長度的數(shù)組产园,合約汞斧,枚舉和結(jié)構(gòu)體之外的任何類型。_ValueType
可以是所以的類型什燕,包括映射粘勒。
映射可以看成是一個hash表,這個表的所有可能的鍵初始化為byte表示全為0的值:這個是默認值.相似的:鍵數(shù)據(jù)不是存儲在映射里屎即,而是鍵的keccak256
hash庙睡,用來查找對應的值。
所以映射沒有長度或者鍵值的概念技俐,也不是集合乘陪。(?Because of this, mappings do not have a length or a concept of a key or value being “set”.)
映射只允許狀態(tài)變量(或者在內(nèi)部函數(shù)中的storage引用類型)雕擂。
可以設置映射為public
啡邑,solidity會自動生成getter。_KeyType
是getter函數(shù)必須的參數(shù)井赌,返回值為_ValueType
谤逼。
_ValueType
值同樣可以是映射,會遞歸產(chǎn)生getter函數(shù)仇穗。(森缠?The _ValueType can be a mapping too. The getter will have one parameter for each _KeyType, recursively.)
pragma solidity ^0.4.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() returns (uint) {
return MappingExample(<address>).balances(this);
}
}
注意:映射不可以遍歷,但是可以在此基礎(chǔ)上生成新的結(jié)構(gòu)仪缸。例如贵涵,可遍歷映射
使用LValues的操作符
如果a
是一個LValue(例如,一個變量或者是可以被賦值something)恰画,那么可以使用以下的操作符宾茂。
a += e
是和a = a + e
一樣的。操作符-=
拴还,*=
跨晴,/=
,%=
片林,|=
端盆,&=
和^=
也是對應的怀骤。a++
和a--
分別對應于a += 1
或a -= 1
。但是表達式本身還是保持a
的原始值焕妙。相反的蒋伦,--a
和++a
同樣是對a加上1,但是返回了改變后的值焚鹊。
刪除
delete a
將a類型的初始值賦給a痕届。例如,對于整形末患,這個相當于是a = 0
研叫。它也可以用于數(shù)組,將一個長度為0的動態(tài)長度數(shù)組賦值給變量璧针,或者是一個與之具有相通長度的靜態(tài)數(shù)組嚷炉,但是所有元素都被初始化的數(shù)組。對于結(jié)構(gòu)體探橱,將初始化所有的字段渤昌。
delete
對整個映射是無效的(因為key可以是任何值,而且通常我們都不知道)走搁。所以独柑,如果是delete結(jié)構(gòu)體,它將會重置所有非映射類型的字段私植,并且將遞歸字段忌栅,直到遇上映射。
delete a
其實是對a重新賦值曲稼,例如重新賦值為一個對象索绪。
pragma solidity ^0.4.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() {
uint x = data;
delete x; // 將x設置為0,并不影響其他數(shù)據(jù)
delete data; //將data設置,不會影響到x贫悄,因為x保存的是值的拷貝
uint[] y = dataArray;
delete dataArray; // 將dataArray.length設置為0, 但是因為uint[]是一個復雜的對象瑞驱,所以y的值也被影響,因為y是storage對象的引用窄坦。
// 另一方面: "delete y"是無效的唤反,因為引用一個storage對象必須要有一個storage對象。
}
}
初級類型的轉(zhuǎn)換
隱式的轉(zhuǎn)換
如果操作符被用于不同的類型時鸭津,編譯器會嘗試對一個操作數(shù)進行隱式的轉(zhuǎn)換成另一個操作數(shù)的類型(賦值的時候也是一樣的)。一般情況下盏阶,如果語意上有意義,并且沒有信息損失闷袒,值類型之間的隱式轉(zhuǎn)換是可行的霜运。uint8
可以轉(zhuǎn)換為uint16
蒋腮,int128
可以轉(zhuǎn)換為int256
,但是int8
不能轉(zhuǎn)換為uint256
(因為uint256
不能存儲負數(shù))。另外,無符號整形可以轉(zhuǎn)換為同樣大小的或者更大的bytes竭讳,但是反過來不行洛波。任何可以轉(zhuǎn)換為uint160
的類型可以轉(zhuǎn)換為address
.
顯式的轉(zhuǎn)換
如果編譯器不允許隱式轉(zhuǎn)換胰舆,但是你知道你要怎么做扽時候倦零,可以用顯式轉(zhuǎn)換诞帐。需要注意的是愕鼓,顯式轉(zhuǎn)換可能會導致有不期望的結(jié)果磺送,所以要充分測試估灿,并且保證結(jié)果是你期望的抵窒。下面的例子說明了從負數(shù)的int8
轉(zhuǎn)換為uint
類型:
int8 y = -3;
uint x = uint(y);
這個代碼端最后疙赠,x
的值變?yōu)?code>0xfffff..fd(64個十六進制字符),值為-3付材。
如果將類型顯式轉(zhuǎn)換為更小的類型,那么高位會被裁切掉:
uint32 a = 0x12345678;
uint16 b = uint16(a); // 現(xiàn)在b的值為0x5678
類型推斷
簡便起見圃阳,并不是總是需要為變量顯式的指定類型厌衔,編譯器會自動的對第一個表達式的類型進行引用,并賦值:
uint24 x = 0x123;
var y = x;
這里捍岳,y
的類型將會是uint24
富寿。函數(shù)參數(shù)或者返回值不能用var
.
警告:類型只能從第一個表達式推斷,所以下面的例子是個死循環(huán)锣夹。因為i
的類型為uint8
页徐,該類型的所有值都是小于2000
的。for (var i = 0; i < 2000; i++) { ... }