str_replace — 子字符串替换 [str_replace]
mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )
php函数str_replace: 返回一个字符串或者数组。该字符串或数组是将 subject 中全部的 search 都被 replace 替换之后的结果。
现在我们所能知道的一些这个函数的用法,如:str_replace("#", "-", "dizaz#7#final"),str_replace(array('#', '$'), "-", "dizaz#7$final") 等,就这些调用方式,php内部是如何实现的呢,鉴于[深入理解PHP内核],在这里小做分析。
测试代码:
<?php $object = "dizaz#7#final"; $res = str_replace("#", "-", $object); echo $res;
如上,先从字符“#”替换为字符“-”开始。
预备工作:
下载PHP源代码,http://www.php.net下载即可
打造自己的阅读代码的工具[本人使用VIM+CSCOPE] 另:Linux用户也推荐图形化查看源代码工具kscope [google之]
编译工具[gcc],调试工具[gdb],另:GDB图形化端口DDD也很不错,推荐
编译PHP源码,记得使用--enable-debug [当然也希望通过./configure --help 看看PHP提供的一些编译选项,会有很多收获的]
开始分析:
通过[深入理解PHP内核]阅读,我们不难发现其PHP提供标准函数所在目录为PHP-SOURCE-DIR/ext/standard目录下,由于是字符串函数,很容易我们就可以在此目录下找到str_replace函数实现的文件 string.c,接下来就围绕着这个文件进行分析。[当然用CScope很容易就可以锁定,用:cs find s str_replace]
查询得知其定义实现:
/* {{{ proto mixed str_replace(mixed search, mixed replace, mixed subject [, int &replace_count]) Replaces all occurrences of search in haystack with replace */ PHP_FUNCTION(str_replace) { php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */
现在需要查看函数php_str_replace_common函数
/* {{{ php_str_replace_common */ static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensitivity) { /** * TODO * typedef struct _zval_struct zval; * typedef struct _zend_class_entry zend_class_entry * * struct _zval_struct { * zvalue_value value; * zend_uint refcount__gc; * zend_uchar type; * zend_uchar is_ref__gc; * }; * * typedef union _zvalue_value { * long lval; * double dval; * struct { * char *val; * int len; * } str; * HashTable *ht; * zend_object_value obj; * } zvalue_value; * * typedef struct _zend_object { * zend_class_entry *ce; * HashTable *properties; * HashTable *guards; * } zend_object; * */ zval **subject, **search, **replace, **subject_entry, **zcount = NULL; zval *result; char *string_key; uint string_key_len; ulong num_key; int count = 0; int argc = ZEND_NUM_ARGS(); if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ZZZ|Z", &search, &replace, &subject, &zcount) == FAILURE) { return; } SEPARATE_ZVAL(search); SEPARATE_ZVAL(replace); SEPARATE_ZVAL(subject); /* Make sure we're dealing with strings and do the replacement. */ if (Z_TYPE_PP(search) != IS_ARRAY) { ....//代码省滤 } else { /* if subject is not an array */ php_str_replace_in_subject(*search, *replace, subject, return_value, case_sensitivity, (argc > 3) ? &count : NULL); } if (argc > 3) { zval_dtor(*zcount); ZVAL_LONG(*zcount, count); } } /* }}} */
继续跟踪php_str_replace_in_subject
/* {{{ php_str_replace_in_subject */ static void php_str_replace_in_subject(zval *search, zval *replace, zval **subject, zval *result, int case_sensitivity, int *replace_count) { zval **search_entry, **replace_entry = NULL, temp_result; char *replace_value = NULL; int replace_len = 0; /* Make sure we're dealing with strings. */ convert_to_string_ex(subject); Z_TYPE_P(result) = IS_STRING; if (Z_STRLEN_PP(subject) == 0) { ZVAL_STRINGL(result, "", 0, 1); return; } /* If search is an array */ if (Z_TYPE_P(search) == IS_ARRAY) { ...//不走这步 } else { if (Z_STRLEN_P(search) == 1) { //例子中只有”#“所以,执行这一步。 php_char_to_str_ex(Z_STRVAL_PP(subject),//subject的值,也就是dizaz#7#final Z_STRLEN_PP(subject), //获取subject的长度 Z_STRVAL_P(search)[0], //由于只有1个”#”,所以只需要第一个字符 Z_STRVAL_P(replace), //所要替换成的字符,现在是“-” Z_STRLEN_P(replace), //目标字符的长度,现在为1 result, //替换结果 case_sensitivity, //大小写是否敏感,默认是1 replace_count); //替换次数 } else if (Z_STRLEN_P(search) > 1) { Z_STRVAL_P(result) = php_str_to_str_ex(Z_STRVAL_PP(subject), Z_STRLEN_PP(subject), Z_STRVAL_P(search), Z_STRLEN_P(search), Z_STRVAL_P(replace), Z_STRLEN_P(replace), &Z_STRLEN_P(result), case_sensitivity, replace_count); } else { MAKE_COPY_ZVAL(subject, result); } } }
到现在为止,我们的目标最终锁定到了php_char_to_str_ex 函数,现在只需要分析这个函数就OK了。其实现为:
/* {{{ php_char_to_str_ex */ PHPAPI int php_char_to_str_ex(char *str, uint len, char from, char *to, int to_len, zval *result, int case_sensitivity, int *replace_count) { int char_count = 0; int replaced = 0; char *source, *target, *tmp, *source_end=str+len, *tmp_end = NULL; if (case_sensitivity) { //现在case_sensitivity = 1 char *p = str, *e = p + len; //计算需要替换几次 while ((p = memchr(p, from, (e - p)))) { char_count++; p++; } } else { for (source = str; source < source_end; source++) { if (tolower(*source) == tolower(from)) { char_count++; } } } if (char_count == 0 && case_sensitivity) { ZVAL_STRINGL(result, str, len, 1); return 0; } //计算替换以后的长度,并且存储到result中。 Z_STRLEN_P(result) = len + (char_count * (to_len - 1)); //申请内存,存放替换后的数据 Z_STRVAL_P(result) = target = safe_emalloc(char_count, to_len, len + 1); //设定结果是一个字符串 Z_TYPE_P(result) = IS_STRING; //target跟result的值都指向统一块内存,所以只需要处理target if (case_sensitivity) { char *p = str, *e = p + len, *s = str; while ((p = memchr(p, from, (e - p)))) { //判断在第几个字符出现# memcpy(target, s, (p - s)); //把#以前的数据拷贝给target target += p - s; memcpy(target, to, to_len); //把目标字符拷贝给target[当然此时的target是开始target+p-s的] target += to_len; p++; s = p; if (replace_count) { *replace_count += 1; //设定替换次数 } } //如果后面还有,继续添加到target后,这样target所指向的内存块已经是替换好的数据了。 if (s < e) { memcpy(target, s, (e - s)); target += e - s; } } else { for (source = str; source < source_end; source++) { if (tolower(*source) == tolower(from)) { replaced = 1; if (replace_count) { *replace_count += 1; } for (tmp = to, tmp_end = tmp+to_len; tmp < tmp_end; tmp++) { *target = *tmp; target++; } } else { *target = *source; target++; } } } *target = 0; return replaced; } /* }}} */
如上注释,其就这样完成了对于字符到字符串的替换。至于其中怎么return,怎么一个详细的过程,需要再对PHP执行过程有个相对的了解。
当然可以用gdb下断点到php_char_to_str_ex函数,来了解其详细执行过程。
下一篇来做对于字符串替换成字符串的分析。
小结:
其结果是存在zval中
其对替换的实现比较巧妙,可以学习
需要继续查看源码,学习更多编写技巧以及设计技巧。
php函数之子字符串替换&#65279; str_replace
声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Reply on: @reply_date@
@reply_contents@