程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> php的擴展與嵌入--php擴展中的數組和哈希表2

php的擴展與嵌入--php擴展中的數組和哈希表2

編輯:關於PHP編程

接著上面一節,繼續說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*值的大小進行排序。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved