自增和自減基礎
學過程式設計語言的同學應該都可以随口說出 ++a 和 a++ 的差別,具體的差別如下:
Example | Name | Effect |
---|---|---|
++ a | Pre-increment | Increments abyone,thenreturns a. |
a++ | Post-increment | Returns a,thenincrements a by one. |
– a | Pre-decrement | Decrements abyone,thenreturns a. |
a– | Post-decrement | Returns a,thendecrements a by one. |
++a 表示取 a 的位址,增加記憶體中 a 的值,然後把值放在寄存器中。
a++ 表示取 a 的位址,把 a 的值裝入寄存器中,然後增加記憶體中 a 的值。
php 中的遞增和遞減
-
官方文檔:
http://php.net/manual/zh/language.operators.increment.php
在php中,通常情況下的遞增和遞減沒什麼特别,下面我們看一些特殊的遞增和遞減。
$a = NULL; $a++; //int 1
$a = NULL; $a--; //null
$a = true; $a++; //true
$a = true; $a--; //true
$a = false; $a++; //false
$a = false; $a--; //false
從上面可以看出,php中遞增和遞減運算符不影響布爾值;遞減NULL值沒有效果,但是遞增NULL值的結果為1。
$a = 'C'; $a++; //string D
$a = 'C'; $a--; //string C
$a = 'C2'; $a++; //string C3
$a = 'C2'; $a--; //string C2
$a = '2C'; $a++; //string D
$a = '2C'; $a--; //string C
從上面可以看出,在php中,字元串支援遞增,但是不支援遞減。
再看一下下面的代碼:
$a = 'Z'; $a++; //string AA
$a = 'C9'; $a++; //string D0
‘A’執行遞增,結果為’B’;‘Z’執行遞增,結果為’AA‘,’C9’遞增,結果為’D0’。這和Perl相似。
php源碼分析: $
a++與++ $
a
$
$
首先說明一下,此處的分析基于 php5.6 的源碼分析。
對于字首自增(++$a),包含的opcode為PRE_INC,其最終調用的是Zend/zend_vm_execute.h檔案中的ZEND_PRE_INC_SPEC_CV_HANDLER函數,源碼如下:
static int ZEND_FASTCALL ZEND_PRE_INC_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval **var_ptr;
SAVE_OPLINE();
var_ptr = _get_zval_ptr_ptr_cv_BP_VAR_RW(execute_data, opline->op1.var TSRMLS_CC);
if (IS_CV == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
}
if (IS_CV == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(&EG(uninitialized_zval));
EX_T(opline->result.var).var.ptr = &EG(uninitialized_zval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
&& Z_OBJ_HANDLER_PP(var_ptr, get)
&& Z_OBJ_HANDLER_PP(var_ptr, set)) {
/* proxy object */
zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
Z_ADDREF_P(val);
fast_increment_function(val);
Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
zval_ptr_dtor(&val);
} else {
fast_increment_function(*var_ptr);
}
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(*var_ptr);
EX_T(opline->result.var).var.ptr = *var_ptr;
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
fast_increment_function函數實際調用的是increment_function函數:
ZEND_API int increment_function(zval op1)
{
switch (Z_TYPE_P(op1)) {
case IS_LONG:
if (Z_LVAL_P(op1) == LONG_MAX) {
/* switch to double */
double d = (double)Z_LVAL_P(op1);
ZVAL_DOUBLE(op1, d+);
} else {
Z_LVAL_P(op1)++;
}
break;
case IS_DOUBLE:
Z_DVAL_P(op1) = Z_DVAL_P(op1) + ;
break;
case IS_NULL:
ZVAL_LONG(op1, );
break;
case IS_STRING: {
long lval;
double dval;
switch (is_numeric_string(Z_STRVAL_P(op1), Z_STRLEN_P(op1), &lval, &dval, )) {
case IS_LONG:
str_efree(Z_STRVAL_P(op1));
if (lval == LONG_MAX) {
/* switch to double */
double d = (double)lval;
ZVAL_DOUBLE(op1, d+);
} else {
ZVAL_LONG(op1, lval+);
}
break;
case IS_DOUBLE:
str_efree(Z_STRVAL_P(op1));
ZVAL_DOUBLE(op1, dval+);
break;
default:
/* Perl style string increment */
increment_string(op1);
break;
}
}
break;
case IS_OBJECT:
if (Z_OBJ_HANDLER_P(op1, do_operation)) {
zval *op2;
int res;
TSRMLS_FETCH();
MAKE_STD_ZVAL(op2);
ZVAL_LONG(op2, );
res = Z_OBJ_HANDLER_P(op1, do_operation)(ZEND_ADD, op1, op1, op2 TSRMLS_CC);
zval_ptr_dtor(&op2);
return res;
}
return FAILURE;
default:
return FAILURE;
}
return SUCCESS;
}
首先調用_get_zval_ptr_ptr_cv_BP_VAR_RW函數擷取CV(Compiled variable)類型變量;
其次會調用fast_increment_function函數,再調用increment_function函數,實作變量的增加操作,
在increment_function函數中,會根據變量的類型來進行對應的操作,從上面可以看出,依次判斷的類型有IS_LONG/IS_DOUBLE/IS_NULL/IS_STRING/IS_OBJECT。
如果是IS_LONG類型,若變量達到long的最大值,則将其轉化為double類型後加1,否則直接加1;
如果是IS_DOUBLE類型,則直接加1;
如果是IS_NULL類型,則會調用宏ZVAL_LONG(op1, 1),直接傳回long類型的1;
如果是IS_STRING類型,則會先将其轉化為數字類型,然後再根據上面判斷數字類型的邏輯判斷;
如果是IS_OBJECT類型,并且其内部定義了運算符操作的實作,那就調用這個handler來處理,進行
簡言之,字首自增實際上操作的是變量本身,在表達式中使用的也是變量本身。
對于字尾自增($a++),包含的opcode為POST_INC,其最終調用的是Zend/zend_vm_execute.h檔案中的ZEND_POST_INC_SPEC_CV_HANDLER函數,源碼如下:
static int ZEND_FASTCALL ZEND_POST_INC_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval **var_ptr, *retval;
SAVE_OPLINE();
var_ptr = _get_zval_ptr_ptr_cv_BP_VAR_RW(execute_data, opline->op1.var TSRMLS_CC);
if (IS_CV == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
}
if (IS_CV == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
ZVAL_NULL(&EX_T(opline->result.var).tmp_var);
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
retval = &EX_T(opline->result.var).tmp_var;
ZVAL_COPY_VALUE(retval, *var_ptr);
zendi_zval_copy_ctor(*retval);
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
&& Z_OBJ_HANDLER_PP(var_ptr, get)
&& Z_OBJ_HANDLER_PP(var_ptr, set)) {
/* proxy object */
zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
Z_ADDREF_P(val);
fast_increment_function(val);
Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
zval_ptr_dtor(&val);
} else {
fast_increment_function(*var_ptr);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
從上面可以看出,字尾自增與字首自增在底層實作的大部分是類似的,但是不同的點在于,字尾自增多了一個臨時變量,用于存儲原始的變量的值,但是它并沒有字首自增的RETURN_VALUE_USED操作。
簡言之,字尾自增使用的是存放在臨時變量中的值,即變量的原始值,而最終變量本身的值還是會增加。
一些奇怪的php自增和自減
$a = '2D9'; $a++; //string E
$a = '2E0'; $a++; //float
$a = '010'; $a++; //
$a = ; $a++; //
第一個很好了解。
對于第二個,并不是我們想的 ‘2E1’,而是3。注意輸出結果為 float 3,原來這裡
$
a=’2E0’,對
$
a執行遞增,由于字元串
$
a中包含’E’,是以會被當作float來取值。科學計數法 2E0 表示 2*10^0 值為2,對其加1則結果為3。
對于第三個,
$
a=’010’,對
$
a執行遞增,字元串
$
a會被當作integer來處理,即為10,對其加1則結果為11。
對于第四個,
$
a=010,對
$
a執行遞增,
$
a本來就是數字類型,由于是0開頭,表示8進制,即為8,對其加1則結果為9。
Happy coding.
轉載請注明出處: @CSU-Max http://blog.csdn.net/csu_max