explode和implode函数主要用作字符串-数组的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出。在PHP中经常会用到这两个函数,因此有必要了解一下其原理。
<p>array explode ( string $delimiter, string $string, [ , $limit ] )</p>
返回由字符串组成的数组,每个元素都是string的一个子串,被字符串$delimiter作为边界点分割出来。
如果设置了$limit,且为正数,则返回的数组最多包含$limit个元素,最后的那个元素将包含$string的剩余部分。
如果$limit是负数,则返回除了最后的-$limit个元素外的所有元素。
如果$limit是0,则会被当做1。
如果$delimiter为空,则函数返回FALSE。如果delimiter不在string中,且$limit为负数,则返回空数组。
<span>//</span><span> 如果delimiter为空字符串,返回FALSE</span> <span>if</span> (delim_len == <span>0</span><span>) { php_error_docref(NULL TSRMLS_CC, E_WARNING, </span><span>"</span><span>Empty delimiter</span><span>"</span><span>); RETURN_FALSE; } </span><span>//</span><span> 初始化返回的数组</span> <span> array_init(return_value); </span><span>if</span> (str_len == <span>0</span><span>) { </span><span>if</span> (limit >= <span>0</span><span>) { </span><span>//</span><span> 如果字符串为空且limit大于等于0,则返回一个包含空字符串的数组,注意此处sizeof("") == 1</span> add_next_index_stringl(return_value, <span>""</span>, <span>sizeof</span>(<span>""</span>) - <span>1</span>, <span>1</span><span>); } </span><span>return</span><span>; } </span><span>//</span><span> 初始化zstr和zdelim的字符串变量</span> ZVAL_STRINGL(&zstr, str, str_len, <span>0</span><span>); ZVAL_STRINGL(</span>&zdelim, delim, delim_len, <span>0</span><span>); </span><span>if</span> (limit > <span>1</span><span>) { </span><span>//</span><span> limit大于1,limit默认是LONG_MAX</span> php_explode(&zdelim, &<span>zstr, return_value, limit); } </span><span>else</span> <span>if</span> (limit < <span>0</span><span>) { </span><span>//</span><span> limit 为负数</span> php_explode_negative_limit(&zdelim, &<span>zstr, return_value, limit); } </span><span>else</span><span> { </span><span>//</span><span> limit为0,被当作1处理,返回整个字符串,add_index_stringl函数将str追加到数组return_value中</span> add_index_stringl(return_value, <span>0</span>, str, str_len, <span>1</span><span>); }</span>
处理完特殊情况和初始化变量后,就调用php_explode/php_explode_negative_limit函数进行下一步处理。下面是php_explode函数的源码
<span> endp </span>= Z_STRVAL_P(str) +<span> Z_STRLEN_P(str); </span><span>//</span><span> p1指向字符串的开始</span> p1 =<span> Z_STRVAL_P(str); </span><span>//</span><span> p2指向第一个分隔符的位置 ,找出分隔符位置主要用的是php_memnstr函数</span> p2 =<span> php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp); </span><span>if</span> (p2 ==<span> NULL) { </span><span>//</span><span> p2为NULL表示找不到分隔符,直接返回整个字符串</span> add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), <span>1</span><span>); } </span><span>else</span><span> { </span><span>do</span><span> { </span><span>//</span><span> 将p1添加到return_value数组中 ,移动到下一个分隔符的位置</span> add_next_index_stringl(return_value, p1, p2 - p1, <span>1</span><span>); p1 </span>= p2 +<span> Z_STRLEN_P(delim); } </span><span>while</span> ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL && --limit > <span>1</span><span>); </span><span>//</span><span> 将最后一个值追加到return_value</span> <span>if</span> (p1 <=<span> endp) add_next_index_stringl(return_value, p1, endp</span>-p1, <span>1</span><span>); }</span>
实现时调用了add_next_index_stringl将得到的每个字符串添加到数组return_value中。add_next_index_string是此功能的核心函数。
ZEND_API <span>int</span> add_next_index_stringl(zval *arg, <span>const</span> <span>char</span> *str, <span>uint</span> length, <span>int</span><span> duplicate) { zval </span>*<span>tmp; MAKE_STD_ZVAL(tmp); ZVAL_STRINGL(tmp, str, length, duplicate); </span><span>return</span> zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp, <span>sizeof</span>(zval *<span>), NULL); }</span>
add_next_index_stringl函数调用zend_hash_next_index_insert函数将str插入到数组中。再看看php_explode_negative_limit函数的源码
<span> //</span><span> 如果delimiter不在string中,且limit为负数,什么都不做,返回空的array,p2为NULL表示delimiter不在string中</span> <span>if</span> (p2 ==<span> NULL) { </span><span>/*<br /> 如果limit <= -1,那么什么都不做,因此如果只有一个字符串,那么- 1 + (limit) <= 0<br /> 返回空数组</span><span>*/</span><span> } </span><span>else</span><span> { </span><span>int</span> allocated = EXPLODE_ALLOC_STEP, found = <span>0</span><span>; </span><span>long</span><span> i, to_return; </span><span>char</span> **positions = emalloc(allocated * <span>sizeof</span>(<span>char</span> *<span>)); </span><span>//</span><span> 第一个单词的位置</span> positions[found++] =<span> p1; </span><span>do</span><span> { </span><span>if</span> (found >=<span> allocated) { allocated </span>= found + EXPLODE_ALLOC_STEP;<span>/*</span><span> 保证有足够的内存空间 </span><span>*/</span><span> positions </span>= erealloc(positions, allocated*<span>sizeof</span>(<span>char</span> *<span>)); } </span><span>//</span><span> positions保存每个单词的起始位置</span> positions[found++] = p1 = p2 +<span> Z_STRLEN_P(delim); } </span><span>while</span> ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) !=<span> NULL); </span><span>//</span><span> to_return 是return_value的数量,其实等于found - |limit|</span> to_return = limit +<span> found; </span><span>/*</span><span> limit至少是-1,因此不需要边界检查:i永远小于found </span><span>*/</span> <span>for</span> (i = <span>0</span>;i < to_return;i++) { <span>/*</span><span> 这个检查是检查to_return大于0 </span><span>*/</span><span> add_next_index_stringl(return_value, positions[i], (positions[i</span>+<span>1</span>] - Z_STRLEN_P(delim)) -<span> positions[i], </span><span>1</span><span> ); } efree(positions); }</span>
php_explode_negative_limit也是跟php_implode类似的操作,找到分隔的字符串之后,调用add_next_index_string函数将limit + found个字符串添加到return_value数组中。
<p>string implode ( string $glue, array $pieces )</p> <p>string implode ( array $pieces )</p>
将一个一维数组的值转换为字符串
implode函数可以接收两种参数顺序。
<span>if</span> (arg2 ==<span> NULL) { </span><span>//</span><span> 第二个参数为空,第一个参数必须为数组</span> <span>if</span> (Z_TYPE_PP(arg1) !=<span> IS_ARRAY) { php_error_docref(NULL TSRMLS_CC, E_WARNING, </span><span>"</span><span>Argument must be an array</span><span>"</span><span>); </span><span>return</span><span>; } MAKE_STD_ZVAL(delim); </span><span>#define</span> _IMPL_EMPTY "" <span>//</span><span> 默认使用""连接</span> ZVAL_STRINGL(delim, _IMPL_EMPTY, <span>sizeof</span>(_IMPL_EMPTY) - <span>1</span>, <span>0</span><span>); SEPARATE_ZVAL(arg1); arr </span>= *<span>arg1; } </span><span>else</span><span> { </span><span>//</span><span> 根据参数类型设置参数的值</span> <span>if</span> (Z_TYPE_PP(arg1) ==<span> IS_ARRAY) { arr </span>= *<span>arg1; convert_to_string_ex(arg2); delim </span>= *<span>arg2; } </span><span>else</span> <span>if</span> (Z_TYPE_PP(arg2) ==<span> IS_ARRAY) { arr </span>= *<span>arg2; convert_to_string_ex(arg1); delim </span>= *<span>arg1; } </span><span>else</span><span> { php_error_docref(NULL TSRMLS_CC, E_WARNING, </span><span>"</span><span>Invalid arguments passed</span><span>"</span><span>); </span><span>return</span><span>; } } </span><span>//</span><span> 调用php_implode函数进行转换</span> php_implode(delim, arr, return_value TSRMLS_CC);
在底层实现中,implode函数处理好参数之后就调用php_implode函数进行转换。
<span> //</span><span> 遍历数组的每一个元素,判断其类型,然后调用smart_str_appendl函数将值追加到字符串中</span> <span>while</span> (zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (<span>void</span> **) &tmp, &pos) ==<span> SUCCESS) { </span><span>switch</span> ((*tmp)-><span>type) { </span><span>case</span><span> IS_STRING: smart_str_appendl(</span>&<span>implstr, Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); </span><span>break</span><span>; </span><span>case</span><span> IS_LONG: { </span><span>char</span> stmp[MAX_LENGTH_OF_LONG + <span>1</span><span>]; str_len </span>= slprintf(stmp, <span>sizeof</span>(stmp), <span>"</span><span>%ld</span><span>"</span><span>, Z_LVAL_PP(tmp)); smart_str_appendl(</span>&<span>implstr, stmp, str_len); } </span><span>break</span><span>; </span><span>case</span><span> IS_BOOL: </span><span>if</span> (Z_LVAL_PP(tmp) == <span>1</span><span>) { smart_str_appendl(</span>&implstr, <span>"</span><span>1</span><span>"</span>, <span>sizeof</span>(<span>"</span><span>1</span><span>"</span>)-<span>1</span><span>); } </span><span>break</span><span>; </span><span>case</span><span> IS_NULL: </span><span>break</span><span>; </span><span>case</span><span> IS_DOUBLE: { </span><span>char</span> *<span>stmp; str_len </span>= spprintf(&stmp, <span>0</span>, <span>"</span><span>%.*G</span><span>"</span>, (<span>int</span><span>) EG(precision), Z_DVAL_PP(tmp)); smart_str_appendl(</span>&<span>implstr, stmp, str_len); efree(stmp); } </span><span>break</span><span>; </span><span>case</span><span> IS_OBJECT: { </span><span>int</span><span> copy; zval expr; zend_make_printable_zval(</span>*tmp, &expr, &<span>copy); smart_str_appendl(</span>&<span>implstr, Z_STRVAL(expr), Z_STRLEN(expr)); </span><span>if</span><span> (copy) { zval_dtor(</span>&<span>expr); } } </span><span>break</span><span>; </span><span>default</span><span>: tmp_val </span>= **<span>tmp; zval_copy_ctor(</span>&<span>tmp_val); convert_to_string(</span>&<span>tmp_val); smart_str_appendl(</span>&<span>implstr, Z_STRVAL(tmp_val), Z_STRLEN(tmp_val)); zval_dtor(</span>&<span>tmp_val); </span><span>break</span><span>; } </span><span>//</span><span> 添加glue字符</span> <span>if</span> (++i !=<span> numelems) { smart_str_appendl(</span>&<span>implstr, Z_STRVAL_P(delim), Z_STRLEN_P(delim)); } zend_hash_move_forward_ex(Z_ARRVAL_P(arr), </span>&<span>pos); } </span><span>//</span><span> 在尾部添加字符0</span> smart_str_0(&implstr);
可以看到,php_implode函数遍历数组的每一个元素,判断其类型,并进行必要的类型转换,然后调用smart_str_appendl函数将值追加到字符串中。smart_str_appendl是implode实现代码中的核心函数。
<span>#define</span> smart_str_appendl(dest, src, len) \<span> smart_str_appendl_ex((dest), (src), (len), </span><span>0</span><span>) </span><span>#define</span> smart_str_appendl_ex(dest, src, nlen, what) do { \<span> register size_t __nl; \ smart_str </span>*__dest = (smart_str *<span>) (dest); \ \ smart_str_alloc4(__dest, (nlen), (what), __nl); \ memcpy(__dest</span>->c + __dest-><span>len, (src), (nlen)); \ __dest</span>->len =<span> __nl; \ } </span><span>while</span> (<span>0</span>)
smart_str_appendl_ex主要调用memcpy函数进行字符串复制。
原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
如果本文对你有帮助,点个推荐吧,谢谢^_^。
更多PHP源码阅读文章:
[PHP源码阅读]strlen函数
[PHP源码阅读]strpos、strstr和stripos、stristr函数