zoukankan      html  css  js  c++  java
  • define常量

    看手册说define定义的常量只允许:

    仅允许标量和 null。标量的类型是 integer, float,string 或者 boolean。 也能够定义常量值的类型为 resource ,但并不推荐这么做,可能会导致未知状况的发生。

    今天阅读php源码,发现define的第二个参数其实也可以是一个对象。

    先贴一段示例:

    class A {
        public function __toString() {
            return 'bar';
        }
    }
    $a = new A();
    define('foo', $a);
    echo foo;
    // 输出bar

    接着来看看php中的define究竟是如何实现的:

    ZEND_FUNCTION(define)
    {
        char *name;
        int name_len;
        zval *val;
        zval *val_free = NULL;
        zend_bool non_cs = 0;
        int case_sensitive = CONST_CS;
        zend_constant c;
    
        // 接收3个参数,string,zval,bool
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) {
            return;
        }
    
        // 是否大小写敏感
        if(non_cs) {
            case_sensitive = 0;
        }
    
        // 如果define类常量,则报错
        if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) {
            zend_error(E_WARNING, "Class constants cannot be defined or redefined");
            RETURN_FALSE;
        }
    
        // 获取真正的值,用val保存
    repeat:
        switch (Z_TYPE_P(val)) {
            case IS_LONG:
            case IS_DOUBLE:
            case IS_STRING:
            case IS_BOOL:
            case IS_RESOURCE:
            case IS_NULL:
                break;
            case IS_OBJECT:
                if (!val_free) {
                    if (Z_OBJ_HT_P(val)->get) {
                        val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC);
                        goto repeat;
                    } else if (Z_OBJ_HT_P(val)->cast_object) {
                        ALLOC_INIT_ZVAL(val_free);
                        if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) {
                            val = val_free;
                            break;
                        }
                    }
                }
                /* no break */
            default:
                zend_error(E_WARNING,"Constants may only evaluate to scalar values");
                if (val_free) {
                    zval_ptr_dtor(&val_free);
                }
                RETURN_FALSE;
        }
        
        // 构建常量
        c.value = *val;
        zval_copy_ctor(&c.value);
        if (val_free) {
            zval_ptr_dtor(&val_free);
        }
        c.flags = case_sensitive; /* non persistent */                 // 如果大小写不敏感,则为0,敏感则为1
        c.name = zend_strndup(name, name_len);
        c.name_len = name_len+1;
        c.module_number = PHP_USER_CONSTANT;                           // 标注非内核常量,而是用户定义的常量
        
        // 注册常量
        if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) {
            RETURN_TRUE;
        } else {
            RETURN_FALSE;
        }
    }

    注意以repeat开始的一段循环,还用到了goto语句T_T

    这段代码的作用为:

    • 对于int,float,string,bool,resource,null,则实际定义的常量时直接使用这些值
    • 对于object,则需要将object转成上述6个类型之一(如果转型之后依然是object,则继续转型)

    如何将object成6个类型之一呢?从代码上看有2种手段:

    if (Z_OBJ_HT_P(val)->get) {
        val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC);
        goto repeat;
    }
    // __toString()方法会在cast_object中被调用
    else if (Z_OBJ_HT_P(val)->cast_object) { ALLOC_INIT_ZVAL(val_free); if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) { val = val_free; break; } }

    1,Z_OBJ_HT_P(val)->get ,宏展开之后为(*val).value.obj.handlers->get

    2,Z_OBJ_HT_P(val)->cast_object,宏展开之后为(*val).value.obj.handlers->cast_object

    handlers是一个包含很多函数指针的结构体,具体定义参见_zend_object_handlers 。该结构体中的函数指针均用于操作object,比如读取/修改对象属性、获取/调用对象方法等等...get和cast_object也是其中之一。

    对于一般的对象,php提供了标准的cast_object函数zend_std_cast_object_tostring,代码位于php-src/zend/zend-object-handlers.c中:

    ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */
    {
        zval *retval;
        zend_class_entry *ce;
    
        switch (type) {
            case IS_STRING:
                ce = Z_OBJCE_P(readobj);
                
                // 如果用户的class中定义了__toString,则尝试调用
                if (ce->__tostring &&
                    (zend_call_method_with_0_params(&readobj, ce, &ce->__tostring, "__tostring", &retval) || EG(exception))) {
                    ……
                    
                }
                return FAILURE;
            ……
        }
        return FAILURE;
    }

    从上述具体实现来看,默认的cast_object就是去寻找class中的__tostring方法然后调用...

    回到刚开始的例子,define('foo', $a) ,由于$a是A的实例,并且class A中定义了__toString,因此实际上foo常量就等于toString的返回值bar。

    ps:继续挖掘一点小细节.

    define有返回值

    通常我们定义常量直接写成:define('foo', 123); 不过从define的实现上来看,它是有返回值的。根据手册上的描述:

    成功时返回 TRUE, 或者在失败时返回 FALSE。

    什么情况下define会失败呢?举个例子:

    define('PHP_INT_MAX', 1);         // 返回FALSE
    
    define('FOO', 1);                 // 返回TRUE
    define('FOO', 2);                 // 返回FALSE

    上面代码包含了两种情况,一是我们尝试重新定义php内核的预定义常量,比如PHP_INT_MAX,这显然会失败。第二种情况是我们曾经在代码的某个位置定义过了一个常量FOO,然后又在接下来的程序中再次定义它,这也会造成失败。因此,在编码时最好将所有需要定义的常量写在一起,以免造成name重复。

    常量名没有限制

    再次回顾一下define的实现,其中仅仅判断name是否为XXX::YYY这种形式。

    换句话说,define几乎对其name不做任何要求,当然也不需要name是一个合法的php变量名。因此,我们可以让define的常量取一些稀奇古怪的名称。例如:

    define('>_<', 123);    // 返回TRUE
    echo >_<;              // syntax error

    不过如果定义了这样的常量,是没法直接使用的,会报语法错误。正确的使用方法如下:

    define('>_<', 123);        // 返回TRUE
    echo constant('>_<');      // 输出123
    知识共享许可协议
    本作品由driftcloudy创作,采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。欢迎自行转载,发布,演绎,但必须保留本文作者署名driftcloudy(包含链接http://www.cnblogs.com/driftcloudy),且不得用于商业目的。
  • 相关阅读:
    map按照值排序
    结构体的三种排序方式
    归并排序
    数组模拟双向链表
    二刷斐波那契高精度
    2019年9月训练(贰)区间DP (luogu 4290)
    2019年9月训练(壹)数位DP (HDU 2089)
    2019年8月训练(贰)
    2019年8月训练(壹)二分,三分
    2019年7月训练(柒)
  • 原文地址:https://www.cnblogs.com/driftcloudy/p/3158272.html
Copyright © 2011-2022 走看看