
本文共 3579 字,大约阅读时间需要 11 分钟。
一起学智能合约之五函数和修改器
函数是所有的语言都离不开的。其实在上面的表达式里就简单的说明了函数的一些形式和用法,这里再整体说明一下。
Solidity的函数形式如下:
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
function 函数名(参数列表){调用修饰符}[函数属性][返回值列表]
看一个实例:
pragma solidity ^0.4.16;
contract C {
function g(uint a) public pure returns (uint ret) { return f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
和上面的一对应就明白怎么回事了。现在主要介绍一下函数的高级用法:
- Solidity封装了两种函数的调用方式internal和external。
1)内部函数调用
当前合约中的函数可以直接(“从内部”)调用,也可以递归调用,就像下边这个荒谬的例子一样
pragma solidity ^0.4.16;
contract C {
function g(uint a) public pure returns (uint ret) { return f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
这些函数调用在 EVM 中被解释为简单的跳转。这样做的效果就是当前内存不会被清除,也就是说,通过内部调用在函数之间传递内存引用是非常有效的。
2)外部函数调用
如果想要调用其他合约的函数,需要外部调用。对于一个外部调用,所有的函数参数都需要被复制到内存。虽然下面两个合约写到了一起,但和不写在一些,部署到链上的结果是一致的。
pragma solidity ^0.4.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) public { feed = InfoFeed(addr); }
function callFeed() public { feed.info.value(10).gas(800)(); }
}
说明:payable 修饰符要用于修饰 info,否则,.value() 选项将不可用。
external调用时,实际是向目标合约发送一个消息调用。消息中的函数定义部分是一个24字节大小的消息体,20字节为地址,4字节为函数签名2。如果被调函数所在合约不存在(也就是账户中不包含代码)或者被调用合约本身抛出异常或者 gas 用完等,函数调用会抛出异常。
3)this关键字
可以使用this关键字来强制以external方式的调用。注意,不可以在构造函数中通过 this 来调用函数,因为此时真实的合约实例还没有被创建。
4)具名调用和匿名函数参数
函数调用参数也可以按照任意顺序由名称给出,如果它们被包含在 {} 中, 如以下示例中所示。参数列表必须按名称与函数声明中的参数列表相符,但可以按任意顺序排列。
pragma solidity ^0.4.0;
contract C {
function f(uint key, uint value) public {
// ...
}
function g() public {
// 具名参数
f({value: 2, key: 3});
}
}
5)函数重载
类似于大多数面向对象编程的语言,Solidity也是支持函数的重载的。
contract Simple {
function func(uint num) public pure returns (uint count) {
return 1;
}
function func(uint num, int id) public pure returns (uint count) {
return 2;
}
}
- Solidity为函数提供了四种可见性,external,public,internal,private。
1)external
external关键字表示合约函数调用的其它合约或者在交易中调用。在接收比较大的数组时性能可能会好一些。
2)public
函数默认声明为public。
这个没啥限制,想怎么访问就怎么访问,这其实相当于API。
3)internal
类似于protected,在本合约和继承合约中,只允许以internal的方式调用。
4)private
类似其它语言,只能在本合约中被访问(继承合约不可访问)。但是按照区块链的特性,其仍然可以被查看,只是不能被访问。
pragma solidity ^0.4.5;
contract Base{
//指定私有
function privateFun() private{}
function callFun(){
//内部调用
privateFun();
}
}
contract Simple is Base{
//错误
function callFun(){
//privateFun();
}
}
3、回退函数fallback
每个合约有且仅有一个没有名字的函数。函数没有参数和返回值。如果调用合约时,没有找到其它函数,即调用fallback函数。
另外,如果想对合约直接转帐(send()函数发送ether),那么这个回退函数也会执行。可以认为它是一个默认的接收函数。但为了安全起见,应该让此函数执行较少的动作。同时,限制此函数对gas的使用。(理论是2300gas)
所以在下列的操作中,都无法在fallback函数中执行,因为耗费的gas超过了2300。
1. 写入到存储(storage);
2. 创建一个合约;
3. 执行一个外部(external)函数调用,会花费非常多的gas;
4. 发送ether。
所以在部署合约前一定要进行安全测试,防止Gas超标,如果没有定义回退函数,那么向其发送以太坊,会引起异常并退回相应的以太坊(V0.4.0开始)。
pragma solidity ^0.4.0;
contract Simple{
function(){
//fallback function
}
}
4、修改器modifier
函数修改器类似于SpringMVC的增强(前置增强,后置增强,环绕增强),其实说白了就是简单的代码重用技术,没有任何高深的知识。
修改器(Modifiers)可以用来改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。
下面是一个官网的防止重入的例子:
pragma solidity ^0.4.0;
contract Mutex {
bool locked;
modifier noReentrancy() {
if (locked) throw;
locked = true;
_;
locked = false;
}
/// This function is protected by a mutex, which means that
/// reentrant calls from within msg.sender.call cannot call f again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() noReentrancy returns (uint) {
if (!msg.sender.call()) throw;
return 7;
}
}
5、总结
从上面的分析来看,Solidity的函数并没有什么高深的知识,只要是掌握其它语言,学习这个应该是很简单的,不过需要注意的是,要把一些细节吃透,防止出现异常。现在黑客越来越生猛,一不小心,就被割韭菜了。
发表评论
最新留言
关于作者
