深入理解PHP内核 第三章 第七节 数据类型转换

3/27/2011来源:PHP教程人气:5060

第七节 数据类型转换
php中的变量不需要显式的数据类型定义, 可以给变量赋值任意类型的数据, PHP之间的数据类型转换有两种: 显式和隐式转换.

隐式类型转换(自动类型转换)
PHP中隐式数据类型转换很常见, 例如:

<?php
$a = 10;
$b = 'a string ';
 
echo $a . $b;上面例子中字符串连接操作就存在自动数据类型转化, $a变量是数值类型, $b变量是字符串类型, 这里$b变量就是隐式(自动)的转换为字符串类型了. 通常自动数据类型转换发生在特定的操作上下文中, 类似的还有求和操作"+". 具体的自动类型转换方式和特定的操作有关. 下面就以字符串连接操作为例:

脚本执行的时候字符串的链接操作是通过Zend/zend_Operators.c文件中的如下函数进行:

ZEND_API int concat_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
{          
        zval op1_copy, op2_copy;
        int use_copy1 = 0, use_copy2 = 0;
 
        if (Z_TYPE_P(op1) != IS_STRING) {
                zend_make_PRintable_zval(op1, &op1_copy, &use_copy1);
        }          
        if (Z_TYPE_P(op2) != IS_STRING) {
                zend_make_printable_zval(op2, &op2_copy, &use_copy2);
        }      
        // 省略
}可用看出如果字符串链接的两个操作数如果不是字符串的话则调用zend_make_printable_zval函数将操作数转换为"printable_zval"也就是字符串.

ZEND_API void zend_make_printable_zval(zval *expr, zval *expr_copy, int *use_copy)
{
    if (Z_TYPE_P(expr)==IS_STRING) {
        *use_copy = 0;
        return;
    }
    switch (Z_TYPE_P(expr)) {
        case IS_NULL:
            Z_STRLEN_P(expr_copy) = 0;
            Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
            break;
        case IS_BOOL:
            if (Z_LVAL_P(expr)) {
                Z_STRLEN_P(expr_copy) = 1;
                Z_STRVAL_P(expr_copy) = estrndup("1", 1);
            } else {
                Z_STRLEN_P(expr_copy) = 0;
                Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
            }
            break;
        case IS_RESOURCE:
            Z_STRVAL_P(expr_copy) = (char *) emalloc(sizeof("Resource id #") - 1 + MAX_LENGTH_OF_LONG);
            Z_STRLEN_P(expr_copy) = sprintf(Z_STRVAL_P(expr_copy), "Resource id #%ld", Z_LVAL_P(expr));
            break;
        case IS_ARRAY:
            Z_STRLEN_P(expr_copy) = sizeof("Array") - 1;
            Z_STRVAL_P(expr_copy) = estrndup("Array", Z_STRLEN_P(expr_copy));
            break;
        case IS_OBJECT:
            {
                TSRMLS_FETCH();
 
                if (Z_OBJ_HANDLER_P(expr, cast_object) && Z_OBJ_HANDLER_P(expr, cast_object)(expr, expr_copy, IS_STRING TSRMLS_CC) == SUCCESS) {
                    break;
                }
                /* Standard PHP objects */
                if (Z_OBJ_HT_P(expr) == &std_object_handlers || !Z_OBJ_HANDLER_P(expr, cast_object)) {
                    if (zend_std_cast_object_tostring(expr, expr_copy, IS_STRING TSRMLS_CC) == SUCCESS) {
                        break;
                    }   
                }   
                if (!Z_OBJ_HANDLER_P(expr, cast_object) && Z_OBJ_HANDLER_P(expr, get)) {
                    zval *z = Z_OBJ_HANDLER_P(expr, get)(expr TSRMLS_CC);
 
                    Z_ADDREF_P(z);
                    if (Z_TYPE_P(z) != IS_OBJECT) {
                        zend_make_printable_zval(z, expr_copy, use_copy);
                        if (*use_copy) {
                            zval_ptr_dtor(&z);
                        } else {
                            ZVAL_ZVAL(expr_copy, z, 0, 1);
                            *use_copy = 1;
                        }
                        return;
                    }
                    zval_ptr_dtor(&z);
                }
                zend_error(EG(exception) ? E_ERROR : E_RECOVERABLE_ERROR, "Object of class %s could not be converted to string", Z_OBJCE_P(expr)->name);
                Z_STRLEN_P(expr_copy) = 0;
                Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
            }
            break;
        case IS_DOUBLE:
            *expr_copy = *expr;
            zval_copy_ctor(expr_copy);
            zend_locale_sprintf_double(expr_copy ZEND_FILE_LINE_CC);
            break;
        default:
            *expr_copy = *expr;
            zval_copy_ctor(expr_copy);
            convert_to_string(expr_copy);
            break;
    }
    Z_TYPE_P(expr_copy) = IS_STRING;
    *use_copy = 1;
}这个函数根据不同的变量类型来返回不同的字符串类型, 例如BOOL类型的数据返回0和1, 数组只是简单的返回Array等等, 类似其他类型的数据转换也是类型, 都是根据操作数的不同类型的转换为相应的目标类型.

显式类型转换(强制类型转换)
PHP中的强制类型转换和C中的非常像:

<?php
$double = 20.10;
echo (int)$double;PHP中允许的强制类型有:

(int), (integer) 转换为整型
(bool), (boolean) 转换为布尔类型
(float), (double) 转换为浮点类型
(string) 转换为字符串
(array) 转换为数组
(object) 转换为对象
(unset) 转换为NULL
在Zend/zend_operators.c中实现了转换为这些目标类型的实现函数convert_to_*系列函数, 读者自行查看这些函数即可, 这些数据类型转换类型中有unset类型转换:

ZEND_API void convert_to_null(zval *op) /* {{{ */
{
    if (Z_TYPE_P(op) == IS_OBJECT) {
        if (Z_OBJ_HT_P(op)->cast_object) {
            zval *org;
            TSRMLS_FETCH();
 
            ALLOC_ZVAL(org);
            *org = *op;
            if (Z_OBJ_HT_P(op)->cast_object(org, op, IS_NULL TSRMLS_CC) == SUCCESS) {
                zval_dtor(org);
                return;
            }
            *op = *org;
            FREE_ZVAL(org);
        }
    }
 
    zval_dtor(op);
    Z_TYPE_P(op) = IS_NULL;
}转换为NULL非常简单, 对变量进行析构操作,然后将数据类型设为IS_NULL即可. 可能读者会好奇(unset)$a和unset($a)这两者有没有关系,其实并没有关系,前者是将变量$a的类型变为NULL, 而后者是将这个变量释放, 释放后当前作用域内该变量及不存在了.

PHP的标准扩展中提供了两个有用的方法settype()以及gettype()方法, 前者可以动态的改变变量的数据类型, gettype()方法则是返回变量的数据类型.

PHP_FUNCTION(settype)
{
    zval **var;
    char *type;
    int type_len = 0;
 
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zs", &var, &type, &type_len) == FAILURE) {
        return;
    }
 
    if (!strcasecmp(type, "integer")) {
        convert_to_long(*var);
    } else if (!strcasecmp(type, "int")) {
        convert_to_long(*var);
    } else if (!strcasecmp(type, "float")) {
        convert_to_double(*var);
    } else if (!strcasecmp(type, "double")) { /* deprecated */
        convert_to_double(*var);
    } else if (!strcasecmp(type, "string")) {
        convert_to_string(*var);
    } else if (!strcasecmp(type, "array")) {
        convert_to_array(*var);
    } else if (!strcasecmp(type, "object")) {
        convert_to_object(*var);
    } else if (!strcasecmp(type, "bool")) {
        convert_to_boolean(*var);
    } else if (!strcasecmp(type, "boolean")) {
        convert_to_boolean(*var);
    } else if (!strcasecmp(type, "null")) {
        convert_to_null(*var);
    } else if (!strcasecmp(type, "resource")) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot convert to resource type");
        RETURN_FALSE;
    } else {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid type");
        RETURN_FALSE;
    }
    RETVAL_TRUE;
}这个函数主要作为一个代理方法, 具体的转换规则由各个类型的处理函数处理, 不管是自动还是强制类型转换,最终都会调用这些内部转换方法.