• 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

  • 相关阅读:
    笔记:Why don't you pull up a chair and give this lifestyle a try?
    使用 Git 来备份 MySQL 数据库
    FastAdmin 将 PHP 框架升级到 ThinkPHP 5.1
    javascript的冻结对象之freeze(),isFrozen()方法
    javascript的密封对象之seal(),isSealed()方法
    javascript的防篡改对象之preventExtensions()方法
    自己根据js的兼容封装了一个小小的js库
    关于跨浏览器的部分代码的封装
    jQuery 的noConflict()的使用.
    jQuery的get()用法
  • 原文地址:https://www.cnblogs.com/52fhy/p/9786780.html
Copyright © 2020-2023  润新知