完整的函数的定义如下:
function () {internal(默认)|external} [constant] [payable] [returns (<return types>)]
若不写类型,默认的函数类型是internal
的。如果函数没有返回结果,则必须省略returns
关键字。
函数类型有两类;可分为internal
和external
函数。
因为不能在当前合约的上下文环境以外的地方执行,内部函数只能在当前合约内被使用。如在当前的代码块内,包括内部库函数,和继承的函数中。
外部函数由地址和函数方法签名两部分组成。可作为外部函数调用
的参数,或者由外部函数调用
返回。
状态变量
可以被定义为constant
,常量。这样的话,它必须在编译期间通过一个表达式赋值。赋值的表达式不允许:1)访问storage
;2)区块链数据,如now
,this.balance
,block.number
;3)合约执行的中间数据,如msg.gas
;4)向外部合约发起调用。也许会造成内存分配副作用表达式是允许的,但不允许产生其它内存对象的副作用的表达式。内置的函数keccak256
,keccak256
,ripemd160
,ecrecover
,addmod
,mulmod
可以允许调用,即使它们是调用的外部合约。
允许内存分配,从而带来可能的副作用的原因是因为这将允许构建复杂的对象,比如,查找表。虽然当前的特性尚未完整支持。
编译器并不会为常量在storage
上预留空间,每个使用的常量都会被对应的常量表达式所替换(也许优化器会直接替换为常量表达式的结果值)。
不是所有的类型都支持常量,当前支持的仅有值类型和字符串。
函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。
pragma solidity ^0.4.0;
contract C {
function f(uint a, uint b) constant returns (uint) {
return a * (b + 42);
}
}
如果一个函数需要进行货币操作,必须要带上payable
关键字,这样才能正常接收msg.value
。
每一个合约有且仅有一个没有名字的函数。这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。
此外,当合约收到ether
时(没有任何其它数据),这个函数也会被执行。在此时,一般仅有少量的gas剩余,用于执行这个函数(准确的说,还剩2300gas)。所以应该尽量保证回退函数使用少的gas。
下述提供给回退函数可执行的操作会比常规的花费得多一点。
ether
请在部署合约到网络前,保证透彻的测试你的回退函数,来保证函数执行的花费控制在2300gas以内。
一个没有定义一个回退函数的合约。如果接收ether,会触发异常,并返还ether(solidity v0.4.0开始)。所以合约要接收ether,必须实现回退函数。下面来看个例子:
pragma solidity ^0.4.0;
contract Test {
// This function is called for all messages sent to
// this contract (there is no other function).
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the "payable"
// modifier.
function() { x = 1; }
uint x;
}
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() payable { }
}
contract Caller {
function callTest(Test test) {
test.call(0xabcdef01); // hash does not exist
// results in test.x becoming == 1.
// The following call will fail, reject the
// Ether and return false:
test.send(2 ether);
}
}
在浏览器中跑的话,记得要先存ether。
修改器(Modifiers)
可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)
。下面我们来看一段示例代码:
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
// This contract only defines a modifier but does not use
// it - it will be used in derived contracts.
// The function body is inserted where the special symbol
// "_;" in the definition of a modifier appears.
// This means that if the owner calls this function, the
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
if (msg.sender != owner)
throw;
_;
}
}
contract mortal is owned {
// This contract inherits the "onlyOwner"-modifier from
// "owned" and applies it to the "close"-function, which
// causes that calls to "close" only have an effect if
// they are made by the stored owner.
function close() onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
function Register(uint initialPrice) { price = initialPrice; }
// It is important to also provide the
// "payable" keyword here, otherwise the function will
// automatically reject all Ether sent to it.
function register() payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) onlyOwner {
price = _price;
}
}
修改器可以被继承,使用将modifier
置于参数后,返回值前即可。
特殊_
表示使用修改符的函数体的替换位置。
从合约Register
可以看出全约可以多继承,通过,
号分隔两个被继承的对象。
修改器也是可以接收参数的,如priced
的costs
。
使用修改器实现的一个防重复进入的例子。
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;
}
}
例子中,由于call()
方法有可能会调回当前方法,修改器实现了防重入的检查。
如果同一个函数有多个修改器,他们之间以空格隔开,修饰器会依次检查执行。
需要注意的是,在Solidity的早期版本中,有修改器的函数,它的
return
语句的行为有些不同。
在修改器中和函数体内的显式的return
语句,仅仅跳出当前的修改器和函数体。返回的变量会被赋值,但整个执行逻辑会在前一个修改器后面定义的"_"后继续执行。
修改器的参数可以是任意表达式。在对应的上下文中,所有的函数中引入的符号,在修改器中均可见。但修改器中引入的符号在函数中不可见,因为它们有可能被重写。