php函数之子字符串替换 str_replace


Posted in PHP onMarch 23, 2011

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 相关文章推荐
php中对xml读取的相关函数的介绍一
Jun 05 PHP
php中强制下载文件的代码(解决了IE下中文文件名乱码问题)
May 09 PHP
php去除换行(回车换行)的三种方法
Mar 26 PHP
php递归遍历多维数组的方法
Apr 18 PHP
Symfony2学习笔记之系统路由详解
Mar 17 PHP
PHP实现一个简单url路由功能实例
Nov 05 PHP
php安全配置记录和常见错误梳理(总结)
Mar 28 PHP
php实现解析xml并生成sql语句的方法
Feb 03 PHP
php获取微信基础接口凭证Access_token
Aug 23 PHP
PHP两个n位的二进制整数相加问题的解决
Aug 26 PHP
网站被恶意镜像怎么办 php一段代码轻松搞定(全面版)
Oct 23 PHP
Laravel框架文件上传功能实现方法示例
Apr 16 PHP
php expects parameter 1 to be resource, array given 错误
Mar 23 #PHP
php去掉字符串的最后一个字符附substr()的用法
Mar 23 #PHP
PHPUnit PHP测试框架安装方法
Mar 23 #PHP
开启CURL扩展,让服务器支持PHP curl函数(远程采集)
Mar 19 #PHP
windows下开发并编译PHP扩展的方法
Mar 18 #PHP
WordPress判断用户是否登录的代码
Mar 17 #PHP
用php的ob_start来生成静态页面的方法分析
Mar 09 #PHP
You might like
PHP4实际应用经验篇(3)
2006/10/09 PHP
深入理解PHP之require/include顺序 推荐
2011/01/02 PHP
PHP编码转换函数 自动转换字符集支持数组转换
2012/12/16 PHP
PHP中file_put_contents追加和换行的实现方法
2017/04/01 PHP
yii2.0整合阿里云oss删除单个文件的方法
2017/09/19 PHP
javascript css float属性的特殊写法
2008/11/13 Javascript
javascript实现类似于新浪微博搜索框弹出效果的方法
2015/07/27 Javascript
使用jQuery判断浏览器滚动条位置的方法
2016/05/30 Javascript
JavaScript中的await/async的作用和用法
2016/10/31 Javascript
JavaScript的六种继承方式(推荐)
2017/06/26 Javascript
JS实现的找零张数最小问题示例
2017/11/28 Javascript
JavaScript基于面向对象实现的猜拳游戏
2018/01/03 Javascript
10个经典的网页鼠标特效代码
2018/01/09 Javascript
Taro集成Redux快速上手的方法示例
2018/06/21 Javascript
在vue中使用公共过滤器filter的方法
2018/06/26 Javascript
通过Nodejs搭建网站简单实现注册登录流程
2019/06/14 NodeJs
vue-cli脚手架引入弹出层layer插件的几种方法
2019/06/24 Javascript
[01:34:42]NAVI vs EG 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
python编写网页爬虫脚本并实现APScheduler调度
2014/07/28 Python
python3第三方爬虫库BeautifulSoup4安装教程
2018/06/19 Python
对pycharm 修改程序运行所需内存详解
2018/12/03 Python
通过pycharm使用git的步骤(图文详解)
2019/06/13 Python
docker-py 用Python调用Docker接口的方法
2019/08/30 Python
详解python 中in 的 用法
2019/12/12 Python
Python实现括号匹配方法详解
2020/02/10 Python
区分python中的进程与线程
2020/08/13 Python
了解一下python内建模块collections
2020/09/07 Python
利用pipenv和pyenv管理多个相互独立的Python虚拟开发环境
2020/11/01 Python
如何设置PyCharm中的Python代码模版(推荐)
2020/11/20 Python
Jupyter Notebook 安装配置与使用详解
2021/01/06 Python
Python实现给PDF添加水印的方法
2021/01/25 Python
超市周年庆活动方案
2014/08/16 职场文书
怎么写工作检讨书
2014/11/16 职场文书
大学生个人年度总结范文
2015/02/15 职场文书
2015年安全月活动总结
2015/03/26 职场文书
豆瓣2021评分最高动画剧集-豆瓣评分最高的动画剧集2021
2022/03/18 日漫