explode和implode函數主要用作字符串-數組的操作,比如獲取一段參數後根據某個字符分割字符串,或者將一個數組的結果使用一個字符合並成一個字符串輸出。在PHP中經常會用到這兩個函數,因此有必要了解一下其原理。
array explode ( string $delimiter, string $string, [ , $limit ] )
返回由字符串組成的數組,每個元素都是string的一個子串,被字符串$delimiter作為邊界點分割出來。
如果設置了$limit,且為正數,則返回的數組最多包含$limit個元素,最後的那個元素將包含$string的剩余部分。
如果$limit是負數,則返回除了最後的-$limit個元素外的所有元素。
如果$limit是0,則會被當做1。
如果$delimiter為空,則函數返回FALSE。如果delimiter不在string中,且$limit為負數,則返回空數組。
// 如果delimiter為空字符串,返回FALSE
if (delim_len == 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter");
RETURN_FALSE;
}
// 初始化返回的數組
array_init(return_value);
if (str_len == 0) {
if (limit >= 0) {
// 如果字符串為空且limit大於等於0,則返回一個包含空字符串的數組,注意此處sizeof("") == 1
add_next_index_stringl(return_value, "", sizeof("") - 1, 1);
}
return;
}
// 初始化zstr和zdelim的字符串變量
ZVAL_STRINGL(&zstr, str, str_len, 0);
ZVAL_STRINGL(&zdelim, delim, delim_len, 0);
if (limit > 1) {
// limit大於1,limit默認是LONG_MAX
php_explode(&zdelim, &zstr, return_value, limit);
} else if (limit < 0) {
// limit 為負數
php_explode_negative_limit(&zdelim, &zstr, return_value, limit);
} else {
// limit為0,被當作1處理,返回整個字符串,add_index_stringl函數將str追加到數組return_value中
add_index_stringl(return_value, 0, str, str_len, 1);
}
處理完特殊情況和初始化變量後,就調用php_explode/php_explode_negative_limit函數進行下一步處理。下面是php_explode函數的源碼
endp = Z_STRVAL_P(str) + Z_STRLEN_P(str);
// p1指向字符串的開始
p1 = Z_STRVAL_P(str);
// p2指向第一個分隔符的位置 ,找出分隔符位置主要用的是php_memnstr函數
p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);
if (p2 == NULL) {
// p2為NULL表示找不到分隔符,直接返回整個字符串
add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), 1);
} else {
do {
// 將p1添加到return_value數組中 ,移動到下一個分隔符的位置
add_next_index_stringl(return_value, p1, p2 - p1, 1);
p1 = p2 + Z_STRLEN_P(delim);
} while ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL &&
--limit > 1);
// 將最後一個值追加到return_value
if (p1 <= endp)
add_next_index_stringl(return_value, p1, endp-p1, 1);
}
實現時調用了add_next_index_stringl將得到的每個字符串添加到數組return_value中。add_next_index_string是此功能的核心函數。
ZEND_API int add_next_index_stringl(zval *arg, const char *str, uint length, int duplicate)
{
zval *tmp;
MAKE_STD_ZVAL(tmp);
ZVAL_STRINGL(tmp, str, length, duplicate);
return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp, sizeof(zval *), NULL);
}
add_next_index_stringl函數調用zend_hash_next_index_insert函數將str插入到數組中。再看看php_explode_negative_limit函數的源碼
// 如果delimiter不在string中,且limit為負數,什麼都不做,返回空的array,p2為NULL表示delimiter不在string中
if (p2 == NULL) {
/*
如果limit <= -1,那麼什麼都不做,因此如果只有一個字符串,那麼- 1 + (limit) <= 0
返回空數組*/
} else {
int allocated = EXPLODE_ALLOC_STEP, found = 0;
long i, to_return;
char **positions = emalloc(allocated * sizeof(char *));
// 第一個單詞的位置
positions[found++] = p1;
do {
if (found >= allocated) {
allocated = found + EXPLODE_ALLOC_STEP;/* 保證有足夠的內存空間 */
positions = erealloc(positions, allocated*sizeof(char *));
}
// positions保存每個單詞的起始位置
positions[found++] = p1 = p2 + Z_STRLEN_P(delim);
} while ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL);
// to_return 是return_value的數量,其實等於found - |limit|
to_return = limit + found;
/* limit至少是-1,因此不需要邊界檢查:i永遠小於found */
for (i = 0;i < to_return;i++) { /* 這個檢查是檢查to_return大於0 */
add_next_index_stringl(return_value, positions[i],
(positions[i+1] - Z_STRLEN_P(delim)) - positions[i],
1
);
}
efree(positions);
}
php_explode_negative_limit也是跟php_implode類似的操作,找到分隔的字符串之後,調用add_next_index_string函數將limit + found個字符串添加到return_value數組中。
string implode ( string $glue, array $pieces )
string implode ( array $pieces )
將一個一維數組的值轉換為字符串
implode函數可以接收兩種參數順序。
if (arg2 == NULL) {
// 第二個參數為空,第一個參數必須為數組
if (Z_TYPE_PP(arg1) != IS_ARRAY) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument must be an array");
return;
}
MAKE_STD_ZVAL(delim);
#define _IMPL_EMPTY ""
// 默認使用""連接
ZVAL_STRINGL(delim, _IMPL_EMPTY, sizeof(_IMPL_EMPTY) - 1, 0);
SEPARATE_ZVAL(arg1);
arr = *arg1;
} else {
// 根據參數類型設置參數的值
if (Z_TYPE_PP(arg1) == IS_ARRAY) {
arr = *arg1;
convert_to_string_ex(arg2);
delim = *arg2;
} else if (Z_TYPE_PP(arg2) == IS_ARRAY) {
arr = *arg2;
convert_to_string_ex(arg1);
delim = *arg1;
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid arguments passed");
return;
}
}
// 調用php_implode函數進行轉換
php_implode(delim, arr, return_value TSRMLS_CC);
在底層實現中,implode函數處理好參數之後就調用php_implode函數進行轉換。
// 遍歷數組的每一個元素,判斷其類型,然後調用smart_str_appendl函數將值追加到字符串中
while (zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **) &tmp, &pos) == SUCCESS) {
switch ((*tmp)->type) {
case IS_STRING:
smart_str_appendl(&implstr, Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
break;
case IS_LONG: {
char stmp[MAX_LENGTH_OF_LONG + 1];
str_len = slprintf(stmp, sizeof(stmp), "%ld", Z_LVAL_PP(tmp));
smart_str_appendl(&implstr, stmp, str_len);
}
break;
case IS_BOOL:
if (Z_LVAL_PP(tmp) == 1) {
smart_str_appendl(&implstr, "1", sizeof("1")-1);
}
break;
case IS_NULL:
break;
case IS_DOUBLE: {
char *stmp;
str_len = spprintf(&stmp, 0, "%.*G", (int) EG(precision), Z_DVAL_PP(tmp));
smart_str_appendl(&implstr, stmp, str_len);
efree(stmp);
}
break;
case IS_OBJECT: {
int copy;
zval expr;
zend_make_printable_zval(*tmp, &expr, ©);
smart_str_appendl(&implstr, Z_STRVAL(expr), Z_STRLEN(expr));
if (copy) {
zval_dtor(&expr);
}
}
break;
default:
tmp_val = **tmp;
zval_copy_ctor(&tmp_val);
convert_to_string(&tmp_val);
smart_str_appendl(&implstr, Z_STRVAL(tmp_val), Z_STRLEN(tmp_val));
zval_dtor(&tmp_val);
break;
}
// 添加glue字符
if (++i != numelems) {
smart_str_appendl(&implstr, Z_STRVAL_P(delim), Z_STRLEN_P(delim));
}
zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos);
}
// 在尾部添加字符0
smart_str_0(&implstr);
可以看到,php_implode函數遍歷數組的每一個元素,判斷其類型,並進行必要的類型轉換,然後調用smart_str_appendl函數將值追加到字符串中。smart_str_appendl是implode實現代碼中的核心函數。
#define smart_str_appendl(dest, src, len) \
smart_str_appendl_ex((dest), (src), (len), 0)
#define smart_str_appendl_ex(dest, src, nlen, what) do { \
register size_t __nl; \
smart_str *__dest = (smart_str *) (dest); \
\
smart_str_alloc4(__dest, (nlen), (what), __nl); \
memcpy(__dest->c + __dest->len, (src), (nlen)); \
__dest->len = __nl; \
} while (0)
smart_str_appendl_ex主要調用memcpy函數進行字符串復制。
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
如果本文對你有幫助,點個推薦吧,謝謝^_^。
更多PHP源碼閱讀文章:
[PHP源碼閱讀]strlen函數
[PHP源碼閱讀]strpos、strstr和stripos、stristr函數