首页 > 编程 > PHP > 正文

[PHP源码阅读]explode和implode函数

2020-03-22 20:27:56
字体:
来源:转载
供稿:网友
  • explode和implode函数主要用作字符串-数组的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出。在PHP中经常会用到这两个函数,因此有必要了解一下其原理。

    explode

    array explode ( string $delimiter, string $string, [ , $limit ] )

    返回由字符串组成的数组,每个元素都是string的一个子串,被字符串$delimiter作为边界点分割出来。

    $limit

    如果设置了$limit,且为正数,则返回的数组最多包含$limit个元素,最后的那个元素将包含$string的剩余部分。

    如果$limit是负数,则返回除了最后的-$limit个元素外的所有元素。

    如果$limit是0,则会被当做1。

    $delimiter

    如果$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_html' target='_blank'>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函数的源码

    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函数的源码

    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数组中。

    implode

    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函数进行转换。

    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, &copy);                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实现代码中的核心函数。

    smart_str_appendl
    #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函数进行字符串复制。

    原创文章 http://www.cnblogs.com/h-hq/p/5509090.html

    PHP编程

    郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

  • 发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表