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

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

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

          专注区块链信息及金融服务

          智能合约基础语言:Solidity函数 | 四

          链块学院 ·

          2018年09月23日

          热度: 39989

          每一个合约?#26143;?#20165;有一个没有名字的函数。


          一、目录

          函数的定义

          函数的调用方式

          函数的可见性

          ? 函数修改器

          ? pure函数

          ? constant、view函数

          ? payable函数

          ? 回退函数

          ? 构造函数

          ? 函数参数

          ? 抽象函数

          ? 数学?#22270;?#23494;函数

          二、函数的定义

          function关键字声明的,合约中的可执行单元,一个函数的完整定义如下:

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

          三、函数的调用方式

          Solidity封装了两种函数的调用方式internal(内部调用)和external(外部调用)。

          3.1 internal(内部调用方式)

          internal调用,实现时转为简单的EVM跳转,所以它能直接使用上下文环境中的数据,对于引用传递时将会变得非常高效(不?#27599;?#36125;数据)。

          在当前的代码单元内,如对合约内函数,引入的库函数,?#32422;?#29238;类合约中的函数直接使用?#35789;且詉nternal方式的调用。我们来看个简单的例子:

          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弄混。声明只?#19988;?#21619;着这个函数需要使用相对应的调用方式去调用。

          四、函数的可见性

          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的方式调?#27809;?#25253;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(私有函数)

          ? 只能在当前合约中?#29615;?#38382;(不可在被继承的合约中访问)。

          ? ?#35789;?#22768;明为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. 使用了包含?#25215;?#25805;作码的内联汇编

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

          六、constant/view(只读函数)

          不改变状态的函数可以被声明为只读函数一下几?#26234;?#20917;被视为修改了状态:

          1. 修改状态变量

          2. 触发?#24405;?/p>

          3. 创建了其他合约的实例

          4. 使用了selfdestruct自我销毁

          5. 调用了向合约转账的函数

          6. 调用了非只读函数或者纯函数

          7. 使用了底层调用

          8. 使用了包含?#25215;?#25805;作码的内联汇编

          注意:

          constant是view的一个别名,会在0.5.0版本中遗弃,访问器(getter)方法默认被标记为view调用只读函数。

          七、函数修改器

          在?#23548;?#24773;况中,我们经常需要对调用者进行一些限制。?#28909;紓?#21482;能是合约的所有者才能改变归属。我们一起来看看如何用函数修改器实现这一限制:

          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 函数修改器参数支?#30452;?#36798;式

          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{}
          }

          九、回退函数

          每一个合约?#26143;?#20165;有一个没有名字的函数。这个函数无参数,?#21442;?#36820;回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。

          ?#36865;猓?#24403;合约收到ether时(没有任何其它数据),这个函数?#19981;?#34987;执行。在此时,一般仅有少量的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. 发送以太币

          用做接收以太?#19968;?#36864;函数内部仅能进行触发?#24405;?#25805;作。

          十、构造函数

          构造函数是一个用constructor关键字声明的可选函数,它在创建合约时执行。构造函数可以是public,?#37096;?#20197;是internal。如果没有构造函数,则该合约将生成默认构造函数:contructor() public {}。

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

          在版本0.4.22之前,构造函数被定义为与合同名称相同的特殊函数,?#26143;?#21482;能有一个,不允许重载。这个函数将在合约创建时,执行一次,用于初始化一些配置。这个语法现在不推荐使用。

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

           function ContractConstructor(){    counter++;  } }

          上述合约在创建成功后,counter的值将为1。说明合约在创建时,被调用了一次。

          十一、函数的输入参数与输出参数

          Solidity函数的输入参数的数量是可选的,?#37096;?#20197;有?#25105;?#25968;量的返回参数。

          入参(Input Parameter)与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称。一种可接受两个整型参数的函数如下:

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

          出参(Output Paramets)在returns关键字后定义,语法类似变量的定义方式。返回结果的数量需要与定义的一致。如果给定了参数名,则函数可以不?#35270;胷eturn关键字返回,如果没有给定参数名则需要函数体重使用return关键字按?#36134;承?#36820;回。

          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)的方式访问,?#28909;?#30452;接访问,你可以直接把它当一个变量进行使用,但如果使用外部(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); }


          这样的合约不能通过编译,?#35789;?#21512;约内也包含一些正常的函数。但它们可以做为基合约被继承。


          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"; } }


          如果一个合约从一个抽象合约里继承,但却没实现所有函数,那么它也是一个抽象合约。

          十四、数字?#22270;?#23494;函数

          以下函数式solidity自带的函数

          asser(bool condition):

          如果条件不满足,抛出异常。

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

          计算(x + y) % k。加法支持?#25105;?#30340;精度。但不超过(wrap around?)2**256。

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

          计算(x * y) % k。乘法支持?#25105;?#31934;度,但不超过(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):

          通过签名信息?#25351;?#38750;对称加密算法公匙地址。如果出错会返回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)

          上述例子中,三?#30452;?#36798;方式都是一致的。

          如果需要补位,需要明确的类型转换,如keccak256("\x00\x12")等同于keccak256(uint16(0x12))

          需要注意的是字面量会用,尽可能小的空间来存储它们。?#28909;紓琸eccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))

          注意:

          在私链(private blockchain)上运行sha256,ripemd160或ecrecover可能会出现Out-Of-Gas报错。因为它们实现了一种预编译的机制,但合约要在收到第一个消息后才会存在。向一个不存在的合约发送消息,非常昂贵,所以才会导致Out-Of-Gas的问题。一种解决办法是每个在你真正使用它们前,先发送1 wei到这些合约上来完成初始化。在官方和测试链上没有这个问题。


          文章声明:本文为火星财经专栏作者作品,不代表火星财经观点,版权归作者所有,如需转载,请提前联系作者或注明出处。

          声明:本文为入驻“火星号”作者作品,不代表火星财经官方立场。转载请注明出处、作者和本文链接
          提示?#21644;?#36164;有风险,入?#34892;?#35880;慎。本资讯不作为投资理财建议。

          推广
          相关新闻

          涨幅榜

          你可能?#34892;?#36259;的内容
          下一篇

          支付:货币的起源

          河北十一选五软件