PHP是弱類型的動態語言,在前面的章節中我們已經介紹了PHP的變量都存放在一個名為ZVAL的容器中,ZVAL包含了變量的類型和各種類型變量的值。PHP中的變量不需要顯式的數據類型定義,可以給變量賦值任意類型的數據,PHP變量之間的數據類型轉換有兩種: 隱式和顯式轉換。
## 隱式類型轉換[]()
隱式類型轉換也被稱為自動類型轉換,是指不需要程序員書寫代碼,由編程語言自動完成的類型轉換。在PHP中,我們經常遇到的隱式轉換有:
1.**直接的變量賦值操作**
在PHP中,直接對變量的賦值操作是隱式類型轉換最簡單的方式,也是我們最常見的一種方式,或許我們已經習以為常,從而沒有感覺到變量的變化。在直接賦值的操作中,變量的數據類型由賦予的值決定,即左值的數據類型由右值的數據類型決定。比如,當把一個字符串類型的數據賦值給變量時,不管該變量以前是什么類型的變量,此時該變量就是一個字符串類型的變量。看一段代碼:
$string = "To love someone sincerely means to love all the people, to love the world and life, too.";
$integer = 10;
$string = $integer;
上面的代碼,當執行完第三行代碼,$string變量的類型就是一個整形了。通過VLD擴展可以查到第三次賦值操作的中間代碼及操作數的類型,再找到賦值的最后實現為**zend_assign_to_variable**函數。這在前面的小節中已經詳細介紹過了。我們這個例子是很簡單的一種賦值,在源碼中是直接將$string的ZVAL容器的指針指向$integer變量指向的指針,并將$integer的引用計數加1。這個操作在本質上改變了$string變量的內容,而原有的變量內容則被垃圾收集機制回收。關于賦值的具體細節,請返回[上一節(變量的賦值和銷毀)](#)查看。
2.**運算式結果對變量的賦值操作**我們常說的隱式類型轉換是將一個表達式的結果賦值給一個變量,在運算的過程中發生了隱式的類型轉換。這種類型轉換不僅僅在PHP語言,在其它眾多的語言中也有見到,這是我們常規意義上的隱式類型轉換。這種類型轉換又分為兩種情況:
- 表達式的操作數為同一數據類型 這種情況的作用以上面的直接變量的類型轉換是同一種情況,只是此時右值變成了表達式的運算結果。
- 表達式的操作數不為同的數據類型 這種情況的類型轉換發生在表達式的運算符的計算過程中,在源碼中也就是發生在運行符的實現過程中。
看一個字符串和整數的隱式數據類型轉換:
<?php
$a = 10;
$b = 'a string ';
?
[echo](http://www.php.net/echo) $a . $b;
上面例子中字符串連接操作就存在自動數據類型轉化,$a變量是數值類型,$b變量是字符串類型,這里$a變量就是隱式(自動)的轉換為字符串類型了。通常自動數據類型轉換發生在特定的操作上下文中,類似的還有求和操作"+"。具體的自動類型轉換方式和特定的操作有關。下面就以字符串連接操作為例說明隱式轉換的實現:
腳本執行的時候字符串的連接操作是通過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:
// ...省略
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:
// ... 省略
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](http://www.php.net/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,這只是一個類型的變化,而后者是將這個變量釋放,釋放后當前作用域內該變量就不存在了。
除了上面提到的與C語言很像,在其它語言中也經常見到的強制數據轉換,PHP中有一個極具PHP特色的強制類型轉換。PHP的標準擴展中提供了兩個有用的方法settype()以及gettype()方法,前者可以動態的改變變量的數據類型,gettype()方法則是返回變量的數據類型。在ext/standard/type.c文件中找到settype的實現源碼:
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;
}
這個極具PHP特色的強制類型轉換就是這個函數,而這個函數是作為一個代理方法存在,具體的轉換規則由各個類型的處理函數處理,不管是自動還是強制類型轉換,最終都會調用這些內部轉換方法,這和前面的強制類型轉換在本質上是一樣的。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊