第七节 数据类型转换
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;
}这个函数主要作为一个代理方法, 具体的转换规则由各个类型的处理函数处理, 不管是自动还是强制类型转换,最终都会调用这些内部转换方法.