原文發表於個人博客 http://starlight36.com/post/php-type-convert php
最近在爲公司面試新人,常常會問到的一道題目就是PHP類型轉換的值,例如: 面試
var_dump((int)true); var_dump((string)true); var_dump((string)false); var_dump((bool)"1"); var_dump((bool)"0"); var_dump((bool)""); var_dump((bool)"false");
我印象中最先見到這道題目是在英極的PHP高級開發工程師崗位的筆試題裏面,看似很基礎,可是依然能夠難住很多PHPer。先來看一下運行結果: 數據結構
int(1) string(1) "1" string(0) "" bool(true) bool(false) bool(false) bool(true)
對於大多數人來講,第一、二、4行一般是沒有問題的。可是爲何false轉換爲字符串是空字符串呢?在處理請求值時,一般會傳一個字符串類型的false,可是「false」(字符串)並不是false(布爾),這有點使人疑惑了。 函數
爲何會這樣呢? post
關於這個問題,咱們從PHP內核入手,看看在類型轉換時系統內部到底發生了什麼。 ui
首先補充一些關於PHP弱類型實現方式的背景知識。PHP解釋器是使用C語言寫成的,固然最終對變量的處理,也會使用C語言構造數據結構來實現。在Zend引擎中,一個PHP變量對應的類型是zval。 code
打開Zend/zend_types.h文件,咱們能夠看到zval類型的定義,php-5.5.23版本大約在第55行左右: orm
typedef struct _zval_struct zval;
這樣咱們發現,zval實際上是一個名爲_zval_struct的結構體類型,咱們在Zend/zend.h文件中找到這個結構體的定義,大約在320行左右開始: 內存
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; } zvalue_value; struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; };
你們能夠看到,_zval_struct中包含兩個重要的成員,一個是zvalue_value類型的value,一個是zend_uchar類型的type。注意zvalue_value類型是一個聯合體,它用來存儲一個PHP變量的值的信息。(若是你忘記了什麼是聯合體,我來解釋一下。聯合體相似結構體,可是聯合體的中的成員,存儲時有且只能有一個,並且聯合體佔用的空間是聯合體中長度最長的那個成員,這樣作是爲了節省內存的使用。)在zvalue_value中,包括了long、double、struct、HashTable、zend_object_value五個類型的成員。他們分別用來存儲PHP變量不一樣類型的值: element
C類型 | PHP類型 |
long | bool |
int | |
resource | |
double | float |
struct | string |
HashTable | array |
zend_object_value | object |
看到這個結構體以後,想必也就明白了常問的諸如PHP中int類型的取值範圍,以及php中strlen的時間複雜度之類的問題。
因而可知,PHP的變量類型轉換,或者說是弱類型實現,本質上是實現zval類型在不一樣類型之間的轉換。除了完成zvalue_value的數值轉換,還須要將_zval_struct中的type設置成當前變量的type類型。在Zend引擎中實現了convert_to_*系列函數完成這一轉換,咱們在Zend/zend_operators.c中能夠看到這些轉換函數,在大約511行左右,能夠找到轉換爲布爾類型的函數:
ZEND_API void convert_to_boolean(zval *op) /* {{{ */ { int tmp; switch (Z_TYPE_P(op)) { case IS_BOOL: break; case IS_NULL: Z_LVAL_P(op) = 0; break; case IS_RESOURCE: { TSRMLS_FETCH(); zend_list_delete(Z_LVAL_P(op)); } /* break missing intentionally */ case IS_LONG: Z_LVAL_P(op) = (Z_LVAL_P(op) ? 1 : 0); break; case IS_DOUBLE: Z_LVAL_P(op) = (Z_DVAL_P(op) ? 1 : 0); break; case IS_STRING: { char *strval = Z_STRVAL_P(op); if (Z_STRLEN_P(op) == 0 || (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) { Z_LVAL_P(op) = 0; } else { Z_LVAL_P(op) = 1; } STR_FREE(strval); } break; case IS_ARRAY: tmp = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0); zval_dtor(op); Z_LVAL_P(op) = tmp; break; case IS_OBJECT: { zend_bool retval = 1; TSRMLS_FETCH(); convert_object_to_type(op, IS_BOOL, convert_to_boolean); if (Z_TYPE_P(op) == IS_BOOL) { return; } zval_dtor(op); ZVAL_BOOL(op, retval); break; } default: zval_dtor(op); Z_LVAL_P(op) = 0; break; } Z_TYPE_P(op) = IS_BOOL; } /* }}} */
case IS_STRING這段代碼便是將一個字符串類型變量轉換爲布爾型的操做。能夠看到,只有空字符串,或者字符串長度爲1,而且此字符爲0時,字符串的布爾值才爲1,也就是true,其餘爲0,也就是false。
一樣的,咱們也就明白了布爾值如何轉換爲字符串的,能夠從_convert_to_string函數的實現中瞭解。
看似簡單而且基礎的PHP問題,究其根源是對PHP實現機制的把握。我的以爲,這道題也不失爲鑑別PHPer知識邊界的一道好題目。