PHP源码阅读笔记十六:array_count_values函数

PHP源码阅读笔记十六:array_count_values
array_count_values

(PHP 4, PHP 5)
array_count_values — 统计数组中所有的值出现的次数
说明

array array_count_values ( array input )

array_count_values() 返回一个数组,该数组用 input 数组中的值作为键名,该值在 input 数组中出现的次数作为值。

源程序说明:
在源代码中的两句注释就说明了这个函数的实现

1
2
3
4
    /* Initialize return array */
    array_init(return_value);
 
    /* Go through input array and add values to the return array */

但是其中还有一些细节需要注意:
1、此函数只能识别字符串和数字,所以程序中使用了类似于下面的语句

1
2
3
4
5
if (Z_TYPE_PP(entry) == IS_LONG) {
} else if (Z_TYPE_PP(entry) == IS_STRING) {
} else {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can only count STRING and INTEGER values!");
}

2、在遍历过程中,首先判断是否不存在,此判断过程在针对字符串和数字时也有不同,但最终都是针对hash table的操作
在代码中针对zval的初始化使用的是宏zval *data; MAKE_STD_ZVAL(data);
跟踪此宏的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MAKE_STD_ZVAL(data);
==> #define MAKE_STD_ZVAL(zv)                 \    zend.h 586行
    ALLOC_ZVAL(zv); \
    INIT_PZVAL(zv);
 
==> #define ALLOC_ZVAL(z)    \
    ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST)    zend_alloc.h 165行
 
==> #define ZEND_FAST_ALLOC(p, type, fc_type)    \
    (p) = (type *) emalloc(sizeof(type))                   zend_alloc.h  152行
 
==> #define emalloc(size)                        _emalloc((size) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)    zend_alloc.h 56行
==> ZEND_API void *_emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)      zend_alloc.c 2288行 程序实现
 
==>  #define INIT_PZVAL(z)        \                 zend.h 576行
    (z)->refcount = 1;        \
    (z)->is_ref = 0;

EOF

PHP源码阅读笔记十五:array_walk函数

PHP源码阅读笔记十五:array_walk函数

array_walk

(PHP 3 >= 3.0.3, PHP 4, PHP 5)
array_walk — 对数组中的每个成员应用用户函数
说明

bool array_walk ( array &array, callback funcname [, mixed userdata] )

如果成功则返回 TRUE,失败则返回 FALSE。
将用户自定义函数 funcname 应用到 array 数组中的每个单元。典型情况下 funcname 接受两个参数。array 参数的值作为第一个,键名作为第二个。如果提供了可选参数 userdata,将被作为第三个参数传递给 callback funcname。
如果 funcname 函数需要的参数比给出的多,则每次 array_walk() 调用 funcname 时都会产生一个 E_WARNING 级的错误。这些警告可以通过在 array_walk() 调用前加上 PHP 的错误操作符 @ 来抑制,或者用 error_reporting()。
注意: 如果 funcname 需要直接作用于数组中的值,则给 funcname 的第一个参数指定为引用。这样任何对这些单元的 改变也将会改变原始数组本身。
注意: 将键名和 userdata 传递到 funcname 中是 PHP 4.0 新增加的。

array_walk() 不会受到 array 内部数组指针的影响。array_walk() 会遍历整个数组而不管指针的位置。(这是由于程序在数组遍历开始时就重置了数组所在hash table的指针)
用户不应在回调函数中改变该数组本身。例如增加/删除单元,unset 单元等等。如果 array_walk() 作用的数组改变了,则此函数的的行为未经定义,且不可预期。
程序实现说明:
扩展最后调用的是函数php_array_walk:

1
static int php_array_walk(HashTable *target_hash, zval **userdata, int recursive TSRMLS_DC)

当recursive == 0时,此函数为array_walk函数实现
当recursive == 1时,此函数为array_walk_recursive函数的实现
源码中,程序会遍历整个数组,并针对每个数组元素,根据传入的函数,作相关的函数调用
函数的调用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
    fci.size = sizeof(fci);
            fci.function_table = EG(function_table);
            fci.function_name = *BG(array_walk_func_name);
            fci.symbol_table = NULL;
            fci.object_pp = NULL;
            fci.retval_ptr_ptr = &retval_ptr;
            fci.param_count = userdata ? 3 : 2;
            fci.params = args;
            fci.no_separation = 0;
 
            /* Call the userland function */
            if (zend_call_function(&fci, &array_walk_fci_cache TSRMLS_CC) == SUCCESS) {

在此函数调用中有使用到一个结构体,个人添加的注释如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct _zend_fcall_info {
 size_t size;    //    整个结构体的长度,等于sizeof(此函数体的变量)
 HashTable *function_table;    //    executor_globals.function_table
 zval *function_name;    //    函数名 
 HashTable *symbol_table;
 zval **retval_ptr_ptr;        //    函数的返回值
 zend_uint param_count;    //    参数个数
 zval ***params;            //    所调用函数的参数
 zval **object_pp;        //    用于对象的方法调用时,存储对象
 zend_bool no_separation;    //    是否清空参数所在的栈
} zend_fcall_info;

以上为个人所注,如有错,请指正!
EOF

PHP源码阅读笔记十四: array_combine函数

PHP源码阅读笔记十四: array_combine函数

array_combine

(PHP 5)
array_combine — 创建一个数组,用一个数组的值作为其键名,另一个数组的值作为其值
说明

array array_combine ( array keys, array values )

返回一个 array,用来自 keys 数组的值作为键名,来自 values 数组的值作为相应的值。
如果两个数组的单元数不同或者数组为空时返回 FALSE。

程序实现说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
array_init(return_value);
 
 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos_keys);
 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos_values);    //    初始化数组指针,将其置为双向链接的头指针
 while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void **)&entry_keys, &pos_keys) == SUCCESS &&
   zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&entry_values, &pos_values) == SUCCESS) {    //    同时遍历两个数组
  if (Z_TYPE_PP(entry_keys) == IS_STRING) {    //    如果key值为字符串,以key
   zval_add_ref(entry_values);
   add_assoc_zval_ex(return_value, Z_STRVAL_PP(entry_keys), Z_STRLEN_PP(entry_keys)+1, *entry_values);
  } else if (Z_TYPE_PP(entry_keys) == IS_LONG) {
   zval_add_ref(entry_values);
   add_index_zval(return_value, Z_LVAL_PP(entry_keys), *entry_values);
  } else {
   zval key;
   key = **entry_keys;
   zval_copy_ctor(&key);
   convert_to_string(&key);    //    转化为字符串,如果为数组,则为Array
   zval_add_ref(entry_values);
   add_assoc_zval_ex(return_value, Z_STRVAL(key), Z_STRLEN(key)+1, *entry_values);
   zval_dtor(&key);
  }
  zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos_keys);
  zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos_values);    //    下一个元素,其实现为:pos_values = pos_values->pListNext;
 
 }

在PHP代码中如果key数组中包含两个数组,则后面一个会覆盖前面,即最终只有一个元素,
如下所示PHP代码:

1
2
3
4
5
6
7
\
<?PHP
$arr1 = array(1, array(1, 2), array(3, 4), array(5, 6));
$arr2 = array(33, 44, 55, 66);
$arr3 = array_combine($arr1, $arr2);
print_r($arr3);
die();

此代码会输出:
Array ( [1] => 33 [Array] => 66 )

EOF