【从源码角度看php自增和自减】

自增和自减基础

学过编程语言的同学应该都可以随口说出 ++a 和 a++ 的区别,具体的区别如下:

ExampleNameEffect
++$aPre-incrementIncrements abyone,thenreturns a.
$a++Post-incrementReturns a,thenincrements a by one.
$aPre-decrementDecrements abyone,thenreturns a.
$a–Post-decrementReturns a,thendecrements a by one.

++a 表示取 a 的地址,增加内存中 a 的值,然后把值放在寄存器中。
a++ 表示取 a 的地址,把 a 的值装入寄存器中,然后增加内存中 a 的值。

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 2D                               
$a = '2C';	$a--;  //string 2C                               

从上面可以看出,在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+1);
            } else {
            Z_LVAL_P(op1)++;
            }
            break;
        case IS_DOUBLE:
            Z_DVAL_P(op1) = Z_DVAL_P(op1) + 1;
            break;
        case IS_NULL:
            ZVAL_LONG(op1, 1);
            break;
        case IS_STRING: {
                long lval;
                double dval;

            switch (is_numeric_string(Z_STRVAL_P(op1), Z_STRLEN_P(op1), &lval, &dval, 0)) {
                    case IS_LONG:
                        str_efree(Z_STRVAL_P(op1));
                        if (lval == LONG_MAX) {
                            /* switch to double */
                            double d = (double)lval;
                            ZVAL_DOUBLE(op1, d+1);
                        } else {
                            ZVAL_LONG(op1, lval+1);
                        }
                        break;
                    case IS_DOUBLE:
                        str_efree(Z_STRVAL_P(op1));
                        ZVAL_DOUBLE(op1, dval+1);
                        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, 1);
            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 2E0
$a = '2E0';	$a++;  //float 3
$a = '010';	$a++;  //11
$a = 010;	$a++;  //9

第一个很好理解。
对于第二个,并不是我们想的 ‘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


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Android 中,ImageView 是一个常用的控件,通常用于显示图片。其中的 setVisible 方法是用来设置 ImageView 是否可见的方法。下面从源码角度分析一下这个方法的实现。 ImageView 的 setVisible 方法实际上是从 View 类继承而来的,其源码如下: ``` @Override public void setVisible(boolean visible, boolean restart) { super.setVisible(visible, restart); mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY; } ``` 在这个方法中,首先调用了 View 类的 setVisible 方法,而该方法中的实现主要是设置 View 的 visibility 属性。而 ImageView 这个子类中,visibility 属性的设置方法是 setVisibility 方法。因此,如果想要设置 ImageView 的可见性,实际上应该调用的是 setVisibility 方法。 setVisible 方法的第二个参数 restart 表示是否需要重绘 View。在 ImageView 中,如果需要重绘,则会调用 ImageView 的 invalidate 方法,使其重绘。而在 invalidate 方法中,会标记 View 的状态为需要重绘,这个标记的状态就是 mPrivateFlags 变量中的 PFLAG_DRAWABLE_STATE_DIRTY 标记。因此,在 setVisible 方法中,还需要将这个标记清除,以便在下一次需要重绘时,重新标记。 综上所述,ImageView 的 setVisible 方法主要是继承自 View 类,并调用了其父类的 setVisible 方法,同时还需要清除重绘标记。如果想要设置 ImageView 的可见性,应该调用 setVisibility 方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值