4.1 常见控制流程
Solidity支持其它编程语言常见的顺序、分支和循环控制结构,包括if、for和while等以及配套的返回语句return、break和continue等。其语义类似JavaScript语言也支持常见的三目运算符 ?: ,但不支持switch和goto。其实笔者认为while循环和do while循环都不是必须的完全可以用for循环进行等效替换。分支和循环结构由于可以进行嵌套,因此可以通过组合来完成任意复杂的功能。
4.1.1 顺序结构
任何编程语言包括solidity最常见的是顺序结构。所谓顺序结构就是代码执行从上往下顺序执行,中间没有判断、跳转和循环,前面的语句总是先于后面的语句执行。常见的顺序结构有变量定义、声明和运算等。
4.1.2 分支结构
solidity中的程序分支是通过if语句来完成的,其使用布尔表达式或布尔常量来进行判断作为分支跳转执行的依据。暂时不支持传统编程语言中的switch多路分支,在需要进行多路分支的时候可以利用if语句的嵌套来模拟实现。其正式文法定义如下:
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
Statement = IfStatement | Block | (Continue | Break |
Return | SimpleStatement ) ‘;'
Block = '{' Statement* ‘}'
SimpleStatement = VariableDefinition | ExpressionStatement
ExpressionStatement = Expression
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
VariableDefinition = ('var' IdentifierList | VariableDeclaration |
'(' VariableDeclaration? (',' VariableDeclaration? )* ')' )
( '=' Expression )?
由正式文法定义可知if语句的else部分是可选的,if语句本身也支持嵌套。如果在循环内部也支持continue、break和return等语句。结论就是if语句的判断部分支持布尔表达式或者常量,其内部实现体语句支持各种执行语句。
需要特别注意的一点就是if不支持其它传统编程语言中的整数自动转换为布尔值,示意代码如下:
contract IfTest {
function test(uint256 data) public pure returns(bool) {
// if(data) return false; //compile err
if(data > 0){
return true;
}
if(data > 2) return true;
return false;
}
}
虽然语法上if语句包括后面介绍的循环结构也支持空执行体但无实际意义。如果if执行语句只有一条可以省略大括号,但笔者还是建议始终坚持用大括号包围if语句的执行体哪怕只有一个语句,因为对智能合约而言任何代码技巧在可读性和安全性面前都是次要的。
solidity不支持其它语言中的类似switch的多路分支选择结构,但可以通过嵌套if结构来进行模拟实现,示意代码如下:
function test(uint256 val) public pure returns(uint256) {
if(val > 10){
return 5;
}else if(val > 8){
return 4;
}else if(val > 6){
return 2;
}else{
return 1;
}
}
不过当前版本solidity支持?模式的三元选择符,这在某些程度上可以简化代码,示意代码如下:
function test () public pure returns(uint256) {
uint256 ret = 2>1? 222:111;
return ret;
}
4.1.3 循环结构
循环语句可以在满足判断条件情况下重复执行循环体内的语句,判断条件不满足时则退出循环。因此对循环结构来说重要的是退出条件要在某种情况下成立否则会造成死循环。当循环中由于bug发生死循环时在solidity中会造成gas耗尽从而导致运行异常发生进而回滚状态更改。solidity主要支持三种循环结构for、while和do…while,其循环本质是一样的,只是语法不同但都包括初始化语句、判断条件和循环体。有了前面if结构的文法定义基础,循环结构文法就比较简单和好理解了,定义如下:
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';'
(ExpressionStatement)? ')' Statement
WhileStatement = 'while' '(' Expression ')' Statement
for循环
for循环要在开始时设定循环变量的初始值,每次循环体结束执行时要检查退出条件是否成立以及下一次循环的起点,这点和其他传统的编程语言行为和语义一致的。循环体内可以包括前面介绍的分支以及后面介绍的循环语句,也可以利用continue退出本次循环以及break退出整个循环。应该说for循环是比较常见的控制结构,示意代码如下:
contract ForTest {
function doTest(uint num) public pure returns(uint){
uint total;
for(uint idx = 0; idx < num; idx++) {
if(4 == idx) continue;
total += idx;
if(idx > 6) break;
}
return total;
}
}
从范例代码可以看出和其他传统语言中的for用法没有本质区别,这里要留意的是定义循环变量idx时不能用类型自动推导var定义,否则当循环目标total大于255时会造成死循环,因为值为0变量编译器自动推导后设置目标类型类型为uint8而不是uint从而造成溢出。
while循环
whilie循环和for循环最大的区别就是初始化语句是在循环结构外面定义的。上面的for循环实现可以改为等效的while循环,只是变量定义顺序要做调整,示意代码如下:
contract WhileTest {
function doTest(uint num) public pure returns(uint){
uint total;
uint idx;
while(idx < num) {
idx++;
if(4 == idx) continue;
total += idx;
if(idx > 6) break;
}
return num <= 20 ? total : num;
}
}
do…while循环
do…while循环和while循环类似但唯一的区别是即使判断条件不满足也会执行一次,而while循环在判断条件不成立时一次也不会执行,示意代码如下:
contract DowhileTest {
function doTest() public pure returns(uint){
uint total;
do {
total = 123;
} while(false);
return total;
}
}
即使循环条件永远不可能成立但循环体都会至少执行一次,一次返回值是123而不是0。
4.2 异常处理
solidity在运行过程中异常大部分情况下都是自动抛出来的,当程序执行错误或某些执行结果期望值没有满足时EVM会自动抛出异常。虽然某些指令如throw也支持手工抛出异常但使用场景比较少见。如果异常发生EVM会终止当前指令的执行,回滚之前对合约状态变量的改变,如果涉及多层函数调用异常还会从发生的函数向上层传递。有些特殊的底层函数在发生异常时并不遵循上述异常处理原则,而仅仅返回false如call、send、delegatecall和callcode,在调用这些底层函数时记得检查其返回值是否为true。示意代码如下:
网友评论