编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

「链块技术39期」以太坊智能合约语言(四):Solidity函数(上)

wxchong 2024-09-30 03:46:02 开源技术 212 ℃ 0 评论

原文链接 http://www.liankuai.tech/public/technology/79.html

区块链技术教程——智能合约,本文主要讲解了区块链开发技术——以太坊智能合约语言Solidity 函数的种类、可见性、特殊函数以及修改器。

一、目录

  • 函数的定义
  • 函数的调用方式
  • 函数的可见性
  • 函数修改器
  • pure函数
  • constant、view函数
  • payable函数
  • 回退函数
  • 构造函数
  • 函数参数
  • 抽象函数
  • 数学和加密函数

二、函数的定义:

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

function (funcName) (<parameter types>) {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 public(公有函数)

  • 函数默认声明为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++;
 }
}


-END-

附上链块学院网课学习平台链接:http://wk.liankuai.tech/

助教卫星:lkxy007

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表