入?yún)⒑统鰠?Input Parameters and Output Parameters)
同javascript一樣庞瘸,函數(shù)有輸入?yún)?shù)续扔,但與之不同的是,函數(shù)可能有任意數(shù)量的返回參數(shù)。
入?yún)?Input Parameters)
入?yún)?Input Parameter)與變量的定義方式一致冰沙,稍微不同的是垂券,不會用到的參數(shù)可以省略變量名稱花盐。一種可接受兩個整型參數(shù)的函數(shù)如下:
pragma solidity ^0.4.0;
contract Simple {
function taker(uint _a, uint) {
// do something with _a.
}
}
出參(Output Parameters)
出參(Output Paramets)在returns關鍵字后定義,語法類似變量的定義方式菇爪。下面的例子展示的是算芯,返回兩個輸入?yún)?shù)的求和,乘積的實現(xiàn):
pragma solidity ^0.4.0;
contract Simple {
//return sum and product
function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
o_sum = _a + _b;
o_product = _a * _b;
}
}
出參的的名字可以省略凳宙。返回的值熙揍,同樣可以通過return
關鍵字來指定。return
也可以同時返回多個值氏涩。出參的默認值為0届囚,如果沒有明確被修改,它將一直是0是尖。
入?yún)⒑统鰠⒁部稍诤瘮?shù)體內(nèi)用做表達式意系。它們也可被賦值。
返回多個值(Returning Multiple Values)
當返回多個參數(shù)時析砸,使用return (v0, v1, ..., vn)
昔字。返回結(jié)果的數(shù)量需要與定義的一致。
控制結(jié)構(gòu)
不支持switch和goto,支持if作郭,else陨囊,while,do夹攒,for蜘醋,break,continue咏尝,return压语,?:。
條件判斷中的括號不可省略编检,但在單行語句中的大括號可以省略胎食。
需要注意的是,這里沒有像C語言允懂,和javascript里的非Boolean類型的自動轉(zhuǎn)換厕怜,比如if(1){...}在Solidity中是無效的。
函數(shù)調(diào)用(Function Calls)
內(nèi)部函數(shù)調(diào)用(Internal Function Calls)
在當前的合約中蕾总,函數(shù)可以直接調(diào)用(內(nèi)部調(diào)用方式)粥航,包括也可遞歸調(diào)用,來看一個簡單的示例:
contract C {
function g(uint a) returns (uint ret) { return f(); }
function f() returns (uint ret) { return g(7) + f(); }
}
這些函數(shù)調(diào)用在EVM中被翻譯成簡單的跳轉(zhuǎn)指令生百。這樣帶來的一個好處是递雀,當前的內(nèi)存不會被回收。所以在一個內(nèi)部調(diào)用時傳遞一個內(nèi)存型引用效率將非常高蚀浆。當然缀程,僅僅是同一個合約的函數(shù)之間才可通過內(nèi)部的方式進行調(diào)用。
外部函數(shù)調(diào)用(External Function Calls)
表達式this.g(8);
和c.g(2)
(這里的c是一個合約實例)是外部調(diào)用函數(shù)的方式蜡坊。實現(xiàn)上是通過一個消息調(diào)用杠输,而不是直接通過EVM的指令跳轉(zhuǎn)。需要注意的是秕衙,在合約的構(gòu)造器中蠢甲,不能使用this調(diào)用函數(shù),因為當前合約還沒有創(chuàng)建完成据忘。
其它合約的函數(shù)必須通過外部的方式調(diào)用鹦牛。對于一個外部調(diào)用,所有函數(shù)的參數(shù)必須要拷貝到內(nèi)存中勇吊。
當調(diào)用其它合約的函數(shù)時曼追,可以通過選項.value(),和.gas()來分別指定汉规,要發(fā)送的ether量(以wei為單位)礼殊,和gas值驹吮。
pragma solidity ^0.4.0;
contract InfoFeed {
function info() payable returns (uint ret) {
return msg.value;
}
}
contract Consumer {
function deposit() payable returns (uint){
return msg.value;
}
function left() constant returns (uint){
return this.balance;
}
function callFeed(address addr) returns (uint) {
return InfoFeed(addr).info.value(1).gas(8000)();
}
}
上面的代碼中,我們首先調(diào)用deposit()
為Consumer
合約存入一定量的ether
晶伦。然后調(diào)用callFeed()
通過value(1)
的方式碟狞,向InfoFeed
合約的info()
函數(shù)發(fā)送1ether
。需要注意的是婚陪,如果不先充值族沃,由于合約余額為0,余額不足會報錯Invalid opcode
1泌参。
InfoFeed.info()
函數(shù)脆淹,必須使用payable
關鍵字,否則不能通過value()
選項來接收ether
沽一。
代碼InfoFeed(addr)
進行了一個顯示的類型轉(zhuǎn)換盖溺,聲明了我們確定知道給定的地址是InfoFeed
類型。所以這里并不會執(zhí)行構(gòu)造器的初始化锯玛。顯示的類型強制轉(zhuǎn)換咐柜,需要極度小心兼蜈,不要嘗試調(diào)用一個你不知道類型的合約攘残。
我們也可以使用function setFeed(InfoFeed _feed) { feed = _feed; }
來直接進行賦值。.info.value(1).gas(8000)
只是本地設置發(fā)送的數(shù)額和gas值为狸,真正執(zhí)行調(diào)用的是其后的括號.info.value(1).gas(8000)()
歼郭。
如果被調(diào)用的合約不存在,或者是不包代碼的帳戶辐棒,或調(diào)用的合約產(chǎn)生了異常病曾,或者gas不足,均會造成函數(shù)調(diào)用發(fā)生異常漾根。
如果被調(diào)用的合約源碼并不事前知道泰涂,和它們交互會有潛在的風險。當前合約會將自己的控制權(quán)交給被調(diào)用的合約辐怕,而對方幾乎可以做任何事逼蒙。即使被調(diào)用的合約是繼承自一個已知的父合約,但繼承的子合約僅僅被要求正確實現(xiàn)了接口寄疏。合約的實現(xiàn)是牢,可以是任意的內(nèi)容,由此會有風險陕截。另外驳棱,準備好處理調(diào)用你自己系統(tǒng)中的其它合約,可能在第一調(diào)用結(jié)果未返回之前就返回了調(diào)用的合約农曲。某種程度上意味著社搅,被調(diào)用的合約可以改變調(diào)用合約的狀態(tài)變量(state variable)
來標記當前的狀態(tài)。如,寫一個函數(shù)形葬,只有當狀態(tài)變量(state variables)
的值有對應的改變時却汉,才調(diào)用外部函數(shù),這樣你的合約就不會有可重入性漏洞荷并。
命名參數(shù)調(diào)用和匿名函數(shù)參數(shù)(Named Calls and Anonymous Function Paramters)
函數(shù)調(diào)用的參數(shù)合砂,可以通過指定名字的方式調(diào)用,但可以以任意的順序源织,使用方式是{}包含翩伪。但參數(shù)的類型和數(shù)量要與定義一致。
pragma solidity ^0.4.0;
contract C {
function add(uint val1, uint val2) returns (uint) { return val1 + val2; }
function g() returns (uint){
// named arguments
return add({val2: 2, val1: 1});
}
}
省略函數(shù)名稱(Omitted Function Parameter Names)
沒有使用的參數(shù)名可以省略(一般常見于返回值)谈息。這些名字在棧(stack)上存在缘屹,但不可訪問。
pragma solidity ^0.4.0;
contract C {
// omitted name for parameter
function func(uint k, uint) returns(uint) {
return k;
}
}
創(chuàng)建合約實例(Creating Contracts)
一個合約可以通過new關鍵字來創(chuàng)建一個合約侠仇。
pragma solidity ^0.4.0;
contract Account{
uint accId;
//construction?
function Account(uint accountId) payable{
accId = accountId;
}
}
contract Initialize{
Account account = new Account(10);
function newAccount(uint accountId){
account = new Account(accountId);
}
function newAccountWithEther(uint accountId, uint amount){
account = (new Account).value(amount)(accountId);
}
}
從上面的例子可以看出來轻姿,可以在創(chuàng)建合約中,發(fā)送ether逻炊,但不能限制gas的使用互亮。如果創(chuàng)建因為out-of-stack,或無足夠的余額以及其它任何問題余素,會拋出一個異常豹休。
表達式的執(zhí)行順序(Order of Evaluation of Expressions)
賦值(Assignment)
解構(gòu)賦值和返回多個結(jié)果(Destructing Assignments and Returning Multip Values)
Solidity內(nèi)置支持元組(tuple),也就是說支持一個可能的完全不同類型組成的一個列表桨吊,數(shù)量上是固定的(Tuple一般指兩個威根,還有個Triple一般指三個)。
這種內(nèi)置結(jié)構(gòu)可以同時返回多個結(jié)果视乐,也可用于同時賦值給多個變量洛搀。
pragma solidity ^0.4.0;
contract C {
uint[] data;
function f() returns (uint, bool, uint) {
return (7, true, 2);
}
function g() {
// Declares and assigns the variables. Specifying the type explicitly is not possible.
var (x, b, y) = f();
// Assigns to a pre-existing variable.
(x, y) = (2, 7);
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
// If the tuple ends in an empty component,
// the rest of the values are discarded.
(data.length,) = f(); // Sets the length to 7
// The same can be done on the left side.
(,data[3]) = f(); // Sets data[3] to 2
// Components can only be left out at the left-hand-side of assignments, with
// one exception:
(x,) = (1,);
// (1,) is the only way to specify a 1-component tuple, because (1) is
// equivalent to 1.
}
}
數(shù)組和自定義結(jié)構(gòu)體的復雜性(Complication for Arrays and Struts)
對于非值類型,比如數(shù)組和數(shù)組佑淀,賦值的語法有一些復雜留美。
- 賦值給一個狀態(tài)變量總是創(chuàng)建一個完全無關的拷貝。
- 賦值給一個局部變量渣聚,僅對基本類型独榴,如那些32字節(jié)以內(nèi)的靜態(tài)類型(static types),創(chuàng)建一份完全無關拷貝奕枝。
- 如果是數(shù)據(jù)結(jié)構(gòu)或者數(shù)組(包括bytes和string)類型棺榔,由狀態(tài)變量賦值為一個局部變量,局部變量只是持有原始狀態(tài)變量的一個引用隘道。對這個局部變量再次賦值症歇,并不會修改這個狀態(tài)變量郎笆,只是修改了引用。但修改這個本地引用變量的成員值忘晤,會改變狀態(tài)變量的值宛蚓。
作用范圍和聲明(Scoping And Decarations)
一個變量在聲明后都有初始值為字節(jié)表示的全0值。也就是所有類型的默認值是典型的零態(tài)(zero-state)设塔。舉例來說凄吏,默認的bool的值為false,uint和int的默認值為0。
對從byte1到byte32定長的字節(jié)數(shù)組闰蛔,每個元素都被初始化為對應類型的初始值(一個字節(jié)的是一個字節(jié)長的全0值痕钢,多個字節(jié)長的是多個字節(jié)長的全零值)。對于變長的數(shù)組bytes和string序六,默認值則為空數(shù)組和空字符串任连。
函數(shù)內(nèi)定義的變量,在整個函數(shù)中均可用例诀,無論它在哪里定義)随抠。因為Solidity使用了javascript的變量作用范圍的規(guī)則。與常規(guī)語言語法從定義處開始繁涂,到當前塊結(jié)束為止不同拱她。由此,下述代碼編譯時會拋出一個異常爆土,Identifier already declared椭懊。
pragma solidity ^0.4.0;
contract ScopingErrors {
function scoping() {
uint i = 0;
while (i++ < 1) {
uint same1 = 0;
}
while (i++ < 2) {
uint same1 = 0;// Illegal, second declaration of same1
}
}
function minimalScoping() {
{
uint same2 = 0;
}
{
uint same2 = 0;// Illegal, second declaration of same2
}
}
function forLoopScoping() {
for (uint same3 = 0; same3 < 1; same3++) {
}
for (uint same3 = 0; same3 < 1; same3++) {// Illegal, second declaration of same3
}
}
function crossFunction(){
uint same1 = 0;//Illegal
}
}
另外的,如果一個變量被聲明了步势,它會在函數(shù)開始前被初始化為默認值。所以下述例子是合法的背犯。
pragma solidity ^0.4.0;
contract C{
function foo() returns (uint) {
// baz is implicitly initialized as 0
uint bar = 5;
if (true) {
bar += baz;
} else {
uint baz = 10;// never executes
}
return bar;// returns 5
}
}
異常(Excepions)
有一些情況下坏瘩,異常是自動拋出來的(見下),你也可以使用throw來手動拋出一個異常漠魏。拋出異常的效果是當前的執(zhí)行被終止且被撤銷(值的改變和帳戶余額的變化都會被回退)倔矾。異常還會通過Solidity的函數(shù)調(diào)用向上冒泡(bubbled up)傳遞。(send柱锹,和底層的函數(shù)調(diào)用call,delegatecall,callcode是一個例外,當發(fā)生異常時担汤,這些函數(shù)返回false)媳搪。
捕捉異常是不可能的(或許因為異常時,需要強制回退的機制)瞧毙。
在下面的例子中胧华,我們將如展示如何使用throw來回退轉(zhuǎn)帳寄症,以及演示如何檢查send的返回值。
pragma solidity ^0.4.0;
contract Sharer {
function sendHalf(address addr) payable returns (uint balance) {
if (!addr.send(msg.value / 2))
throw; // also reverts the transfer to Sharer
return this.balance;
}
}
當前矩动,Solidity在下述場景中自動產(chǎn)生運行時異常有巧。
- 如果越界,或是負的序號值訪問數(shù)組悲没。
- 如果訪問一個定長的bytesN篮迎,序號越界,或是負的序號值示姿。
- 如果你通過消息調(diào)用一個函數(shù)柑潦,但在調(diào)用的過程中,并沒有正確結(jié)束(gas不足峻凫,沒有匹配到對應的函數(shù)渗鬼,或他自己出現(xiàn)異常)。底層操作如call,send,delegatecall或callcode除外荧琼,它們不會拋出異常譬胎,但它們會通過返回false來表示失敗。
- 如果在使用new創(chuàng)建一個新合約時命锄,但合約的初化化由于類似3中的原因沒有正常完成堰乔。
- 被除數(shù)為0。
- 對一個二進制移動一個負的值脐恩。
- 使用枚舉時镐侯,將過大值,負值轉(zhuǎn)為枚舉類型驶冒。
- 使用外部函數(shù)調(diào)用時苟翻,被調(diào)用的對象并不包含代碼。
- 如果你的public的函數(shù)在沒有payable關鍵字時骗污,卻嘗試在接收ether(包括構(gòu)造函數(shù)崇猫,和回退函數(shù))。
- 合約通過一個public的getter函數(shù)(public getter funciton)接收ether需忿。
- 調(diào)用一個未初始化的內(nèi)部函數(shù)诅炉。
- transfer()執(zhí)行失敗。
- assert返回false屋厘。
當一個用戶通過下述方式觸發(fā)一個異常:
- 調(diào)用throw涕烧。
- 調(diào)用require,但參數(shù)值為false汗洒。
當上述情況發(fā)生時议纯,在Solidity會執(zhí)行一個回退操作(指令0xfd)。與之相對的是仲翎,如果發(fā)生運行時異常痹扇,或assert失敗時铛漓,將執(zhí)行無效操作(指令0xfe)。在上述的情況下鲫构,由此促使EVM撤回所有的狀態(tài)改變浓恶。這樣做的原因是,沒有辦法繼續(xù)安全執(zhí)行了结笨,因為想要發(fā)生的事件并未發(fā)生包晰。因為我們想保持交易的原子性(一致性),所以撤銷所有操作炕吸,讓整個交易沒有任何影響伐憾。
通過assert判斷內(nèi)部條件是否達成,require驗證輸入的有效性赫模。這樣的分析工具树肃,可以假設正確的輸入,減少錯誤瀑罗。這樣無效的操作碼將永遠不會出現(xiàn)胸嘴。