Solidity語法(六)表達式與控制結(jié)構(gòu)

入?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 opcode1泌参。

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)

運算符的執(zhí)行順序

賦值(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)生運行時異常有巧。

  1. 如果越界,或是負的序號值訪問數(shù)組悲没。
  2. 如果訪問一個定長的bytesN篮迎,序號越界,或是負的序號值示姿。
  3. 如果你通過消息調(diào)用一個函數(shù)柑潦,但在調(diào)用的過程中,并沒有正確結(jié)束(gas不足峻凫,沒有匹配到對應的函數(shù)渗鬼,或他自己出現(xiàn)異常)。底層操作如call,send,delegatecall或callcode除外荧琼,它們不會拋出異常譬胎,但它們會通過返回false來表示失敗。
  4. 如果在使用new創(chuàng)建一個新合約時命锄,但合約的初化化由于類似3中的原因沒有正常完成堰乔。
  5. 被除數(shù)為0。
  6. 對一個二進制移動一個負的值脐恩。
  7. 使用枚舉時镐侯,將過大值,負值轉(zhuǎn)為枚舉類型驶冒。
  8. 使用外部函數(shù)調(diào)用時苟翻,被調(diào)用的對象并不包含代碼。
  9. 如果你的public的函數(shù)在沒有payable關鍵字時骗污,卻嘗試在接收ether(包括構(gòu)造函數(shù)崇猫,和回退函數(shù))。
  10. 合約通過一個public的getter函數(shù)(public getter funciton)接收ether需忿。
  11. 調(diào)用一個未初始化的內(nèi)部函數(shù)诅炉。
  12. transfer()執(zhí)行失敗。
  13. assert返回false屋厘。

當一個用戶通過下述方式觸發(fā)一個異常:

  1. 調(diào)用throw涕烧。
  2. 調(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)胸嘴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斩祭,隨后出現(xiàn)的幾起案子劣像,更是在濱河造成了極大的恐慌,老刑警劉巖摧玫,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耳奕,死亡現(xiàn)場離奇詭異,居然都是意外死亡诬像,警方通過查閱死者的電腦和手機屋群,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颅停,“玉大人谓晌,你說我怎么就攤上這事●啵” “怎么了?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵溺欧,是天一觀的道長喊熟。 經(jīng)常有香客問我,道長姐刁,這世上最難降的妖魔是什么芥牌? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮聂使,結(jié)果婚禮上壁拉,老公的妹妹穿的比我還像新娘谬俄。我一直安慰自己,他們只是感情好弃理,可當我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布溃论。 她就那樣靜靜地躺著,像睡著了一般痘昌。 火紅的嫁衣襯著肌膚如雪钥勋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天辆苔,我揣著相機與錄音算灸,去河邊找鬼。 笑死驻啤,一個胖子當著我的面吹牛菲驴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骑冗,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼赊瞬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沐旨?” 一聲冷哼從身側(cè)響起森逮,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磁携,沒想到半個月后褒侧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡谊迄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年闷供,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片统诺。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡歪脏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粮呢,到底是詐尸還是另有隱情婿失,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布啄寡,位于F島的核電站豪硅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挺物。R本人自食惡果不足惜懒浮,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望识藤。 院中可真熱鬧砚著,春花似錦次伶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至秧骑,卻和暖如春版确,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乎折。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工绒疗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骂澄。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓吓蘑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坟冲。 傳聞我的和親對象是個殘疾皇子磨镶,可洞房花燭夜當晚...
    茶點故事閱讀 43,500評論 2 348

推薦閱讀更多精彩內(nèi)容