• 冒险村物语
  • 英雄无敌3塔防
  • 驾考家园手游

php的扩展与嵌入--php扩展中的数组和哈希表2

2014-09-05

接着上面一节,继续说php扩展中的数组与哈希表的api,这节主要是说回调遍历函数正常遍历函数析构函数排序、对比、极值函数

Iteration by hash Apply:对数组进行遍历,最简单的是使用一种与php语言中foreach语句功能类似的函数,zend_hash_apply,它接收一个回调函数,并将hashtable的每一个元素传递给它。
typedef int (*apply_func_t)(void *pDest TSRMLS_DC);void zend_hash_apply(HashTable *ht,        apply_func_t apply_func TSRMLS_DC);
typedef int (*apply_func_arg_t)(void *pDest,                            void *argument TSRMLS_DC);void zend_hash_apply_with_argument(HashTable *ht,        apply_func_arg_t apply_func, void *data TSRMLS_DC);
一个是可以传递参数的,还有一个是不传递参数,只传递哈希表中的值的。对于可传递参数的函数而言,在扩展中应用到可能性更大。回调函数可能有不同的返回值:
表 回调函数的返回值
Constant Meaning
ZEND_HASH_APPLY_KEEP 结束当前请求,进入下一个循环。与PHP语言forech语句中的一次循环执行完毕或者遇到continue关键字的作用一样。
ZEND_HASH_APPLY_STOP 跳出,与PHP语言forech语句中的break关键字的作用一样。
ZEND_HASH_APPLY_REMOVE 删除当前的元素,然后继续处理下一个。相当于在PHP语言中:unset($foo[$key]);continue;

对于一段简单的php遍历代码
扩展形式如下:首先定义回调函数
int php_sample_print_zval(zval **val TSRMLS_DC){    //重新copy一个zval,防止破坏原数据    zval tmpcopy = **val;    zval_copy_ctor(&tmpcopy);         //转换为字符串    INIT_PZVAL(&tmpcopy);    convert_to_string(&tmpcopy);        //开始输出    php_printf("The value is: ");    PHPWRITE(Z_STRVAL(tmpcopy), Z_STRLEN(tmpcopy));    php_printf("/n");         //毁尸灭迹    zval_dtor(&tmpcopy);         //返回,继续遍历下一个~    return ZEND_HASH_APPLY_KEEP;}
然后定义循环函数:zend_hash_apply(arrht, php_sample_print_zval TSRMLS_CC);
遍历了一个名为arrht,元素类型是zval*的哈希表。
注意保存在哈希表中的并不是元素,而是指针,也就是一个zval**.在复制的时候也是复制指针的,哈希表本身不会动内容的。

为了能够在循环的时候既接受到值,也接收到key,第三种形式zend_hash_apply():zend_hash_apply_with_arguments()
 $val) {     echo "The value of $key is: $val/n"; }?>
针对这段php代码的c代码:
int php_sample_print_zval_and_key(zval **val,         int num_args, va_list args, zend_hash_key *hash_key) {    /* 复制zval从而使得原来的内容被保存下来 */         zval tmpcopy = **val;         /* tsrm_ls is needed by output functions */         TSRMLS_FETCH();         zval_copy_ctor(&tmpcopy);         /* Reset refcount & Convert */    INIT_PZVAL(&tmpcopy);    convert_to_string(&tmpcopy);         /* 输出 */    php_printf("The value of ");    if (hash_key->nKeyLength) {          /* 如果是字符串类型的key */            PHPWRITE(hash_key->arKey, hash_key->nKeyLength);         } else {        /* 如果是数字类型的key */         php_printf("%ld", hash_key->h);         }    php_printf(" is: ");    PHPWRITE(Z_STRVAL(tmpcopy), Z_STRLEN(tmpcopy));     php_printf("/n");    /* Toss out old copy */    zval_dtor(&tmpcopy);    /* continue; */    return ZEND_HASH_APPLY_KEEP;}

执行遍历:zend_hash_apply_with_arguments(arrht, php_sample_print_zval_and_key, 0);当我们检查这个hash_key是字符串类型还是数字类型时,是通过nKeyLength属性来检测的,而不是arKey属性。这是因为内核有时候会留在arKey属性里些脏数据,但nKeyLength属性是安全的,可以安全的使用。甚至对于空字符串索引,它也照样能处理。比如:$foo[''] ="Bar";索引的值是NULL字符,但它的长度却是包括最后这个NULL字符的,所以为1。

Iteration by move forward不用callback也可以实现哈希表遍历。这时候用的是哈希表的内部指针。
在用户空间里有很多可用的函数:
1, 'b'=>2, 'c'=>3);    reset($arr);    while (list($key, $val) = each($arr)) {        /* Do something with $key and $val */    }    reset($arr);    $firstkey = key($arr);    $firstval = current($arr);    $bval = next($arr);    $cval = next($arr);?>
每一个相应的函数都会有一个zend版本:
	* /* reset() */void zend_hash_internal_pointer_reset(HashTable *ht);        /* key() */int zend_hash_get_current_key(HashTable *ht,        char **strIdx, unit *strIdxLen,        ulong *numIdx, zend_bool duplicate);	* /* current() */int zend_hash_get_current_data(HashTable *ht, void **pData);	* /* next()/each() */int zend_hash_move_forward(HashTable *ht);	* /* prev() */int zend_hash_move_backwards(HashTable *ht);	* /* end() */void zend_hash_internal_pointer_end(HashTable *ht);	* /* Other... */int zend_hash_get_current_key_type(HashTable *ht);int zend_hash_has_more_elements(HashTable *ht);
next() prev() end()其实都是找到相应的索引值,再用zend_hash_get_current_data()返回元素值
each()跟next步骤一样,但是又调用并返回了zend_hash_get_current_key()

所以下面给出了不用回调函数的哈希表遍历方法:
void php_sample_print_var_hash(HashTable *arrht){    for(zend_hash_internal_pointer_reset(arrht);    zend_hash_has_more_elements(arrht) == SUCCESS;    zend_hash_move_forward(arrht)) {        char *key;        uint keylen;        ulong idx;        int type;        zval **ppzval, tmpcopy;        type = zend_hash_get_current_key_ex(arrht, &key, &keylen,                                                  &idx, 0, NULL);//获得返回的key的类型。这个类型可能有三种        if (zend_hash_get_current_data(arrht, (void**)&ppzval) == FAILURE) {//获得当前索引所指的数据值            /* Should never actually fail             * since the key is known to exist. */            continue;        }        /* 复制zval的值,从而原来的值不会被破坏掉 */        tmpcopy = **ppzval;        zval_copy_ctor(&tmpcopy);        /* 重新设定refcount 并且转换 */        INIT_PZVAL(&tmpcopy);        convert_to_string(&tmpcopy);        /* 输出 */        php_printf("The value of ");        if (type == HASH_KEY_IS_STRING) {            /* String Key / Associative */            PHPWRITE(key, keylen);        } else {            /* Numeric Key */            php_printf("%ld", idx);        }        php_printf(" is: ");        PHPWRITE(Z_STRVAL(tmpcopy), Z_STRLEN(tmpcopy));        php_printf("/n");        /* 销毁原来的副本 */        zval_dtor(&tmpcopy);    }}
来看一下zend_hash_get_current_key_ex返回值的可能性:Constant Meaning

HASH_KEY_IS_STRING 当前元素的索引是字符串类型的。therefore, a pointer to the element's key name will be populated into strIdx, and its length will be populated into stdIdxLen. If the duplicate flag is set to a nonzero value, the key will be estrndup()'d before being populated into strIdx. The calling application is expected to free this duplicated string.

HASH_KEY_IS_LONG 当前元素的索引是数字型的。
HASH_KEY_NON_EXISTANT HashTable中的内部指针已经移动到尾部,不指向任何元素。


Destruction注意只有四种析构函数:前两个是用来从哈希表中删掉单个元素的:
int zend_hash_del(HashTable *ht, char *arKey, uint nKeyLen);int zend_hash_index_del(HashTable *ht, ulong h);
返回SUCCESS OR FAILURE
分别对应字符串和数字索引的版本。
当一个元素从哈希表中移除的时候,哈希表的析构函数带着指向这个元素的指针被调用。

完全删除哈希表的时候:void zend_hash_clean(HashTable *ht);相当于是循环调用一下zend_hash_del。调用下面这个函数除了执行clean之外,还会把zend_hash_init申请的空间都给搞掉:void zend_hash_destroy(HashTable *ht);
来看一个哈希表的生命周期就可以对整个过程有更清楚的认识:
int sample_strvec_handler(int argc, char **argv TSRMLS_DC){    HashTable *ht;    /* 为哈希表分配空间 */    ALLOC_HASHTABLE(ht);    /* 初始化哈希表的内部状态 */    if (zend_hash_init(ht, argc, NULL,                        ZVAL_PTR_DTOR, 0) == FAILURE) {        FREE_HASHTABLE(ht);        return FAILURE;    }    /* 把每个字符串变成zval* */    while (argc) {        zval *value;        MAKE_STD_ZVAL(value);        ZVAL_STRING(value, argv[argc], 1);        argv++;        if (zend_hash_next_index_insert(ht, (void**)&value,                            sizeof(zval*)) == FAILURE) {            /* 对于分配失败的情况应该跳掉 */            zval_ptr_dtor(&value);        }    }    /* Do some work */    process_hashtable(ht);    /* 毁坏哈希表     * 释放所有的分配的空旷 */    zend_hash_destroy(ht);    /* Free the HashTable itself */    FREE_HASHTABLE(ht);    return SUCCESS;}


Sorting, Comparing, and Going to the Extreme(s)对于两个哈希表进行大小比较:typedef int (*compare_func_t)(void *a, void *b TSRMLS_DC);这个函数就跟qsort一样,期待你自己的函数去比较a和b,返回-1 0 1

下面就是一个用大小比较的例子:
int zend_hash_minmax(HashTable *ht, compare_func_t compar,                        int flag, void **pData TSRMLS_DC);
flag为0就返回最小值,否则就是最大值。

下面则给出一个更为具体的例子,通过不同的flag就可以控制到底是返回最大值还是最小值:
int fname_compare(zend_function *a, zend_function *b TSRMLS_DC){    return strcasecmp(a->common.function_name, b->common.function_name);}void php_sample_funcname_sort(TSRMLS_D){    zend_function *fe;    if (zend_hash_minmax(EG(function_table), fname_compare,                0, (void **)&fe) == SUCCESS) {        php_printf("Min function: %s/n", fe->common.function_name);    }    if (zend_hash_minmax(EG(function_table), fname_compare,                1, (void **)&fe) == SUCCESS) {        php_printf("Max function: %s/n", fe->common.function_name);    }}

还有一个进行哈希比较的函数:int zend_hash_compare(HashTable *hta, HashTable *htb,
compare_func_t compar, zend_bool ordered TSRMLS_DC);先比较哈希表的个数,哪个多哪个大。如果一样多的,就每个元素去比较。

另外还有一个专门的排序函数:
typedef void (*sort_func_t)(void **Buckets, size_t numBuckets,            size_t sizBucket, compare_func_t comp TSRMLS_DC);int zend_hash_sort(HashTable *ht, sort_func_t sort_func,        compare_func_t compare_func, int renumber TSRMLS_DC);
一般就用zend_qsort作为sort_func就够了。renumber这个参数如果设为1的话,那么就会抛弃原有的索引键值关系,赋予新的数字键值。zend_hash_sort(target_hash, zend_qsort,array_data_compare, 1 TSRMLS_CC);array_data_compare是一个返回compare_func_t类型数据的函数,它将按照HashTable中zval*值的大小进行排序。


(免责声明:文章内容如涉及作品内容、版权和其它问题,请及时与我们联系,我们将在第一时间删除内容,文章内容仅供参考)

人气推荐

知识阅读

精彩推荐

  • 游戏
  • 软件
查看更多>>