🐹4 函数
[TOC]
函数
函数是合约的重要组成部分,合约就是通过暴露函数的调用来提供 API 式的服务,对外暴露一个可以调用的函数,就是用户通过操作 DAPP 与部署在链上的合约的交互。
函数定义
函数定义使用 function 关键字,函数定义必须声明其可见性、函数类型、形参、返回值。
函数可以接受任意数量形参,函数返回值可以返回任意数量参数。
自由函数与内部函数
在 Solidity 0.7.0 之前只能在合约内部定义函数,0.7.0 之后可以在合约内部和外部定义函数。
合约之外的函数称为“自由函数”,始终具有隐式的 internal 可见性。 自由函数的代码会包含在所有调用它们的合约中,类似于内部库函数。
自由函数仍然在合约的上下文内执行,可以访问变量 this ,也可以调用其它合约,发送以太币或销毁调用它们合约等其它行为。
自由函数与在合约中定义的函数的主要区别为:自由函数不能直接访问存储变量和不在他们的作用域范围内函数。
function 函数名(形参...) 可见性 函数类型 returns(返回值...){}
函数可见性
Solidity 中函数调用分内部调用和外部调用,内部调用不会产生实际的 EVM 调用称为消息调用,而外部调用则会产生一个 EVM 调用。
函数可见性有 external、public、internal 和 private 四种类型。
external
external 可见性的是外部函数,外部函数作为合约接口的一部分,可以从其他合约和交易中调用。 一个外部函数 f 不能从内部调用(即 f() 不起作用,但 this.f() 可以)。
当收到大量数据的时候,外部函数有时候会更有效率,因为数据不会从 calldata 复制到内存。
public
public 可见性的函数是合约接口的一部分,可以在内部直接调用或通过消息调用。
internal
internal 可见性的函数只能是内部访问,从当前合约内部或从它派生的合约访问,不能使用 this 调用。
private
private 可见性的函数仅在当前定义它的合约中使用,派生合约无法调用,不能使用 this 调用。
函数类型
函数类型有 payable 支付、view 视图函数、pure 纯函数、receive 接收函数 和 fallback 回退函数等五种。
编译器的 EVM 有4个操作符: REVERT 、RETURNDATASIZE、RETURNDATACOPY 、STATICCALL;函数类型的实现是基于这些操作符的。
payable 支付
接收 ether 函数上要增加payable标识,payable 函数才能正常接收msg.value。
需要支付 gas 的函数,如函数修改了状态变量,可以不显式使用 payable 修饰。
view 视图函数
将函数声明为 view 类型,要保证函数中不修改状态。
操作码 STATICCALL 用于 view 类型的函数, 这些函数强制在 EVM 执行过程中保持不修改状态。
constant 之前是 view 的别名,在0.5.0之后移除了。
pure 纯函数
将函数声明为 pure 类型,函数中不读取也不修改状态。
对于 pure 类型函数,编译器的 EVM 使用操作码 STATICCALL , 这并不保证状态未被读取, 但至少不被修改。
纯函数能够使用 revert() 和 require() 在发生错误时去还原潜在状态更改,还原状态更改不被视为 “状态修改”, 因为它只还原以前在没有
view或 pure 限制的代码中所做的状态更改, 并且代码可以选择捕获 revert 并不传递还原。
receive 接收以太函数
一个合约最多有一个 receive 函数, 声明函数为:
receive() external payable { ... }不需要 function 关键字,也没有参数和返回值并且必须是 external 可见性和 payable 修饰. 它可以是 virtual 的,可以被重载也可以有 修改器 modifier 。
在对合约没有任何附加数据调用(通常是对合约转账)是会执行 receive 函数. 例如 通过 .send()或 r.transfer() 如果 receive 函数不存在, 但是有payable 的 fallback 回退函数 那么在进行纯以太转账时,fallback 函数会调用。
如果两个函数都没有,这个合约就没法通过常规的转账交易接收以太(会抛出异常)。
receive 函数可能只有 2300 gas 可以使用(当使用 send 或 transfer 时), 除了基础的日志输出之外,进行其它操作的余地很小。写入存储、创建合约、调用消耗大量 gas 的外部函数、发送以太币等操作都会消耗 2300 gas。
fallback 回退函数
一个合约最多有一个 fallback 函数。函数声明为: fallback () external [payable] 或 fallback (bytes calldata _input) external [payable] returns (bytes memory _output)
没有 function 关键字。必须是 external 可见性,它可以是 virtual 的,可以被重载也可以有修改器 modifier。
如果在一个对合约调用中,没有其他函数与给定的函数标识符匹配 fallback 会被调用,或者在没有 receive 函数时,而没有提供附加数据对合约调用,那么 fallback 函数会被执行。
fallback 函数始终会接收数据,但为了同时接收以太时,必须标记为 payable。
如果使用了带参数的版本,_input 将包含发送到合约的完整数据(等于 msg.data ),并且通过 _output 返回数据。 返回数据不是 ABI 编码过的数据,它返回不经过修改的数据。
如果回退函数在接收以太时调用,可能只有 2300 gas 可以使用,这与 receive 接收函数一致。
与任何其他函数一样,只要有足够的 gas 传递给它,回退函数就可以执行复杂的操作。
修改状态的行为:
修改状态变量。
产生事件。
创建其它合约。
使用 selfdestruct 自毁。
通过调用发送以太币。
调用任何没有标记为 view 或者 pure 的函数。
使用低级调用。
使用包含特定操作码的内联汇编。
读取状态的行为:
读取状态变量。
访问 address(this).balance 或者.balance。
访问 block,tx, msg 中任意成员 (除 msg.sig 和 msg.data 之外)。
调用任何未标记为 pure 的函数。
使用包含某些操作码的内联汇编。
函数修改器 modifier
使用函数修改器 modifier 可以改变函数的行为,例如用于在执行函数之前自动检查某个条件,其实函数修改器就像拦截器,可以在函数执行前执行一些操作,也可在函数执行之后执行一些操作。
修改器 modifier 是可以被继承的,被标记为 virtual 可以被派生合约覆盖(重写)。
只能使用在当前合约或在基类合约中定义的修改器 modifier, 定义在库中的修改器只能在库函数使用。
函数可以使用多个修改器,使用空格隔开,修改器会依次检查执行。
修改器重写
父合约使用 virtual 修饰的修改器可以被重写,重写的修改器使用 override 修饰。
函数重载
多个不同形参的同名函数称为重载(overloading),这也适用于继承函数。
重载函数也存在于外部接口中,如果两个外部可见函数仅区别于 Solidity 内的类型而不是它们的外部类型则会导致错误。
尽可能避免函数重载,在 Solidity 中同名函数形参是 uint 和 uint8 是可以通过编译的,但是调用时就是不稳定的因素。
重载解析和参数匹配
通过将当前范围内的函数声明与函数调用中提供的参数相匹配,可以选择重载函数。 如果所有参数都可以隐式地转换为预期类型,则选择函数作为重载候选项。如果一个候选都没有,解析失败。
内部调用函数
合约中的函数在合约中调用称为内部调用函数。
内部调用函数支持递归调用,但是每个内部函数调用至少使用一个堆栈槽,最多有1024堆栈槽可用,因此要避免过多的递归调用。
函数调用在 EVM 中被解释为简单的跳转,当前内存不会被清除。
函数之间通过传递内存引用进行内部调用非常高效,只有在同一合约的函数可以内部调用。
外部调用函数(消息调用)
通过
this.fun();或合约实例.fun();方式调用,函数会通过一个消息调用来进行 外部调用,而不是内部调用函数那样直接的跳转。
调用其它合约的函数一定是外部调用,外部调用时所有的形参都需要被复制到内存。
合约间的函数调用不会创建自己的交易, 它是作为整个交易的一部分的消息调用。
与其它合约的交互存在风险,交互时当前合约会将控制权移交给被调用合约,而被调用合约能做任何事。被调用合约可以通过它自己的函数改变调用合约的状态变量。
Last updated