一道關於PHP類型轉換的面試題

原文發表於個人博客 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知識邊界的一道好題目。

相關文章
相關標籤/搜索