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 相关文章推荐
PHP4引用文件语句的对比
Oct 09 PHP
PHP+AJAX实现无刷新注册(带用户名实时检测)
Dec 02 PHP
php对mongodb的扩展(初出茅庐)
Nov 11 PHP
作为PHP程序员应该了解MongoDB的五件事
Jun 03 PHP
php使用mysqli向数据库添加数据的方法
Mar 20 PHP
功能强大的php文件上传类
Aug 29 PHP
php对象工厂类完整示例
Aug 09 PHP
PHP 7.4 新语法之箭头函数实例详解
May 09 PHP
php判断数组是否为空的实例方法
May 10 PHP
解决PhpStorm64不能启动的问题
Jun 20 PHP
php实现JWT验证的实例教程
Nov 26 PHP
PHP7 windows支持
Mar 09 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
8个出色的WordPress SEO插件收集
2011/02/26 PHP
深入php之规范编程命名小结
2013/05/15 PHP
PHP receiveMail实现收邮件功能
2018/04/25 PHP
解决Laravel5.2 Auth认证退出失效的问题
2019/10/14 PHP
jQuery中filter(),not(),split()使用方法
2010/07/06 Javascript
javascript实现日历控件(年月日关闭按钮)
2012/12/12 Javascript
可以浮动某个物体的jquery控件用法实例
2015/07/24 Javascript
jQuery mobile 移动web(4)
2015/12/20 Javascript
基于jQuery和CSS3制作响应式水平时间轴附源码下载
2015/12/20 Javascript
使用javascript插入样式
2016/03/14 Javascript
js简单判断flash是否加载完成的方法
2016/06/21 Javascript
如何利用模板将HTML从JavaScript中抽离
2016/10/08 Javascript
VueJS全面解析
2016/11/10 Javascript
JavaScript中使用webuploader实现上传视频功能(demo)
2017/04/10 Javascript
vue与TypeScript集成配置最简教程(推荐)
2017/10/17 Javascript
layui layer select 选择被遮挡的解决方法
2019/09/21 Javascript
wxpython学习笔记(推荐查看)
2014/06/09 Python
Python排序搜索基本算法之归并排序实例分析
2017/12/08 Python
python查看文件大小和文件夹内容的方法
2019/07/08 Python
pycharm设置当前工作目录的操作(working directory)
2020/02/14 Python
Python中logging日志库实例详解
2020/02/19 Python
Python多进程编程常用方法解析
2020/03/26 Python
使用keras框架cnn+ctc_loss识别不定长字符图片操作
2020/06/29 Python
Python Process创建进程的2种方法详解
2021/01/25 Python
HTML5之HTML元素扩展(上)—新增加的元素及使用概述
2013/01/31 HTML / CSS
Canvas与Image互相转换示例代码
2013/08/09 HTML / CSS
印度最大的旅游网站:MakeMyTrip
2016/10/05 全球购物
意大利文具和办公产品在线商店:Y-Office
2020/02/27 全球购物
Prototype如何为一个Ajax添加一个参数
2015/12/06 面试题
运动会稿件300字
2014/02/14 职场文书
2014新年元旦活动策划方案
2014/02/18 职场文书
关于美容院的活动方案
2014/08/14 职场文书
2014年纳税评估工作总结
2014/12/23 职场文书
后天观后感
2015/06/08 职场文书
2015年教学副校长工作总结
2015/07/22 职场文书
python中 Flask Web 表单的使用方法
2022/05/20 Python