和其它常见的编程语言一样在solidity中函数是一个重要的功能组成部分,外部账户和合约之间、合约与合约之间的交互实际上都是通过函数来完成的。函数体实现包括我们常见的参数列表与返回值,也支持调用其它内部函数、继承来的函数以及递归调用等,但由于栈高度上限为1024所以循环次数、函数调用深度等不能太深,较深的递归可以用循环替代。由于区块链平台编程的特殊性,solidity在传统的函数概念上叠加了区块链相关的特性比如支付属性、修改器和fallback陷进函数等。函数正式的文法定义如下:
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( ModifierInvocation | StateMutability |
'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | ( DoWhileStatement |
PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ‘;'
从函数文法定义可知solidity中函数以关键字function开头,然后跟函数名称和参数列表,后面跟修改器列表和可见性说明符。函数修改器也支持多个参数和自己的函数体。最后是返回值列表其中返回值和参数列表一样支持多个。后面跟函数体或者空函数体,而函数体也就是我们前面介绍的顺序、分支和循环语句的组合或嵌套。有一点需要注意的是函数头中返回值列表定义时是returns关键字而在进行实际值返回时是return关键字,这点在实际使用时需特别注意。下面我们详细介绍函数的每一个组成部分的功能特性和注意事项。
5.1 函数参数
solidity中函数支持2种类型的入口参数:与函数定义时数量和类型一致的参数列表和命名参数列表,示意代码如下:
contract ArgsTest {
function argsGet(address target, uint amount) public view returns(uint) {
uint total = target.balance + amount;
return total;
}
function doTest() public view returns(uint, uint, uint){
uint uptAmt = 123;
uint total1 = argsGet(msg.sender, uptAmt);
uint total2 = argsGet({target:msg.sender, amount:uptAmt});
uint total3 = argsGet({amount:uptAmt, target:msg.sender});
return (total1, total2, total3);
}
}
由范例代码可知当使用默认参数传递方式时,参数类型和数量必须与定义时一致。但使用命名参数传递时顺序可以任意指定但数量要一致,这三种调用方式返回结果都是一样的,结果如下:
{
"0": "uint256: 99999999999996843958",
"1": "uint256: 99999999999996843958",
"2": "uint256: 99999999999996843958"
}
笔者建议尽可能使用命名参数传递方式提高程序可读性降低潜在的由于参数传递位置错误导致的程序错误。参数在函数体内没有使用时可以省略其名称,示意代码如下:
function argsIgnore(uint amount, bytes4) public pure returns(uint) {
return amount + 10;
}
实例中我们只用到了第一个参数amount而没有用第二个参数,所以可以忽略其名称。忽略函数参数列表中变量名称的实现在接口合约函数定义、事件定义以及继承中比较常见。
另外需要注意的是数组作为参数传递时需要明确指定存储位置。当以memory方式拷贝传递,因此函数体内的修改不会影响原值,但如果明确指定通过storage方式进行传递则传递的是引用,函数体内的修改会影响原值,示意代码如下:
contract ArgsTest2 {
uint[] datas;
event LogVal(string info, uint first, uint second);
function arrayMem(uint[] memory arr) public pure{
arr[0] = 333; arr[1] = 444;
}
function arrayStorage(uint[] storage arr) internal {
arr[0] = 333; arr[1] = 444;
}
function doTest() public {
datas = new uint[](2);
datas[0] = 11; datas[1]=22;
emit LogVal("before arrayDefault", datas[0], datas[1]);
arrayMem(datas);
emit LogVal("after arrayDefault", datas[0], datas[1]);
emit LogVal("before arrayStorage", datas[0], datas[1]);
arrayStorage(datas);
emit LogVal("after arrayStorage", datas[0], datas[1]);
}
}
笔者建议如无特殊原因在将数组作为参数传递给函数时明确指定其存储位置如memory。













网友评论