<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 4.3 循環結構 實際應用中有許多具有規律性的重復操作,因此在程序中就需要重復執行某些語句。循環結構是在一定條件下反復執行某段程序的流程結構,被反復執行的程序被稱為循環體。循環語句是由循環體及循環的終止條件兩部分組成的。 PHP中的循環結構有4種:while、for、foreach、do while,接下來我們分析下這幾個結構的具體的實現。 ### 4.3.1 while循環 while循環的語法: ```php while(expression) { statement;//循環體 } ``` while的結構比較簡單,由兩部分組成:expression、statement,其中expression為循環判斷條件,當expression為true時重復執行statement,具體的語法規則: ```c statement: ... | T_WHILE '(' expr ')' while_statement { $$ = zend_ast_create(ZEND_AST_WHILE, $3, $5); } ... ; while_statement: statement { $$ = $1; } | ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; } ; ``` 從while語法規則可以看出,在解析時會創建一個`ZEND_AST_WHILE`節點,expression、statement分別保存在兩個子節點中,其AST如下: ![](https://box.kancloud.cn/900d7edc466058e56803d180ac97b024_363x201.png) while編譯的過程也比較簡單,比較特別的是while首先編譯的是循環體,然后才是循環判斷條件,更像是do while,編譯過程大致如下: * __(1)__ 首先編譯一條ZEND_JMP的opcode,這條opcode用來跳到循環判斷條件expression的位置,由于while是先編譯循環體再編譯循環條件,所以此時還無法確定具體的跳轉值; * __(2)__ 編譯循環體statement;編譯完成后更新步驟(1)中ZEND_JMP的跳轉值; * __(3)__ 編譯循環判斷條件expression; * __(4)__ 編譯一條ZEND_JMPNZ的opcode,這條opcode用于循環判斷條件執行完以后跳到循環體的,如果循環條件成立則通過此opcode跳到循環體開始的位置,否則繼續往下執行(即:跳出循環)。 具體的編譯過程: ```c void zend_compile_while(zend_ast *ast) { zend_ast *cond_ast = ast->child[0]; zend_ast *stmt_ast = ast->child[1]; znode cond_node; uint32_t opnum_start, opnum_jmp, opnum_cond; //(1)編譯ZEND_JMP opnum_jmp = zend_emit_jump(0); zend_begin_loop(ZEND_NOP, NULL); //(2)編譯循環體statement,opnum_start為循環體起始位置 opnum_start = get_next_op_number(CG(active_op_array)); zend_compile_stmt(stmt_ast); //設置ZEND_JMP opcode的跳轉值 opnum_cond = get_next_op_number(CG(active_op_array)); zend_update_jump_target(opnum_jmp, opnum_cond); //(3)編譯循環條件expression zend_compile_expr(&cond_node, cond_ast); //(4)編譯ZEND_JMPNZ,用于循環條件成立時跳回循環體開始位置:opnum_start zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, opnum_start); zend_end_loop(opnum_cond); } ``` 編譯后opcode整體如下: ![](https://box.kancloud.cn/c5c054e86943e0c61b55245569b37154_378x367.png) 運行時首先執行`ZEND_JMP`,跳到while條件expression處開始執行,然后由`ZEND_JMPNZ`對條件的執行結果進行判斷,如果條件成立則跳到循環體statement起始位置開始執行,如果條件不成立則繼續向下執行,跳出while,第一次循環執行以后將不再執行`ZEND_JMP`,后續循環只有靠`ZEND_JMPNZ`控制跳轉,循環體執行完成后接著執行循環判斷條件,進行下一輪循環的判斷。 > __Note:__ 實際執行時可能會省略`ZEND_JMPNZ`這一步,這是因為很多while條件expression執行完以后會對下一條opcode進行判斷,如果是`ZEND_JMPNZ`則直接根據條件成立與否進行快速跳轉,不需要再由`ZEND_JMPNZ`判斷,比如: > > $a = 123; > while($a > 100){ > echo "yes"; > } > `$a > 100`對應的opcode:ZEND_IS_SMALLER,執行時發現$a與100類型可以直接比較(都是long),則直接就能知道循環條件的判斷結果,這種情況下將會判斷下一條opcode是否為ZEND_JMPNZ,是的話直接設置下一條要執行的opcode,這樣就不需要再單獨執行依次ZEND_JMPNZ了。 > > 上面的例子如果`$a = '123';`就不會快速進行處理了,而是按照正常的邏輯調用ZEND_JMPNZ。 ### 4.3.2 do while循環 do while與while非常相似,唯一的區別在于do while第一次執行時不需要判斷循環條件。 do while循環的語法: ```php do{ statement;//循環體 }while(expression) ``` do while編譯過程與while的基本一致,不同的地方在于do while沒有`ZEND_JMP`這條opcode: ```c void zend_compile_do_while(zend_ast *ast) { zend_ast *stmt_ast = ast->child[0]; zend_ast *cond_ast = ast->child[1]; znode cond_node; uint32_t opnum_start, opnum_cond; //(1)編譯循環體statement,opnum_start為循環體起始位置 opnum_start = get_next_op_number(CG(active_op_array)); zend_compile_stmt(stmt_ast); //(2)編譯循環判斷條件expression opnum_cond = get_next_op_number(CG(active_op_array)); zend_compile_expr(&cond_node, cond_ast); //(3)編譯ZEND_JMPNZ zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, opnum_start); } ``` 編譯后的結果: ![](https://box.kancloud.cn/449874d21b056771b06a075a3dd80c55_410x339.png) 運行時首先執行循環體statement,然后執行循環判斷條件,如果條件成立跳到循環體起始位置,否則結束循環。 ### 4.3.3 for循環 for循環語法: ```php for (init expr; condition expr; loop expr){ statement } ``` init expr在循環開始前無條件執行一次,后面循環不再執行;condition expr在每次循環開始前運算,是循環的判斷條件,如果值為true,則繼續循環,執行循環體,如果值為false,則終止循環;loop expr在每次循環體執行完以后被執行。 for的語法規則: ```c statement: ... | T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement { $$ = zend_ast_create(ZEND_AST_FOR, $3, $5, $7, $9); } ... ; ``` 從語法規則可以看出,for被編譯為`ZEND_AST_FOR`節點,包含4個子節點,分別為:expr1、expr2、expr3、statement。 ![](https://box.kancloud.cn/4fc2a7a02bd01fae2fc48230b858ace3_708x283.png) for的編譯與while類似,只是多了init expr、loop expr兩部分,編譯過程大致如下: * __(1)__ 首先編譯初始化表達式:init expr; * __(2)__ 編譯一條`ZEND_JMP`的opcode,此opcode用于跳到條件expression位置,具體跳轉值需要后面才能確定; * __(3)__ 編譯循環體statement; * __(4)__ 編譯loop expr;然后設置步驟(2)中`ZEND_JMP`的跳轉值; * __(5)__ 編譯循環條件:condition expr; * __(6)__ 編譯一條`ZEND_JMPNZ`,此opcode用于循環條件成立時跳到循環體起始位置。 具體編譯過程: ```c void zend_compile_for(zend_ast *ast) { zend_ast *init_ast = ast->child[0]; zend_ast *cond_ast = ast->child[1]; zend_ast *loop_ast = ast->child[2]; zend_ast *stmt_ast = ast->child[3]; znode result; uint32_t opnum_start, opnum_jmp, opnum_loop; //(1)編譯init expression zend_compile_expr_list(&result, init_ast); zend_do_free(&result); //(2)編譯ZEND_JMP opnum_jmp = zend_emit_jump(0); //opnum_start是循環體起始位置 opnum_start = get_next_op_number(CG(active_op_array)); //(3)編譯循環體 zend_compile_stmt(stmt_ast); //(4)編譯loop expression opnum_loop = get_next_op_number(CG(active_op_array)); zend_compile_expr_list(&result, loop_ast); zend_do_free(&result); //設置ZEND_JMP跳轉值 zend_update_jump_target_to_next(opnum_jmp); //(5)編譯循環條件expression zend_compile_expr_list(&result, cond_ast); zend_do_extended_info(); //(6)編譯ZEND_JMPNZ zend_emit_cond_jump(ZEND_JMPNZ, &result, opnum_start); } ``` 最終編譯結果: ![](https://box.kancloud.cn/e257f55e77a2b723a420c0140d503609_424x474.png) 運行時首先執行初始化表達式:init expression,然后執行`ZEND_JMP`跳到循環條件expression處,如果條件成立則執行`ZEND_JMPNZ`跳到循環體起始位置依次執行循環體、loop expression,如果條件不成立則終止循環,第一次循環之后就是:`循環條件->ZEND_JMPNZ->循環體->loop expression`之間循環了。 ### 4.3.4 foreach循環 foreach是PHP針對數組、對象提供的一種遍歷方式,foreach語法: ```php foreach (array_expression as $key => $value){ statement } ``` 遍歷arraiy_expression時每次循環會把當前單元的值賦給$value,當前單元的鍵值賦給$key,其中$key可以省略,$value前也可以加"&"表示引用單元的值。 foreach的語法規則: ```c statement: ... //省略key的規則: foreach($array as $v){ ... } | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement { $$ = zend_ast_create(ZEND_AST_FOREACH, $3, $5, NULL, $7); } //有key的規則: foreach($array as $k=>$v){ ... } | T_FOREACH '(' expr T_AS foreach_variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement { $$ = zend_ast_create(ZEND_AST_FOREACH, $3, $7, $5, $9); } ... ; ``` foreach在編譯階段解析為`ZEND_AST_FOREACH`節點,包含4個子節點,分別表示:遍歷的數組或對象、遍歷的value、遍歷的key以及循環體,生成的AST類似這樣: ![](https://box.kancloud.cn/3e3ac2b9c5592bc1ac658d0dbb1622c9_613x240.png) 如果value是指向數組或對象成員的引用,則value對應的節點類型為`ZEND_AST_REF`。 相對上面幾種常規的循環結構,foreach的實現略顯復雜:$key、$value實際就是兩個普通的局部變量,遍歷的過程就是對兩個局部變量不斷賦值、更新的過程,以數組為例,首先將數組拷貝一份用于遍歷(只拷貝zval,value還是指向同一份),從arData第一個元素開始,把Bucket.zval.value值賦值給$value,把Bucket.key(或Bucket.h)賦值給$key,然后更新迭代位置:將下一個元素的位置記錄在`zval.u2.fe_iter_idx`中,這樣下一輪遍歷時直接從這個位置開始,這也是遍歷前為什么要拷貝一份zval用于遍歷的原因,如果發現`zval.u2.fe_iter_idx`已經到達arData末尾了則結束遍歷,銷毀一開始拷貝的zval。舉個例子來看: ```php $arr = array(1,2,3); foreach($arr as $k=>$v){ echo $v; } ``` 局部變量對應的內存結構: ![](https://box.kancloud.cn/58dfcbbf26b84c0fa1b5e26b4d402fd4_733x413.png) 如果value是引用則在循環前首先將原數組或對象重置為引用類型,然后新分配一個zval指向這個引用,后面的過程就與上面的一致了,仍以上面的例子為例,如果是:`foreach($arr as $k=>&$v){ ... }`則: ![](https://box.kancloud.cn/533a8a2476e3d816b3eea1f557ff6069_845x408.png) 了解了foreach的實現、運行機制我們再回頭看下其編譯過程: * __(1)__ 編譯"拷貝"數組/對象操作的opcode:`ZEND_FE_RESET_R`,如果value是引用則是`ZEND_FE_RESET_RW`,執行時如果發現數組或對象屬性為空則直接跳出遍歷,所以這條opcode還需要知道跳出的位置,這個位置需要編譯完foreach以后才能確定; * __(2)__ 編譯fetch數組/對象當前單元key、value的opcode:`ZEND_FE_FETCH_R`,如果是引用則是`ZEND_FE_FETCH_RW`,此opcode還需要知道當遍歷已經到達數組末尾時跳出遍歷的位置,與步驟(1)的opcode相同,另外還有一個關鍵操作,前面已經說過遍歷的key、value實際就是普通的局部變量,它們的內存存儲位置正是在這一步分配確定的,分配過程與普通局部變量的過程完全相同,如果value不是一個CV變量(比如:foreach($arr as $v["xx"]){...})則還會編譯其它操作的opcode; * __(3)__ 如果foreach定義了key則編譯一條賦值opcode,此操作是對key進行賦值; * __(4)__ 編譯循環體statement; * __(5)__ 編譯跳回遍歷開始位置的opcode:`ZEND_JMP`,一次遍歷結束時會跳回步驟(2)編譯的opcode處進行下次遍歷; * __(6)__ 設置步驟(1)、(2)兩條opcode跳過的opcode數; * __(7)__ 編譯`ZEND_FE_FREE`,此操作用于釋放步驟(1)"拷貝"的數組。 最終編譯后的結構: ![](https://box.kancloud.cn/4f7917762af318aa95eb04d1509832f2_426x440.png) 運行時的步驟: * __(1)__ 執行`ZEND_FE_RESET_R`,過程上面已經介紹了; * __(2)__ 執行`ZEND_FE_FETCH_R`,此opcode的操作主要有三個:檢查遍歷位置是否到達末尾、將數組元素的value賦值給$value、將數組元素的key賦值給一個臨時變量(注意與value不同); * __(3)__ 如果定義了key則執行`ZEND_ASSIGN`,將key的值從臨時變量賦值給$key,否則跳到步驟(4); * __(4)__ 執行循環體的statement; * __(5)__ 執行`ZEND_JMPNZ`跳回步驟(2); * __(6)__ 遍歷結束后執行`ZEND_FE_FREE`釋放數組。 PHP中還有幾個與遍歷相關的函數: * current() - 返回數組中的當前單元 * each() - 返回數組中當前的鍵/值對并將數組指針向前移動一步 * end() - 將數組的內部指針指向最后一個單元 * next() - 將數組中的內部指針向前移動一位 * prev() - 將數組的內部指針倒回一位
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看