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 相关文章推荐
PHP5中的this,self和parent关键字详解教程
Mar 19 PHP
一个PHP验证码类代码分享(已封装成类)
Jul 17 PHP
PHP常用编译参数中文说明
Sep 27 PHP
自定义session存储机制避免会话保持问题
Oct 08 PHP
PHP统计目录大小的自定义函数分享
Nov 18 PHP
php+mysqli实现将数据库中一张表信息打印到表格里的方法
Jan 28 PHP
php中stdClass的用法分析
Feb 27 PHP
php实现字符串首字母大写和单词首字母大写的方法
Mar 14 PHP
PHP结合jQuery实现找回密码
Jul 22 PHP
thinkphp分页集成实例
Jul 24 PHP
PHP实现的mysql操作类【MySQL与MySQLi方式】
Oct 07 PHP
Laravel5.1 框架路由基础详解
Jan 04 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解决抢购秒杀抽奖等大流量并发入库导致的库存负数的问题
2014/06/19 PHP
Smarty使用自定义资源的方法
2015/08/08 PHP
thinkPHP框架中执行原生SQL语句的方法
2017/10/25 PHP
PHP实现求连续子数组最大和问题2种解决方法
2017/12/26 PHP
php设计模式之组合模式实例详解【星际争霸游戏案例】
2020/03/27 PHP
javascript 新浪背投广告实现代码
2009/07/07 Javascript
JS随即打乱数组实现代码
2012/12/03 Javascript
封装html的select标签的js操作实例
2013/07/02 Javascript
javascript unicode与GBK2312(中文)编码转换方法
2013/11/14 Javascript
JavaScript的各种常见函数定义方法
2014/09/16 Javascript
jQuery带箭头提示框tooltips插件集锦
2014/11/17 Javascript
js根据鼠标移动速度背景图片自动旋转的方法
2015/02/28 Javascript
Javascript中的高阶函数介绍
2015/03/15 Javascript
jQuery实现的省市联动菜单功能示例【测试可用】
2017/01/13 Javascript
微信小程序云开发实现云数据库读写权限
2019/05/17 Javascript
vue-dplayer 视频播放器实例代码
2019/11/08 Javascript
JS数组方法slice()用法实例分析
2020/01/18 Javascript
[02:44]DOTA2英雄基础教程 克林克兹
2014/01/15 DOTA
跟老齐学Python之dict()的操作方法
2014/09/24 Python
python实现FTP服务器服务的方法
2017/04/11 Python
浅谈python numpy中nonzero()的用法
2018/04/02 Python
Python模拟自动存取款机的查询、存取款、修改密码等操作
2018/09/02 Python
pandas通过索引进行排序的示例
2018/11/16 Python
使用python list 查找所有匹配元素的位置实例
2019/06/11 Python
Python argparse模块应用实例解析
2019/11/15 Python
Python socket实现的文件下载器功能示例
2019/11/15 Python
Tensorflow全局设置可见GPU编号操作
2020/06/30 Python
德国最大的服装、鞋子和配件在线商店之一:Outfits24
2019/07/23 全球购物
学生个人的自我评价分享
2013/11/05 职场文书
个人简历自我评价范文
2014/02/04 职场文书
项目合作协议书
2014/09/23 职场文书
2015年为民办实事工作总结
2015/05/26 职场文书
2019自荐信范文集锦!
2019/07/03 职场文书
新手必备之MySQL msi版本下载安装图文详细教程
2021/05/21 MySQL
JavaScript架构搭建前端监控如何采集异常数据
2022/06/25 Javascript
mysql拆分字符串作为查询条件的示例代码
2022/07/07 MySQL