zoukankan      html  css  js  c++  java
  • PHP源码阅读(一):str_split函数

    注:源码版本:php5.6.33。

    函数简介

    str_split 原型:

    array str_split ( string $string [, int $split_length = 1 ] )
    

    说明:将一个字符串转换为数组。 参数:string为输入字符串。split_length是每一段的长度。

    str_split() 使用范例 :

    $str  =  "Hello Friend" ;
    
    $arr1  =  str_split ( $str );
    $arr2  =  str_split ( $str ,  3 );
    
    print_r ( $arr1 );
    print_r ( $arr2 );
    

    以上例程会输出:

    Array
    (
        [0] => H
        [1] => e
        [2] => l
        [3] => l
        [4] => o
        [5] =>
        [6] => F
        [7] => r
        [8] => i
        [9] => e
        [10] => n
        [11] => d
    )Array
    (
        [0] => Hel
        [1] => lo
        [2] => Fri
        [3] => end
    )
    

    对应的C源码在 ext/standard/string.c 5568行。这里我贴出来:

    PHP_FUNCTION(str_split)
    {
    	char *str;
    	int str_len;
    	long split_length = 1;
    	char *p;
    	int n_reg_segments;
    
    	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &split_length) == FAILURE) {
    		return;
    	}
    
    	if (split_length <= 0) {
    		php_error_docref(NULL TSRMLS_CC, E_WARNING, "The length of each segment must be greater than zero");
    		RETURN_FALSE;
    	}
    
    	array_init_size(return_value, ((str_len - 1) / split_length) + 1);
    
    	if (split_length >= str_len) {
    		add_next_index_stringl(return_value, str, str_len, 1);
    		return;
    	}
    
    	n_reg_segments = str_len / split_length;
    	p = str;
    
    	while (n_reg_segments-- > 0) {
    		add_next_index_stringl(return_value, p, split_length, 1);
    		p += split_length;
    	}
    
    	if (p != (str + str_len)) {
    		add_next_index_stringl(return_value, p, (str + str_len - p), 1);
    	}
    }
    

    zend_parse_parameters

    首先看参数解析部分:

    zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &split_length)
    

    1、第一个参数我们使用默认值。下面是原因:

    传递给 zend_parse_parameters() 的第一个参数是用户实际传递到函数的参数数量。此数值做为 ht 参数传给函数,但就像上面讨论的那样,应使用做为实现细节的 ZEND_NUM_ARGS()。为了与 PHP 的线程隔离、线程安全资源管理器兼容,还要用 TSRMLS_CC 传递线程上下文。与其他函数不同,它不能是最后的参数,因为在 zend_parse_parameters 内要求有不定数量的参数——依赖于要读取的用户参数的数量。

    2、第二个参数定义所要求的参数。

    每个参数都由字符串中的一个字符表示其类型。 如果希望一个字符串参数,则在此类型说明只不过是个 "s"。

    这里的s|l表示接受一个字符串和它的长度,另外再取得一个可选的长整数。|表示可选。

    相关所有类型说明符和对应的附加的 C 语言类型的文档可在源代码发布包中的文件 README.PARAMETER_PARSING_API 中找到。大多数重要类型可见下表。

    zend_parse_parameters() 类型说明符

    修饰符 对应C里的数据类型 描述
    b zend_bool Boolean 值
    l long integer (long) 值
    d double float (double) 值
    s char*, int 二进制的安全串。前者接收指针,后者接收长度
    h HashTable* 数组的哈希表
    r zval* Resource 资源
    a zval* Array 数组
    o zval* Object instance 对象
    O zval, zend_class_entry Object instance of a specified type 特定类型的对象
    z zval* Non-specific zval 任意类型~
    Z zval** zval**类型
    f zval** 表示函数、方法名称,PHP5.3之前没有的

    bldsh这几个比较常用,需要熟记。s比较特殊,需要用两个参数来接收。

    如果有多个参数,类型说明符可以有多个。例如lsz表示取得一个长整数,一个字符串和它的长度,再取得一个 zval 值。类型说明符还有几个特殊标记:

    | - 表明剩下的参数都是可选参数。如果用户没有传进来这些参数值,那么这些值就会被初始化成默认值。
     
    / - 表明参数解析函数将会对剩下的参数以 SEPARATE_ZVAL_IF_NOT_REF() 的方式来提供这个参数的一份拷贝,除非这些参数是一个引用。
     
    ! - 表明剩下的参数允许被设定为 NULL(仅用在 a、o、O、r和z身上)。如果用户传进来了一个 NULL 值,则存储该参数的变量将会设置为 NULL。
    

    3、最后一个参数是传递一个或多个指针给要填充变量值的 C 变量,或提供更多细节。比如字符串,事实上的字符串,总是以 NULL 结尾,以 char*,且其长度是除 NULL 字节外的 int 型值。

    参考:

    函数返回值

    PHP扩展开发里不是直接以return的形式返回值的,zend引擎在每个zif函数声明里加了一个zval*类型的形参,名为return_value,专门来解决返回值这个问题。

    ZEND_FUNCTION本身并没有通过return关键字返回任何有价值的东西,它只不过是在运行时修改了return_value指针所指向的变量的值而已,而内核则会把return_value指向的变量作为用户端调用此函数
    后的得到的返回值。ZVAL_LONG()等宏是对一类操作的封装,展开后应该就是下面这样:

    Z_TYPE_P(return_value) = IS_LONG;
    Z_LVAL_P(return_value) = 42;
    
    //更彻底的讲,应该是这样的:
    return_value->type = IS_LONG;
    return_value->value.lval = 42;
    

    其它的还有:

    //这些宏都定义在Zend/zend_API.h文件里
    #define RETVAL_RESOURCE(l)              ZVAL_RESOURCE(return_value, l)
    #define RETVAL_BOOL(b)                  ZVAL_BOOL(return_value, b)
    #define RETVAL_NULL()                   ZVAL_NULL(return_value)
    #define RETVAL_LONG(l)                  ZVAL_LONG(return_value, l)
    #define RETVAL_DOUBLE(d)                ZVAL_DOUBLE(return_value, d)
    #define RETVAL_STRING(s, duplicate)         ZVAL_STRING(return_value, s, duplicate)
    #define RETVAL_STRINGL(s, l, duplicate)     ZVAL_STRINGL(return_value, s, l, duplicate)
    #define RETVAL_EMPTY_STRING()           ZVAL_EMPTY_STRING(return_value)
    #define RETVAL_ZVAL(zv, copy, dtor)     ZVAL_ZVAL(return_value, zv, copy, dtor)
    #define RETVAL_FALSE                    ZVAL_BOOL(return_value, 0)
    #define RETVAL_TRUE                     ZVAL_BOOL(return_value, 1)
    
    #define RETURN_RESOURCE(l)              { RETVAL_RESOURCE(l); return; }
    #define RETURN_BOOL(b)                  { RETVAL_BOOL(b); return; }
    #define RETURN_NULL()                   { RETVAL_NULL(); return;}
    #define RETURN_LONG(l)                  { RETVAL_LONG(l); return; }
    #define RETURN_DOUBLE(d)                { RETVAL_DOUBLE(d); return; }
    #define RETURN_STRING(s, duplicate)     { RETVAL_STRING(s, duplicate); return; }
    #define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
    #define RETURN_EMPTY_STRING()           { RETVAL_EMPTY_STRING(); return; }
    #define RETURN_ZVAL(zv, copy, dtor)     { RETVAL_ZVAL(zv, copy, dtor); return; }
    #define RETURN_FALSE                    { RETVAL_FALSE; return; }
    #define RETURN_TRUE                     { RETVAL_TRUE; return; }
    

    再回头看str_split里的实现,我们发现没有使用RETURN_*相关的宏进行返回。这是怎么回事呢?仔细看,发现使用array_init_size修改了return_value指针,我们追踪array_init_size代码:

    #define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)
    

    继续展开:

    ZEND_API int _array_init(zval *arg, uint size ZEND_FILE_LINE_DC) /* {{{ */
    {
    	ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg));
    
    	_zend_hash_init(Z_ARRVAL_P(arg), size, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC);
    	Z_TYPE_P(arg) = IS_ARRAY;
    	return SUCCESS;
    }
    

    原来array_init_size底层已经实现了RETURN_*的功能。

    php_error_docref

    php_error_docref是一个错误抛出函数。还有一个zend_error函数,它主要被Zend Engine使用,但也经常出现在扩展代码中。

    两个函数都使用sprintf函数,比如格式化信息,因此错误信息可以包含占位符,那些占位符会被后面的参数填充。下面有一个例子:

    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write %d bytes to %s", Z_STRLEN_PP(tmp), filename);
    
    // %d is filled with Z_STRLEN_PP(tmp)
    // %s is filled with filename
    

    参考:

    array_init_size

    #define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)
    

    初始化一个数组,指定初始化数组的元素个数。该函数定义在Zend_API.h里。

    代码里:

    array_init_size(return_value, ((str_len - 1) / split_length) + 1);
    

    初始化了一个数组,大小为字符串分段长度:最终分为几部分,使用向上取整方法。

    用向上取整的计算公式为 : (a-1)/b+1 。

    参考:

    array_init

    该函数与array_init_size用法相似,只是不用指定数组大小。该函数用于初始化一个空数组。

    #define array_init(arg)			_array_init((arg), 0 ZEND_FILE_LINE_CC)
    

    示例:

    ZEND_FUNCTION(sample_array)
    {
        array_init(return_value);
    }
    
    //return_value是zval*类型的,所以我们直接对它调用array_init()函数即可,即把它初始化成了一个空数组。
    增!
    

    add_next_index_stringl

    将数组初始化后,接下来就要向其添加元素了。

    函数原型:

    int add_next_index_stringl(zval *arg, const char *str, uint length, int duplicate) 
    

    该函数就是给指定数组增加一个元素,该元素是字符串类型,其中length参数指的是截取的str的长度。该函数是二进制安全的。

    代码里多次用到这个函数:

    
    //参数指定长度大于字符串长度,不用分割了,直接返回字符串本身即可
    if (split_length >= str_len) {
    	add_next_index_stringl(return_value, str, str_len, 1);
    	return;
    }
    
    //分段长度
    n_reg_segments = str_len / split_length;
    p = str;
    
    //字符串指针p每次往后移动split_length长度
    while (n_reg_segments-- > 0) {
    	add_next_index_stringl(return_value, p, split_length, 1);
    	p += split_length;
    }
    
    //当str_len / split_length不能整除的时候, str_len > split_length * n_reg_segments 
    if (p != (str + str_len)) {
    	add_next_index_stringl(return_value, p, (str + str_len - p), 1);
    }
    

    扩展阅读:给数组添加元素

    上面介绍的add_next_index_stringl函数是add_next_index_string的变种,l表示length。其实类似的还有很多。因为PHP语言中有多种类型的变量,所以也对应的有多种类型的add_assoc()add_index()add_next_index*()函数。如:

    array_init(arrval);
    
    add_assoc_long(zval *arrval, char *key, long lval);
    add_index_long(zval *arrval, ulong idx, long lval);
    add_next_index_long(zval *arrval, long lval);
    

    这三个函数的第一个参数都要被操作的数组指针,然后是索引值,最后是变量,唯一不同的是add_next_index_long()函数的索引值是其自己计算出来的。

    这三个函数分别在内部使用了zend_hash_update()zend_hash_index_update()zend_hash_next_index_insert()函数。

    //add_assoc_*系列函数:
    add_assoc_null(zval *aval, char *key);
    add_assoc_bool(zval *aval, char *key, zend_bool bval);
    add_assoc_long(zval *aval, char *key, long lval);
    add_assoc_double(zval *aval, char *key, double dval);
    add_assoc_string(zval *aval, char *key, char *strval, int dup);
    add_assoc_stringl(zval *aval, char *key,char *strval, uint strlen, int dup);
    add_assoc_zval(zval *aval, char *key, zval *value);
    
    //备注:其实这些函数都是宏,都是对add_assoc_*_ex函数的封装。
    
    //add_index_*系列函数:
    ZEND_API int add_index_long     (zval *arg, ulong idx, long n);
    ZEND_API int add_index_null     (zval *arg, ulong idx           );
    ZEND_API int add_index_bool     (zval *arg, ulong idx, int b    );
    ZEND_API int add_index_resource (zval *arg, ulong idx, int r    );
    ZEND_API int add_index_double   (zval *arg, ulong idx, double d);
    ZEND_API int add_index_string   (zval *arg, ulong idx, const char *str, int duplicate);
    ZEND_API int add_index_stringl  (zval *arg, ulong idx, const char *str, uint length, int duplicate);
    ZEND_API int add_index_zval     (zval *arg, ulong index, zval *value);
    
    //add_next_index_long函数:
    ZEND_API int add_next_index_long        (zval *arg, long n  );
    ZEND_API int add_next_index_null        (zval *arg          );
    ZEND_API int add_next_index_bool        (zval *arg, int b   );
    ZEND_API int add_next_index_resource    (zval *arg, int r   );
    ZEND_API int add_next_index_double      (zval *arg, double d);
    ZEND_API int add_next_index_string      (zval *arg, const char *str, int duplicate);
    ZEND_API int add_next_index_stringl     (zval *arg, const char *str, uint length, int duplicate);
    ZEND_API int add_next_index_zval        (zval *arg, zval *value);
    

    总结:
    上述这些函数都是给指定数组增加元素的。add_index_*add_assoc_*系列函数的第一个参数都要被操作的数组指针,然后是索引值,最后是变量;add_next_index_*系列函数无需指定索引值。

    下面让我们通过一个例子来演示下它们的用法:

    ZEND_FUNCTION(sample_array)
    {
        zval *subarray;
    
        array_init(return_value);
    
        /* Add some scalars */
        add_assoc_long(return_value, "life", 42);
        add_index_bool(return_value, 123, 1);
        add_next_index_double(return_value, 3.1415926535);
    
        /* Toss in a static string, dup'd by PHP */
        add_next_index_string(return_value, "Foo", 1);
    
        /* Now a manually dup'd string */
        add_next_index_string(return_value, estrdup("Bar"), 0);
    
        /* Create a subarray */
        MAKE_STD_ZVAL(subarray);
        array_init(subarray);
    
        /* Populate it with some numbers */
        add_next_index_long(subarray, 1);
        add_next_index_long(subarray, 20);
        add_next_index_long(subarray, 300);
    
        /* Place the subarray in the parent */
        add_index_zval(return_value, 444, subarray);
    }
    

    这时如果我们用户端var_dump这个函数的返回值便会得到:

    <?php
    var_dump(sample_array());
    

    输出:

    array(6)
    {
        ["life"]=> int(42)
        [123]=> bool(true)
        [124]=> float(3.1415926535)
        [125]=> string(3) "Foo"
        [126]=> string(3) "Bar"
        [444]=> array(3)
        {
            [0]=> int(1)
            [1]=> int(20)
            [2]=> int(300)
        }
    }
    

    参考:

    在内核中操作PHP语言中数组 - PHP 扩展开发及内核应用相关内容 - 极客学院Wiki
    http://wiki.jikexueyuan.com/project/extending-embedding-php/8.3.html

  • 相关阅读:
    JDBC 查询的三大参数 setFetchSize prepareStatement(String sql, int resultSetType, int resultSetConcur)
    有空必看
    SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换
    FusionCharts JavaScript API Column 3D Chart
    FusionCharts JavaScript API
    FusionCharts JavaScript API
    Extjs 继承Ext.Component自定义组件
    eclipse 彻底修改复制后的项目名称
    spring 转换器和格式化
    Eclipse快速生成一个JavaBean类的方法
  • 原文地址:https://www.cnblogs.com/52fhy/p/9786780.html
Copyright © 2011-2022 走看看