<div id="ng5vq"><tr id="ng5vq"></tr></div>

  • <div id="ng5vq"></div>

      <em id="ng5vq"></em>
      <div id="ng5vq"></div>

          投資有風險 入市需謹慎
          APP
          下載火星財經客戶端

          掃描下載APP

          微信公眾號
          火星財經二維碼 火星財經

          智能合約基礎語言:Solidity函數 | 四

          鏈塊學院 ·

          09月23日

          熱度: 23050

          每一個合約有且僅有一個沒有名字的函數。

          智能合約基礎語言:Solidity函數 | 四

          一、目錄

          ??函數的定義

          ??函數的調用方式

          ??函數的可見性

          ??函數修改器

          ??pure函數

          ??constant、view函數

          ??payable函數

          ??回退函數

          ??構造函數

          ??函數參數

          ??抽象函數

          ??數學和加密函數

          二、函數的定義

          function關鍵字聲明的,合約中的可執行單元,一個函數的完整定義如下:

          function (funcName) () {public|external|internal|private} [constant|view|payable] [returns (<return types>)]

          三、函數的調用方式

          Solidity封裝了兩種函數的調用方式internal(內部調用)和external(外部調用)。

          3.1 internal(內部調用方式)

          internal調用,實現時轉為簡單的EVM跳轉,所以它能直接使用上下文環境中的數據,對于引用傳遞時將會變得非常高效(不用拷貝數據)。

          在當前的代碼單元內,如對合約內函數,引入的庫函數,以及父類合約中的函數直接使用即是以internal方式的調用。我們來看個簡單的例子:

          pragma solidity ^0.4.24; contract Test { ? ?
          ? ?function
          f(){} ? ? ? ?
          ? ?//以internal的方式調用 ? ?function
          callInternally(){ ? ? ? ?f(); ? ?} }

          在上述代碼中,callInternally()以internal的方式對f()函數進行了調用。
          簡而言之,internal(內部調用方式)就是直接使用函數名去調用函數。

          3.2 external(外部調用方式)

          external調用,實現為合約的外部消息調用。所以在合約初始化時不能external的方式調用自身函數,因為合約還未初始化完成。下面來看一個以external方式調用的例子:

          pragma solidity ^0.4.24; contract A{ ? ?
          ? ?function
          f(){} } contract B{ ? ?
          ? ?//以external的方式調用另一合約中的函數 ? ?function
          callExternal(A a){ ? ? ? ?a.f(); ? ?} }

          雖然當前合約A和B的代碼放在一起,但部署到網絡上后,它們是兩個完全獨立的合約,它們之間的方法調用是通過消息調用。上述代碼中,在合約B中的callExternal()以external的方式調用了合約A的f()。
          簡而言之,external(外部調用方式)就是使用合約實例名.函數名的方式去調用函數。

          3.3 this

          我們可以在合約的調用函數前加this.來強制以external方式的調用。

          pragma solidity ^0.4.24;
          
          contract A{ ? 
          ? ?function
          f() external{} ? ? ?
          ? ?function
          callExternally(){ ?
          ? ? ? ?this.f(); ? ?} }

          3.4 調用方式說明

          上面所提到的internal和external指的函數調用方式,請不要與后面的函數可見性聲明的external,public,internal,private弄混。聲明只是意味著這個函數需要使用相對應的調用方式去調用。

          四、函數的可見性

          Solidity為函數提供了四種可見性,external,public,internal,private。

          4.1 external(外部函數)

          ??聲明為external的函數可以從其它合約來進行調用,所以聲明為external的函數是合約對外接口的一部分。

          ??不能以internal的方式進行調用。

          ??有時在接收大的數據數組時性能更好。

          pragma solidity ^0.4.24;
          
          contract FuntionTest{ ? ?
          ? ? function
          externalFunc() external{} ? ?

          ? ? function
          callFunc(){ ? ? ? ?
          ? ? ? ?//`internal`的方式調用函數報錯 ? ? ? ?//Error: Undeclared identifier. ? ? ? ?//externalFunc(); ? ? ? ?//`external`的方式調用函數 ? ? ? ?this.externalFunc(); ? ?} }

          聲明為external的externalFunc()只能以external的方式進行調用,以internal的方式調用會報Error: Undeclared identifier。

          4.2 pulic(公有函數)

          ??函數默認聲明為public。

          ??public的函數既允許以internal的方式調用,也允許以external的方式調用。

          ??public的函數由于允許被外部合約訪問,是合約對外接口的一部分。

          pragma solidity ^0.4.24;
          
          contract FuntionTest{ ? ?
          ? ?//默認是public函數 ? ?function
          publicFunc(){} ? ?

          ? ?function
          callFunc(){ ? ? ? ?
          ? ? ? ?//以`internal`的方式調用函數 ? ? ? ?publicFunc();
          ? ? ? ? ? ? ? ?//以`external`的方式調用函數 ? ? ? ?this.publicFunc(); ? ?} }

          我們可以看到聲明為public的publicFunc()允許兩種調用方式。

          4.3 internal(內部函數)

          在當前的合約或繼承的合約中,只允許以internal的方式調用。


          pragma solidity ^0.4.24;
          
          contract A{ ? ?

          ? ?function
          internalFunc() internal{} ? ?

          ? ?function
          callFunc(){ ? ? ? ?
          ? ? ? ?//以`internal`的方式調用函數 ? ? ? ?internalFunc(); ? ?} } contract B is A{ ? ?
          ? ?//子合約中調用 ? ?function
          callFunc(){ ? ? ? ?internalFunc(); ? ?} }

          上述例子中聲明為internal的internalFunc()在定義合約,和子合約中均只能以internal的方式可以進行調用。

          4.4 private(私有函數)

          ??只能在當前合約中被訪問(不可在被繼承的合約中訪問)。

          ??即使聲明為private,仍能被所有人查看到里面的數據,但是不能修改數據且不能被其它合約訪問。

          pragma solidity ^0.4.24;
          
          contract A{ ? ?
          ?function privateFunc() private{} ? ?
          ?function callFunc(){ ? ? ? ?
          ? ? ? //`internal`的方式調用函數 ? ? ? privateFunc(); ? ?} } contract B is A{ ? ?
          ? ?//不可調用`private` ? ?function callFunc(){ ? ? ? ?
          ? ? ? ?//privateFunc(); ? ? ? ?//這里無法調用合約A中的內部函數, ? ? ? ?//且在編譯階段就會報錯 ? ?} ? ?
          ? ?//但是間接調用private函數,但是需要這個private函數處在public ? ?//function callPrivateByPublicFunc(){ ? ?// ? ?callFunc(); ? ?//}
          }

          五、pure(純函數)

          既不從狀態讀取數據也不寫入數據的函數可以被聲明為純函數 除了之前修改狀態數據的情況外,我們認為一下情況屬于從狀態讀取數據。

          1. 讀取狀態變量

          2. 調用this.balance或者address.balance

          3. 調用block、tx、msg的成員

          4. 調用任何非純函數

          5. 使用了包含某些操作碼的內聯匯編

          pragma solidity ^0.4.24;
          
          contract C { ? ?
          function f(uint a, uint b) public pure returns (uint) { ? ? ? ?
          ? ?return a * (b + 42); ? ?} }

          六、constant/view(只讀函數)

          不改變狀態的函數可以被聲明為只讀函數一下幾種情況被視為修改了狀態:

          1. 修改狀態變量

          2. 觸發事件

          3. 創建了其他合約的實例

          4. 使用了selfdestruct自我銷毀

          5. 調用了向合約轉賬的函數

          6. 調用了非只讀函數或者純函數

          7. 使用了底層調用

          8. 使用了包含某些操作碼的內聯匯編

          注意:

          constant是view的一個別名,會在0.5.0版本中遺棄,訪問器(getter)方法默認被標記為view調用只讀函數。

          七、函數修改器

          在實際情況中,我們經常需要對調用者進行一些限制。比如,只能是合約的所有者才能改變歸屬。我們一起來看看如何用函數修改器實現這一限制:

          pragma solidity ^0.4.24;
          
          contract Ownable {
           ?address public owner = msg.sender; ?

          ?/// 限制只有創建者才能訪問
          ?modifier onlyOwner { ? ?
          ? ?if (msg.sender != owner) throw; ? ?
          ? ?_; ?} ?

          ?/// 改變合約的所有者 ?function
          changeOwner(address _newOwner) ?onlyOwner ?{ ? ?
          ? ?if(_newOwner == 0x0) throw; ? ?owner = _newOwner; ?} }

          7.1 函數修改器支持參數

          pragma solidity ^0.4.24;
          
          contract Parameter{ ?
          ?uint balance = 10; ?modifier lowerLimit(uint _balance, uint _withdraw){ ? ?
          ? ?if( _withdraw < 0 || _withdraw > _balance) throw; ? ?_; ?} ?

          ?//含參數的函數修改器 ?function
          f(uint withdraw) lowerLimit(balance, withdraw) returns (uint){ ? ?
          ? ?return balance; ?} }

          在上面的例子中,f()函數,有一個函數修改器lowerLimit(),傳入了狀態變量參數balance,和入參withdraw,以lowerLimit(balance, withdraw)的方式進行調用。最后函數能否正確執行取決于輸入的withdraw值大小。

          7.2 函數修改器參數支持表達式

          pragma solidity ^0.4.24;
          
          contract ParameterExpression{
           ?modifier m(uint a){ ? ?
          ? ?if(a > 0) ? ? ?_; ?} ?

          ?function
          add(uint a, uint b) private returns(uint){ ? ?
          ? ?return a + b; ?} ?

          ?function
          f() m(add(1, 1)) returns(uint){ ? ?
          ? ?return 1; ?} }

          八、payable(接收以太幣函數)

          是聲明了該函數涉及接收以太幣操作,如果函數沒有聲明為payable,并且在調用過程中有以太幣通過被調用的函數轉入合約,那么EVM虛擬機將會拋出異常,狀態回退。

          pragma solidity ^0.4.24;
          
          contract AddressExample { ? ?
          ? ?function
          AddressExample() payable{} ? ?
          ? ?function
          giveEthersTo(address _toAccount,uint amount){
          ? ? ? ?if (this.balance >=amount){ ? ? ? ? ? ? _toAccount.transfer(amount); ? ? ? ?} ? ?} ? ?
          ? ?function
          getBalance() view returns(uint){ ? ? ? ?
          ? ? ? ?return this.balance; ? ?} ? ?
          ? ?//function() payable{}
          }

          九、回退函數

          每一個合約有且僅有一個沒有名字的函數。這個函數無參數,也無返回值。如果調用合約時,沒有匹配上任何一個函數(或者沒有傳哪怕一點數據),就會調用默認的回退函數。

          此外,當合約收到ether時(沒有任何其它數據),這個函數也會被執行。在此時,一般僅有少量的gas剩余,用于執行這個函數(準確的說,還剩2300gas)。所以應該盡量保證回退函數使用少的gas。

          下述提供給回退函數可執行的操作會比常規的花費得多一點。

          寫入到存儲(storage) 創建一個合約 執行一個外部(external)函數調用,會花費非常多的gas 發送ether 請在部署合約到網絡前,保證透徹的測試你的回退函數,來保證函數執行的花費控制在2300gas以內。

          一個沒有定義一個回退函數的合約。如果接收ether,會觸發異常,并返還ether(solidity v0.4.0開始)。所以合約要接收ether,必須實現回退函數。下面來看個例子。下面來看個例子:

          pragma solidity ^0.4.24;
          
          contract Test { ? ?

          ? ?function()
          public payable{} ? ? ? ?
          ? ?function
          getX() view returns(uint){ ?
          ? ? ? ?return x; ? ?} ? ? ? ?
          ? ?function
          getBalance() view returns(uint){
          ? ? ? ?return this.balance; ? ?} } contract Caller { ? ?
          ? ?function
          Caller()payable{} ? ?
          ? ?function
          callTest(Test test) public{ ? ? ? ?test.call(0xabcdef01); ? ? ? ?// test.transfer(2 ether); ? ?} ? ? ? ?
          ? ? ? function
          getBalance() view returns(uint){
          ? ? ? ?return this.balance; ? ?} }

          如果涉及支付以太幣,即回退函數被聲明為payable類型,并且通過send或者transfer被調用,那么回退函數僅有你2300gas可以使用,如果回退函數中的代碼執行消耗超過2300gas那么被轉入的以太幣將會退回,修改過的數據狀態回退。

          以下操作會消耗超過2300gas:

          1. 修改狀態變量

          2. 創建新的合約實例

          3. 調用了會消耗gas較多的外部函數

          4. 發送以太幣

          用做接收以太幣回退函數內部僅能進行觸發事件操作。

          十、構造函數

          構造函數是一個用constructor關鍵字聲明的可選函數,它在創建合約時執行。構造函數可以是public,也可以是internal。如果沒有構造函數,則該合約將生成默認構造函數:contructor() public {}。

          pragma solidity ^0.4.24;
          
          contract A { ? ?
          ? ?uint public a; ? ?constructor(uint _a) internal { ? ? ? ?a = _a; ? ?} }

          在版本0.4.22之前,構造函數被定義為與合同名稱相同的特殊函數,有且只能有一個,不允許重載。這個函數將在合約創建時,執行一次,用于初始化一些配置。這個語法現在不推薦使用。

          pragma solidity ^0.4.24;
          
          contract ContractConstructor{ ?
          ?uint public counter; ?

          ?function ContractConstructor(){ ? ?counter++; ?} }

          上述合約在創建成功后,counter的值將為1。說明合約在創建時,被調用了一次。

          十一、函數的輸入參數與輸出參數

          Solidity函數的輸入參數的數量是可選的,也可以有任意數量的返回參數。

          入參(Input Parameter)與變量的定義方式一致,稍微不同的是,不會用到的參數可以省略變量名稱。一種可接受兩個整型參數的函數如下:

          pragma solidity ^0.4.0;
          contract Simple { ? ?
          ? ?function taker(uint _a, uint) { ? ? ? ?// do something with _a. ? ?} }

          出參(Output Paramets)在returns關鍵字后定義,語法類似變量的定義方式。返回結果的數量需要與定義的一致。如果給定了參數名,則函數可以不適用return關鍵字返回,如果沒有給定參數名則需要函數體重使用return關鍵字按照順序返回。

          pragma solidity ^0.4.24;
          
          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; ? ?} }

          pragma solidity ^0.4.24; contract Simple { ? ?
          ? ?//return sum and product ? ?function arithmetics(uint _a, uint _b) ?pure returns (uint , uint ) { ? ? ? ?
          ? ? ? ?return(_a + _b,_a * _b); ? ?} }

          十二、訪問函數

          編譯器為自動為所有的public的狀態變量創建訪問函數。下面的合約例子中,編譯器會生成一個名叫data的無參,返回值是uint的類型的值data。狀態變量的初始化可以在定義時完成。


          pragma solidity ^0.4.0;

          contract C{ ? ?
          ? ?uint public c = 10; }

          contract D{ ? ?
          ? ?C c = new C(); ? ? ? ?
          ? ?function getDataUsingAccessor() returns (uint){
          ? ? ? ?return c.c(); ? ?} }


          訪問函數有外部(external)可見性。如果通過內部(internal)的方式訪問,比如直接訪問,你可以直接把它當一個變量進行使用,但如果使用外部(external)的方式來訪問,如通過this.,那么它必須通過函數的方式來調用。


          pragma solidity ^0.4.0;
          
          
          contract C{
           ? ?uint public c = 10; ? ?
           ? ?
          ? ?function
          accessInternal() returns (uint){
          ? ? ? ?return c; ? ?} ? ? ? ?
          ? ?function
          accessExternal() returns (uint){ ?
          ? ? ? ?return this.c(); ? ?} }


          十三、抽象函數

          是沒有函數體的的函數。如下:


          pragma solidity ^0.4.0;
          
          contract Feline { ? ?
          ? ?function
          utterance() returns (bytes32); }


          這樣的合約不能通過編譯,即使合約內也包含一些正常的函數。但它們可以做為基合約被繼承。


          pragma solidity ^0.4.0;
          
          contract Feline { ? ?
          ? ?function utterance() returns (bytes32); ? ? ? ?
          ? ?function getContractName() returns (string){
          ? ? ? ?return "Feline"; ? ?} } contract Cat is Feline { ? ?
          ? ?function
          utterance() returns (bytes32) { return "miaow"; } }


          如果一個合約從一個抽象合約里繼承,但卻沒實現所有函數,那么它也是一個抽象合約。

          十四、數字和加密函數

          以下函數式solidity自帶的函數

          asser(bool condition):

          如果條件不滿足,拋出異常。

          addmod(uint x, uint y, uint k) returns (uint):

          計算(x + y) % k。加法支持任意的精度。但不超過(wrap around?)2**256。

          mulmod(uint x, uint y, uint k) returns (uint):

          計算(x * y) % k。乘法支持任意精度,但不超過(wrap around?)2**256。

          keccak256(...) returns (bytes32):

          使用以太坊的(Keccak-256)計算HASH值。緊密打包。

          sha3(...) returns (bytes32):

          等同于keccak256()。緊密打包。

          sha256(...) returns (bytes32):

          使用SHA-256計算HASH值。緊密打包。

          ripemd160(...) returns (bytes20):

          使用RIPEMD-160計算HASH值。緊密打包。

          ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):

          通過簽名信息恢復非對稱加密算法公匙地址。如果出錯會返回0,例如:


          function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) constant returns(bool) { ? ?

          ? ?bytes memory prefix = "\x19Ethereum Signed Message:\n32"; ? ?bytes32 prefixedHash = keccak256(prefix, hash); ? ?return ecrecover(prefixedHash, v, r, s) == (Your Address);
          }

          revert():

          取消執行,并回撤狀態變化。

          需要注意的是參數是“緊密打包(tightly packed)”的,意思是說參數不會補位,就直接連接在一起的。下面來看一個例子:

          keccak256("ab", "c")
          keccak256("abc")
          //hex
          keccak256(0x616263)
          keccak256(6382179)
          //ascii
          keccak256(97, 98, 99)

          上述例子中,三種表達方式都是一致的。

          如果需要補位,需要明確的類型轉換,如keccak256("\x00\x12")等同于keccak256(uint16(0x12))

          需要注意的是字面量會用,盡可能小的空間來存儲它們。比如,keccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))

          注意:

          在私鏈(private blockchain)上運行sha256,ripemd160或ecrecover可能會出現Out-Of-Gas報錯。因為它們實現了一種預編譯的機制,但合約要在收到第一個消息后才會存在。向一個不存在的合約發送消息,非常昂貴,所以才會導致Out-Of-Gas的問題。一種解決辦法是每個在你真正使用它們前,先發送1 wei到這些合約上來完成初始化。在官方和測試鏈上沒有這個問題。


          文章聲明:本文為火星財經專欄作者作品,不代表火星財經觀點,版權歸作者所有,如需轉載,請提前聯系作者或注明出處。

          推廣
          相關新聞

          漲幅榜

          你可能感興趣的內容
          下一篇

          支付:貨幣的起源

          河北十一选五软件