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 相关文章推荐
一个取得文件扩展名的函数
Oct 09 PHP
实现dedecms全站URL静态化改造的代码
Mar 29 PHP
PHP查询MySQL大量数据的时候内存占用分析
Jul 22 PHP
在yii中新增一个用户验证的方法详解
Jun 20 PHP
Laravel 5框架学习之Laravel入门和新建项目
Apr 07 PHP
一个完整的PHP类包含的七种语法说明
Jun 04 PHP
提交表单后 PHP获取提交内容的实现方法
May 25 PHP
php实现有序数组打印或排序的方法【附Python、C及Go语言实现代码】
Nov 10 PHP
php实现基于pdo的事务处理方法示例
Jul 21 PHP
PHP基于堆栈实现的高级计算器功能示例
Sep 15 PHP
支持汉转拼和拼音分词的PHP中文工具类ChineseUtil
Feb 23 PHP
Yii框架安装简明教程
May 15 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
有关PHP中MVC的开发经验分享
2012/05/17 PHP
php不写闭合标签的好处
2014/03/04 PHP
ThinkPHP使用smarty模板引擎的方法
2014/07/01 PHP
ThinkPHP文件上传实例教程
2014/08/22 PHP
浅谈PHP中其他类型转化为Bool类型
2016/03/28 PHP
自定义Laravel (monolog)日志位置,并增加请求ID的实现
2019/10/17 PHP
PHP开发API接口签名生成及验证操作示例
2020/05/27 PHP
Js中获取frames中的元素示例代码
2013/07/30 Javascript
javascript eval(func())使用示例
2013/12/05 Javascript
javascript修改图片src的方法
2015/01/27 Javascript
JS基于FileSystemObject创建一个指定路径的TXT文本文件
2015/08/05 Javascript
学习javascript面向对象 理解javascript对象
2016/01/04 Javascript
js中setTimeout的妙用--防止循环超时
2017/03/06 Javascript
vue2.0$nextTick监听数据渲染完成之后的回调函数方法
2018/09/11 Javascript
vue仿淘宝滑动验证码功能(样式模仿)
2019/12/10 Javascript
Bootstrap实现前端登录页面带验证码功能完整示例
2020/03/26 Javascript
vue项目中微信登录的实现操作
2020/09/08 Javascript
vue切换菜单取消未完成接口请求的案例
2020/11/13 Javascript
Python Web框架Flask下网站开发入门实例
2015/02/08 Python
Python的Twisted框架上手前所必须了解的异步编程思想
2016/05/25 Python
python opencv实现任意角度的透视变换实例代码
2018/01/12 Python
使用python编写简单的小程序编译成exe跑在win10上
2018/01/15 Python
Python设计模式之解释器模式原理与用法实例分析
2019/01/10 Python
浅谈keras 模型用于预测时的注意事项
2020/06/27 Python
CSS3 倾斜的网页图片库实例教程
2009/11/14 HTML / CSS
美国户外生活方式品牌:Eddie Bauer
2016/12/28 全球购物
linux面试题参考答案(10)
2016/10/26 面试题
房地产融资计划书
2014/01/10 职场文书
个人合作协议书范本
2014/04/18 职场文书
运动会方阵口号
2014/06/07 职场文书
医学专业毕业生推荐信
2014/07/12 职场文书
2014民事授权委托书范本
2014/09/29 职场文书
党员三严三实心得体会
2014/10/13 职场文书
锦旗赠语
2015/06/23 职场文书
2019最新版劳务派遣管理制度
2019/08/16 职场文书
Python中time与datetime模块使用方法详解
2022/03/31 Python