php属于选择结构语句的是什么意思,走进php内核】之 选择结构(if,switch)

选择结构

程序并不都是顺序执行的,选择结构用于判断给定的条件,根据判断的结果来控制程序的流程。PHP中通过if、elseif、else和switch语句实现条件控制。这一节我们就分析下PHP中两种条件语句的具体实现。

if语句

If语句用法:

if(Condition1){

Statement1;

}elseif(Condition2){

Statement2;

}else{

Statement3;

}

IF语句有两部分组成:condition(条件)、statement(声明),每个条件分支对应一组这样的组合,其中最后的else比较特殊,它没有条件,编译时也是按照这个逻辑编译为一组组的condition和statement,其具体的语法规则如下:

if_stmt:

if_stmt_without_else %prec T_NOELSE { $$ = $1; }

| if_stmt_without_else T_ELSE statement

{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_IF_ELEM, NULL, $3)); }

;

if_stmt_without_else:

T_IF '(' expr ')' statement { $$ = zend_ast_create_list(1, ZEND_AST_IF,

zend_ast_create(ZEND_AST_IF_ELEM, $3, $5)); }

| if_stmt_without_else T_ELSEIF '(' expr ')' statement

{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_IF_ELEM, $4, $6)); }

;

从上面的语法规则可以看出,编译if语句时首先会创建一个ZEND_AST_IF的节点,这个节点是一个list,用于保存各个分支的condition、statement,编译每个分支时将创建一个ZEND_AST_IF_ELEM的节点,它有两个子节点,分别用来记录:condition、statement,然后把这个节点插入到ZEND_AST_IF下,最终生成的AST:

062d4b87ba7f7af349d606577fbc5e2e.png

编译opcode时顺序编译每个分支的condition、statement即可,编译过程大致如下:

(1) 编译当前分支的condition语句,这里可能会有多个条件,但最终会归并为一个true/false的结果;

(2) 编译完condition后编译一条ZEND_JMPZ的opcode,这条opcode用来判断当前condition最终为true还是false,如果当前condition成立直接继续执行本组statement即可,无需进行跳转,但是如果不成立就需要跳过本组的statement,所以这条opcode还需要知道该往下跳过多少条opcode,而跳过的这些opcode就是本组的statement,因此这个值需要在编译完本组statement后才能确定,现在还无法确定;

(3) 编译当前分支的statement列表,其节点类型ZEND_AST_STMT_LIST,就是普通语句的编译;

(4) 编译完statement后编译一条ZEND_JMP的opcode,这条opcode是当condition成立执行完本组statement时跳出if的,因为当前分支既然条件成立就不需要再跳到其他分支,执行完当前分支的statement后将直接跳出if,所以ZEND_JMP需要知道该往下跳过多少opcode,而跳过的这些opcode是后面所有分支的opcode数,只有编译完全部分支后才能确定;

(5) 编译完statement后再设置步骤(2)中条件不成立时ZEND_JMPZ应该跳过的opcode数;

(6) 重复上面的过程依次编译后面的condition、statement,编译完全部分支后再设置各分支在步骤(4)中ZEND_JMP跳出if的opcode位置。

具体的编译过程在zend_compile_if()中,过程比较清晰:

void zend_compile_if(zend_ast *ast)

{

zend_ast_list *list = zend_ast_get_list(ast);

uint32_t i;

uint32_t *jmp_opnums = NULL;

//用来保存每个分支在步骤(4)中的ZEND_JMP opcode

if (list->children > 1) {

jmp_opnums = safe_emalloc(sizeof(uint32_t), list->children - 1, 0);

}

//依次编译各个分支

for (i = 0; i < list->children; ++i) {

zend_ast *elem_ast = list->child[i];

zend_ast *cond_ast = elem_ast->child[0]; //条件

zend_ast *stmt_ast = elem_ast->child[1]; //声明

znode cond_node;

uint32_t opnum_jmpz;

if (cond_ast) {

//编译condition

zend_compile_expr(&cond_node, cond_ast);

//编译condition跳转opcode:ZEND_JMPZ

opnum_jmpz = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0);

}

//编译statement

zend_compile_stmt(stmt_ast);

//编译statement执行完后跳出if的opcode:ZEND_JMP(最后一个分支无需这条opcode)

if (i != list->children - 1) {

jmp_opnums[i] = zend_emit_jump(0);

}

if (cond_ast) {

//设置ZEND_JMPZ跳过opcode数

zend_update_jump_target_to_next(opnum_jmpz);

}

}

if (list->children > 1) {

//设置前面各分支statement执行完后应跳转的位置

for (i = 0; i < list->children - 1; ++i) {

zend_update_jump_target_to_next(jmp_opnums[i]); //设置每组stmt最后一条jmp跳转为if外

}

efree(jmp_opnums);

}

}

最终if语句编译后基本是这样的结构:

2640507de05f0b99e9bd52ad08061265.png

执行时依次判断各分支条件是否成立,成立则执行当前分支statement,执行完后跳到if外语句;不成立则调到下一分支继续判断是否成立,以此类推。不管各分支条件有几个,其最终都会归并为一个结果,也就是每个分支只需要判断最终的条件值是否为true即可,而多个条件计算得到最终值的过程就是普通的逻辑运算。

Note: 注意elseif与else if,上面介绍的是elseif的编译,而else if则实际相当于嵌套了一个if,也就是说一个if的分支中包含了另外一个if,在编译、执行的过程中这两个是有差别的。

switch语句

switch语句与if类似,都是条件语句,很多时候需要将一个变量或者表达式与不同的值进行比较,根据不同的值执行不同的代码,这种场景下用if、switch都可以实现,但switch相对更加直观。

switch语法:

switch(expression){

case value1:

statement1;

case value2:

statement2;

...

default:

statementn;

}

这里并没有将break加入到switch的语法中,因为严格意义上break并不是switch的一部分,break属于另外一类单独的语法:中断语法,PHP中如果没有在switch中加break则执行时会从命中的那个case开始一直执行到结束,这与很多其它的语言不同(比如:golang)。

从switch的语法可以看出,switch主要包含两部分:expression、case list,case list包含多个case,每个case包含value、statement两部分。expression是一个表达式,但它将在case对比前执行,所以switch最终执行时就是拿expression的值逐个与case的value比较,如果相等则从命中case的statement开始向下执行。

下面看下switch的语法规则:

statement:

...

| T_SWITCH '(' expr ')' switch_case_list { $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $5); }

...

;

switch_case_list:

'{' case_list '}' { $$ = $2; }

| '{' ';' case_list '}' { $$ = $3; }

| ':' case_list T_ENDSWITCH ';' { $$ = $2; }

| ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; }

;

case_list:

/* empty */ { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); }

| case_list T_CASE expr case_separator inner_statement_list

{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); }

| case_list T_DEFAULT case_separator inner_statement_list

{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); }

;

case_separator:

':'

| ';'

;

从语法解析规则可以看出,switch最终被解析为一个ZEND_AST_SWITCH节点,这个节点主要包含两个子节点:expression、case list,其中expression节点比较简单,case list节点对应一个ZEND_AST_SWITCH_LIST节点,这个节点是一个list,有多个case子节点,每个case节点对应一个ZEND_AST_SWITCH_CASE节点,包括value(或expr)、statement两个子节点,生成的AST如下:

72b6d3456b0695813ec05608c9c8da43.png

与if不同,switch不会像if那样依次把每个分支编译为一组组的condition、statement,而是会先编译全部case的value表达式,再编译全部case的statement,编译过程大致如下:

(1)首先编译expression,其最终将得到一个固定的value;

(2)依次编译每个case的value,如果value是一个表达式则编译expression,与(1)相同,执行时其最终也是一个固定的value,每个case编译一条ZEND_CASE的opcode,除了这条opcode还会编译出一条ZEND_JMPNZ的opcode,这条opcode用来跳到当前case的statement的开始位置,但是statement在这时还未编译,所以ZEND_JMPNZ的跳转值暂不确定;

(3)编译完全部case的value后接着从头开始编译每个case的statement,编译前首先设置步骤(2)中ZEND_JMPNZ的跳转值为当前statement起始位置。

具体编译过程在zend_compile_switch()中,这里不再展开,编译后的基本结构如下:

4b8663cbab45d0547d99f90936090706.png

执行时首先如果switch的是一个表达式则会首先执行表达式的语句,然后再拿最终的结果逐个与case的值比较,如果case也是一个表达式则也先执行表达式,执行完再与switch的值比较,比较结果如果为true则跳到当前case的statement位置开始顺序执行,如果结果为false则继续向下执行,与下一个case比较,以此类推。

Note:

(1) case不管是表达式还是固定的值其最终比较时是一样的,如果是表达式则将其执行完以后再作比较,也就是说switch并不支持case多个值的用法,比如:case value1 || value2 : statement,这么写首先是会执行(value1 || value2),然后把结果与switch的值比较,并不是指switch的值等于value1或value2,这个地方一定要注意,如果想命中多个value只能写到不同case下

(2) switch的value与case的value比较用的是"==",而不是"==="

标签:case,ast,list,ZEND,编译,switch,内核,statement,php

来源: https://blog.csdn.net/u013756836/article/details/106565469

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值