# 8.2 使用HashTable與{數組}
Zend把與HashTable有關的API分成了好幾類以便于我們尋找,這些API的返回值大多都是常量SUCCESS或者FAILURE。
### 創建HashTable
> 下面在介紹函數原型的時候都使用了ht名稱,但是我們在編寫擴展的時候,
> 一定不要使用這個名稱,因為一些PHP宏展開后會聲明這個名稱的變量,
> 進而引發命名沖突。
創建并初始化一個HashTable非常簡單,只要使用zend_hash_init函數即可,它的定義如下:
````c
int zend_hash_init(
HashTable *ht,
uint nSize,
hash_func_t pHashFunction,
dtor_func_t pDestructor,
zend_bool persistent
);
````
* **\*ht是指針**,指向一個HashTable,我們既可以&一個已存在的HashTable變量,
也可以通過emalloc()、pemalloc()等函數來直接申請一塊內存,
不過最常用的方法還是用ALLOC_HASHTABLE(ht)宏來讓內核自動的替我們完成這項工作。
ALLOC_HASHTABLE(ht)所做的工作相當于ht = emalloc(sizeof(HashTable));
* **nSize**代表著這個HashTable可以擁有的元素的最大數量(HashTable能夠包含任意數量的元素,
這個值只是為了提前申請好內存,提高性能,省的不停的進行rehash操作)。
在我們添加新的元素時,這個值會根據情況決定是否自動增長,有趣的是,
這個值永遠都是2的次方,如果你給它的值不是一個2的次方的形式,
那它將自動調整成大于它的最小的2的次方值。
它的計算方法就像這樣:nSize = pow(2, ceil(log(nSize, 2)));
* **pHashFunction**是早期的Zend Engine中的一個參數,為了兼容沒有去掉它,
但它已經沒有用處了,所以我們直接賦成NULL就可以了。在原來,
它其實是一個鉤子,用來讓用戶自己hook一個散列函數,替換php默認的DJBX33A算法實現。
* **pDestructor**也代表著一個回調函數,當我們刪除或者修改HashTable中其中一個元素時候便會調用,
它的函數原型必須是這樣的:void method_name(void *pElement);這里的*pElement是一個指針,指向HashTable中那么將要被刪除或者修改的那個數據,而數據的類型往往也是個指針。
* **persistent**是最后一個參數,它的含義非常簡單。
如果它為true,那么這個HashTable將永遠存在于內存中,而不會在RSHUTDOWN階段自動被注銷掉。
此時第一個參數ht所指向的地址必須是通過pemalloc()函數申請的。
舉個例子,PHP內核在每個Request請求的頭部都調用了這個函數來初始化symbol_table。
````c
zend_hash_init(&EG(symbol_table), 50, NULL, ZVAL_PTR_DTOR, 0);
//#define ZVAL_PTR_DTOR (void (*)(void *)) zval_ptr_dtor_wrapper
````
如你所見,每個元素在從符號表里刪除的時候(比如執行"<?php unset($foo);"操作),都會觸發ZVAL_PTR_DTOR宏代表的函數來對其進行與引用計數有關的操作。
因為50不是2的整數冪形式,所以它會在函數執行時被調成成64。
### 添加&&修改
我們有四個常用的函數來完成這項操作,它們的原型分別如下:
````c
int zend_hash_add(
HashTable *ht, //待操作的ht
char *arKey, //索引,如"my_key"
uint nKeyLen, //字符串索引的長度,如6
void **pData, //要插入的數據,注意它是void **類型的。int *p,i=1;p=&i,pData=&p;。
uint nDataSize,
void *pDest //如果操作成功,則pDest=*pData;
);
int zend_hash_update(
HashTable *ht,
char *arKey,
uint nKeyLen,
void *pData,
uint nDataSize,
void **pDest
);
int zend_hash_index_update(
HashTable *ht,
ulong h,
void *pData,
uint nDataSize,
void **pDest
);
int zend_hash_next_index_insert(
HashTable *ht,
void *pData,
uint nDataSize,
void **pDest
);
````
前兩個函數用戶添加帶字符串索引的數據到HashTable中,就像我們在PHP中使用的那樣:$foo['bar'] = 'baz';用C來完成便是:
````c
zend_hash_add(fooHashTbl, "bar", sizeof("bar"), &barZval, sizeof(zval*), NULL);
````
zend_hash_add()和zend_hash_update()唯一的區別就是如果這個key已經存在了,那么zend_hash_add()將返回FAILURE,而不會修改原有數據。
接下來的兩個函數用于像HT中添加數字索引的數據,zend_hash_next_index_insert()函數則不需要索引值參數,而是自己直接計算出下一個數字索引值.
但是如果我們想獲取下一個元素的數字索引值,也是有辦法的,可以使用zend_hash_next_free_element()函數:
````c
ulong nextid = zend_hash_next_free_element(ht);
zend_hash_index_update(ht, nextid, &data, sizeof(data), NULL);
````
<code>所有這些函數中,如果pDest不為NULL,內核便會修改其值為被操作的那個元素的地址。在下面的代碼中這個參數也有同樣的功能。</code>
### 查找
因為HashTable中有兩種類型的索引值,所以需要兩個函數來執行find操作。
````c
int zend_hash_find(HashTable *ht, char *arKey, uint nKeyLength,void **pData);
int zend_hash_index_find(HashTable *ht, ulong h, void **pData);
````
第一種就是我們處理PHP語言中字符串索引數組時使用的,第二種是我們處理PHP語言中數字索引數組使用的。Recall from Chapter 2 that when data is added to a HashTable, a new memory block is allocated for it and the data passed in is copied; when the data is extracted back out it is the pointer to that data which is returned. The following code fragment adds data1 to the HashTable, and then extracts it back out such that at the end of the routine, *data2 contains the same contents as *data1 even though the pointers refer to different memory addresses.
````c
void hash_sample(HashTable *ht, sample_data *data1)
{
sample_data *data2;
ulong targetID = zend_hash_next_free_element(ht);
if (zend_hash_index_update(ht, targetID,
data1, sizeof(sample_data), NULL) == FAILURE) {
/* Should never happen */
return;
}
if(zend_hash_index_find(ht, targetID, (void **)&data2) == FAILURE) {
/* Very unlikely since we just added this element */
return;
}
/* data1 != data2, however *data1 == *data2 */
}
````
除了讀取,我們還需要檢測某個key是否存在:
````c
int zend_hash_exists(HashTable *ht, char *arKey, uint nKeyLen);
int zend_hash_index_exists(HashTable *ht, ulong h);
````
這兩個函數返回SUCCESS或者FAILURE,分別代表著是否存在:
````c
if( zend_hash_exists(EG(active_symbol_table),"foo", sizeof("foo")) == SUCCESS )
{
/* $foo is set */
}
else
{
/* $foo does not exist */
}
````
### 提速!
````c
ulong zend_get_hash_value(char *arKey, uint nKeyLen);
````
當我們需要對同一個字符串的key進行許多操作時候,比如先檢測有沒,然后插入,然后修改等等,這時我們便可以使用zend_get_hash_value函數來對我們的操作進行加速!這個函數的返回值可以和quick系列函數使用,達到加速的目的(就是不再重復計算這個字符串的散列值,而直接使用已準備好的)!
````c
int zend_hash_quick_add(
HashTable *ht,
char *arKey,
uint nKeyLen,
ulong hashval,
void *pData,
uint nDataSize,
void **pDest
);
int zend_hash_quick_update(
HashTable *ht,
char *arKey,
uint nKeyLen,
ulong hashval,
void *pData,
uint nDataSize,
void **pDest
);
int zend_hash_quick_find(
HashTable *ht,
char *arKey,
uint nKeyLen,
ulong hashval,
void **pData
);
int zend_hash_quick_exists(
HashTable *ht,
char *arKey,
uint nKeyLen,
ulong hashval
);
````
雖然很意外,但你還是要接受沒有zend_hash_quick_del()這個函數。quick類函數會在下面這種場合中用到:
````c
void php_sample_hash_copy(HashTable *hta, HashTable *htb,char *arKey, uint nKeyLen TSRMLS_DC)
{
ulong hashval = zend_get_hash_value(arKey, nKeyLen);
zval **copyval;
if (zend_hash_quick_find(hta, arKey, nKeyLen,hashval, (void**)©val) == FAILURE)
{
//標明不存在這個索引
return;
}
//這個zval已經被其它的Hashtable使用了,這里我們進行引用計數操作。
(*copyval)->refcount__gc++;
zend_hash_quick_update(htb, arKey, nKeyLen, hashval,copyval, sizeof(zval*), NULL);
}
````
### 復制與合并(Copy And Merge)
在PHP語言中,我們經常需要進行數組間的Copy與Merge操作,所以php語言中的數組在C語言中的實現HashTable也肯定會經常碰到這種情況。為了簡化這一類操作,內核中早已準備好了相應的API供我們使用。
````c
void zend_hash_copy(
HashTable *target,
HashTable *source,
copy_ctor_func_t pCopyConstructor,
void *tmp,
uint size
);
````
* \*source中的所有元素都會通過pCopyConstructor函數Copy到*target中去,我們還是以PHP語言中的數組舉例,pCopyConstructor這個hook使得我們可以在copy變量的時候對他們的ref_count進行加一操作。target中原有的與source中索引位置的數據會被替換掉,而其它的元素則會被保留,原封不動。
* tmp參數是為了兼容PHP4.0.3以前版本的,現在賦值為NULL即可。
* size參數代表每個元素的大小,對于PHP語言中的數組來說,這里的便是sizeof(zval*)了。
````c
void zend_hash_merge(
HashTable *target,
HashTable *source,
copy_ctor_func_t pCopyConstructor,
void *tmp,
uint size,
int overwrite
);
````
zend_hash_merge()與zend_hash_copy唯一的不同便是多了個int類型的overwrite參數,當其值非0的時候,兩個函數的工作是完全一樣的;如果overwrite參數為0,則zend_hash_merge函數就不會對target中已有索引的值進行替換了。
````c
typedef zend_bool (*merge_checker_func_t)(HashTable *target_ht,void *source_data, zend_hash_key *hash_key, void *pParam);
void zend_hash_merge_ex(
HashTable *target,
HashTable *source,
copy_ctor_func_t pCopyConstructor,
uint size,
merge_checker_func_t pMergeSource,
void *pParam
);
````
這個函數又繁瑣了些,與zend_hash_copy相比,其多了兩個參數,多出來的pMergeSoure回調函數允許我們選擇性的進行merge,而不是全都merge。The final form of this group of functions allows for selective copying using a merge checker function. The following example shows zend_hash_merge_ex() in use to copy only the associatively indexed members of the source HashTable (which happens to be a userspace variable array):
````c
zend_bool associative_only(HashTable *ht, void *pData,zend_hash_key *hash_key, void *pParam)
{
//如果是字符串索引
return (hash_key->arKey && hash_key->nKeyLength);
}
void merge_associative(HashTable *target, HashTable *source)
{
zend_hash_merge_ex(target, source, zval_add_ref,sizeof(zval*), associative_only, NULL);
}
````
### 遍歷
在PHP語言中,我們有很多方法來遍歷一個數組,對于數組的本質HashTable,我們也有很多辦法來對其進行遍歷操作。首先最簡單的一種辦法便是使用一種與PHP語言中foreach語句功能類似的函數——zend_hash_apply,它接收一個回調函數,并將HashTable的每一個元素都傳遞給它。
````c
typedef int (*apply_func_t)(void *pDest TSRMLS_DC);
void zend_hash_apply(HashTable *ht,apply_func_t apply_func TSRMLS_DC);
````
下面是另外一種遍歷函數:
````c
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);
````
通過上面的函數可以在執行遍歷時向回調函數傳遞任意數量的值,這在一些diy操作中非常有用。
上述函數對傳給它們的回調函數的返回值有一個共同的約定,詳細介紹下下表:
````c
表格 8.1. 回調函數的返回值
Constant Meaning
ZEND_HASH_APPLY_KEEP 結束當前請求,進入下一個循環。與PHP語言foreach語句中的一次循環執行完畢或者遇到continue關鍵字的作用一樣。
ZEND_HASH_APPLY_STOP 跳出,與PHP語言foreach語句中的break關鍵字的作用一樣。
ZEND_HASH_APPLY_REMOVE 刪除當前的元素,然后繼續處理下一個。相當于在PHP語言中:unset($foo[$key]);continue;
````
我們來一下PHP語言中的foreach循環:
````php
<?php
foreach($arr as $val) {
echo "The value is: $val\n";
}
?>
````
那我們的回調函數在C語言中應該這樣寫:
````c
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;
}
````
遍歷我們的HashTable:
````c
//生成一個名為arrht、元素為zval*類型的HashTable
zend_hash_apply(arrht, php_sample_print_zval TSRMLS_CC);
````
> 再次提醒,保存在HashTable中的元素并不是真正的最終變量,而是指向它的一個指針。我們的上面的遍歷函數接收的是一個zval**類型的參數。
````c
typedef int (*apply_func_args_t)(void *pDest,int num_args, va_list args, zend_hash_key *hash_key);
void zend_hash_apply_with_arguments(HashTable *ht,apply_func_args_t apply_func, int numargs, ...);
````
為了能在遍歷時同時接收索引的值,我們必須使用第三種形式的zend_hash_apply!就像PHP語言中這樣的功能:
````php
<?php
foreach($arr as $key => $val)
{
echo "The value of $key is: $val\n";
}
?>
````
為了配合zend_hash_apply_with_arguments()函數,我們需要對我們的遍歷執行函數做一下小小的改動,使其接受索引作為一個參數:
````c
int php_sample_print_zval_and_key(zval **val,int num_args,va_list args,zend_hash_key *hash_key)
{
//重新copy一個zval,防止破壞原數據
zval tmpcopy = **val;
/* tsrm_ls is needed by output functions */
TSRMLS_FETCH();
zval_copy_ctor(&tmpcopy);
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");
//毀尸滅跡
zval_dtor(&tmpcopy);
/* continue; */
return ZEND_HASH_APPLY_KEEP;
}
````
執行遍歷:
````c
zend_hash_apply_with_arguments(arrht,php_sample_print_zval_and_key, 0);
````
<div class="tip-common">這個函數通過C語言中的可變參數特性來接收參數。This particular example required no arguments to be passed; for information on extracting variable argument lists from va_list args, see the POSIX documentation pages for va_start(), va_arg(), and va_end().</div>
當我們檢查這個hash_key是字符串類型還是數字類型時,是通過nKeyLength屬性來檢測的,而不是arKey屬性。這是因為內核有時候會留在arKey屬性里些臟數據,但nKeyLength屬性是安全的,可以安全的使用。甚至對于空字符串索引,它也照樣能處理。比如:$foo[''] ="Bar";索引的值是NULL字符,但它的長度卻是包括最后這個NULL字符的,所以為1。
### 向前遍歷HashTable
有時我們希望不用回調函數也能遍歷一個數組的數據,為了實現這個功能,內核特意的為每個HashTable加了個屬性:The internal pointer(內部指針)。</p?
我們還是以PHP語言中的數組舉例,有以下函數來處理它所對應的那個HashTable的內部指針:reset(), key(), current(), next(), prev(), each(), and end()。 <p>
````c
<?php
$arr = array('a'=>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內核中有一組操作HashTable的功能與以上函數功能類似的函數:
````c
/* 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);
/* 其他的...... */
int zend_hash_get_current_key_type(HashTable *ht);
int zend_hash_has_more_elements(HashTable *ht);
````
<div class="tip-common">PHP語言中的next()、prev()、end()函數在移動完指針之后,都通過調用zend_hash_get_current_data()函數來獲取當前所指的元素并返回。而each()雖然和next()很像,卻是使用zend_hash_get_current_key()函數的返回值來作為它的返回值。</div>
現在我們用另外一種方法來實現上面的foreach:
````c
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);
if (zend_hash_get_current_data(arrht, (void**)&ppzval) == FAILURE)
{
/* Should never actually fail
* since the key is known to exist. */
continue;
}
//重新copy一個zval,防止破壞原數據
tmpcopy = **ppzval;
zval_copy_ctor(&tmpcopy);
INIT_PZVAL(&tmpcopy);
convert_to_string(&tmpcopy);
/* Output */
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");
/* Toss out old copy */
zval_dtor(&tmpcopy);
}
}
````
上面的代碼你應該都能看懂了,唯一還沒接觸到的可能是zend_hash_get_current_key()函數的返回值,它的返回值見表8.2。
````c
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中的內部指針已經移動到尾部,不指向任何元素。
````
### Preserving the Internal Pointer
在我們遍歷一個HashTable時,一般是很難陷入死循環的。When iterating through a HashTable, particularly one containing userspace variables, it's not uncommon to encounter circular references, or at least self-overlapping loops. If one iteration context starts looping through a HashTable and the internal pointer reachesfor examplethe halfway mark, a subordinate iterator starts looping through the same HashTable and would obliterate the current internal pointer position, leaving the HashTable at the end when it arrived back at the first loop.
The way this is resolvedboth within the zend_hash_apply implementation and within custom move forward usesis to supply an external pointer in the form of a HashPosition variable.
Each of the zend_hash_*() functions listed previously has a zend_hash_*_ex() counterpart that accepts one additional parameter in the form of a pointer to a HashPostion data type. Because the HashPosition variable is seldom used outside of a short-lived iteration loop, it's sufficient to declare it as an immediate variable. You can then dereference it on usage such as in the following variation on the php_sample_print_var_hash() function you saw earlier:
````c
void php_sample_print_var_hash(HashTable *arrht)
{
HashPosition pos;
for(zend_hash_internal_pointer_reset_ex(arrht, &pos);
zend_hash_has_more_elements_ex(arrht, &pos) == SUCCESS;
zend_hash_move_forward_ex(arrht, &pos)) {
char *key;
uint keylen;
ulong idx;
int type;
zval **ppzval, tmpcopy;
type = zend_hash_get_current_key_ex(arrht,
&key, &keylen,
&idx, 0, &pos);
if (zend_hash_get_current_data_ex(arrht,
(void**)&ppzval, &pos) == FAILURE) {
/* Should never actually fail
* since the key is known to exist. */
continue;
}
/* Duplicate the zval so that
* the original's contents are not destroyed */
tmpcopy = **ppzval;
zval_copy_ctor(&tmpcopy);
/* Reset refcount & Convert */
INIT_PZVAL(&tmpcopy);
convert_to_string(&tmpcopy);
/* Output */
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");
/* Toss out old copy */
zval_dtor(&tmpcopy);
}
}
With these very slight additions, the HashTable's true internal pointer is preserved in whatever state it was initially in on entering the function. When it comes to working with internal pointers of userspace variable HashTables (that is, arrays), this extra step will very likely make the difference between whether the scripter's code works as expected.
### 刪除
內核中一共預置了四個刪除HashTable元素的函數,頭兩個是用戶刪除某個確定索引的數據:
<code c>
int zend_hash_del(HashTable *ht, char *arKey, uint nKeyLen);
int zend_hash_index_del(HashTable *ht, ulong h);
````
它們兩個分別用來刪除字符串索引和數字索引的數據,操作完成后都返回SUCCESS或者FAILURE表示成功or失敗。
回顧一下最上面的敘述,當一個元素被刪除時,會激活HashTable的destructor回調函數。
````c
void zend_hash_clean(HashTable *ht);
void zend_hash_destroy(HashTable *ht);
````
前者用于將HashTable中的元素全部刪除,而后者是將這個HashTable自身也毀滅掉。
現在讓我們來完整的回顧一下HashTable的創建、添加、刪除操作。
````c
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;
}
//填充數據
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) {
/* Silently skip failed additions */
zval_ptr_dtor(&value);
}
}
//完成工作
process_hashtable(ht);
//毀尸滅跡
zend_hash_destroy(ht);
//釋放ht 為什么不在destroy里free呢,求解釋!
FREE_HASHTABLE(ht);
return SUCCESS;
}
````
### 排序、比較and Going to the Extreme(s)
針對HashTable操作的Zend Api中有很多都需要回調函數。首先讓我們來處理一下對HashTable中元素大小比較的問題:
````c
typedef int (*compare_func_t)(void *a, void *b TSRMLS_DC);
````
這很像PHP語言中usort函數需要的參數,它將比較兩個值*a與*b,如果*a>*b,則返回1,相等則返回0,否則返回-1。下面是zend_hash_minmax函數的聲明,它就需要我們上面聲明的那個類型的函數作為回調函數:
int zend_hash_minmax(HashTable *ht, compare_func_t compar,int flag, void **pData TSRMLS_DC);
這個函數的功能我們從它的名稱中便能肯定,它用來比較HashTable中的元素大小。如果flag==0則返回最小值,否則返回最大值!
下面讓我們來利用這個函數來對用戶端定義的所有函數根據函數名找到最大值與最小值(大小寫不敏感~)。
````c
//先定義一個比較函數,作為zend_hash_minmax的回調函數。
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);
}
}
````
zend_hash_compare()也許要回調函數,它的功能是將HashTable看作一個整體與另一個HashTable做比較,如果前者大于后者返回1,相等返回0,否則返回-1。
<p>int zend_hash_compare(HashTable *hta, HashTable *htb,compare_func_t compar, zend_bool ordered TSRMLS_DC);
默認情況下它往往是先判斷各個HashTable元素的個數,個數多的最大!
如果兩者的元素一樣多,然后就比較它們各自的第一個元素,If the ordered flag is set, it compares keys/indices with the first element of htb string keys are compared first on length, and then on binary sequence using memcmp(). If the keys are equal, the value of the element is compared with the first element of htb using the comparison callback function.
If the ordered flag is not set, the data portion of the first element of hta is compared against the element with a matching key/index in htb using the comparison callback function. If no matching element can be found for htb, then hta is considered greater than htb and 1 is returned.
If at the end of a given loop, hta and htb are still considered equal, comparison continues with the next element of hta until a difference is found or all elements have been exhausted, in which case 0 is returned.
另外一個重要的需要回調函數的API便是排序函數,它需要的回調函數形式是這樣的:
````c
typedef void (*sort_func_t)(void **Buckets, size_t numBuckets,size_t sizBucket, compare_func_t comp TSRMLS_DC);
````
This callback will be triggered once, and receive a vector of all the Buckets (elements) in the HashTable as a series of pointers. These Buckets may be swapped around within the vector according to the sort function's own logic with or without the use of the comparison callback. In practice, sizBucket will always be sizeof(Bucket*).
Unless you plan on implementing your own alternative bubblesort method, you won't need to implement a sort function yourself. A predefined sort methodzend_qsortalready exists for use as a callback to zend_hash_sort() leaving you to implement the comparison function only.
````c
int zend_hash_sort(HashTable *ht, sort_func_t sort_func,compare_func_t compare_func, int renumber TSRMLS_DC);
````
最后一個參數如果為TRUE,則會拋棄HashTable中原有的索引-鍵關系,將對排列好的新值賦予新的數字鍵值。PHP語言中的sort函數實現如下:
````c
zend_hash_sort(target_hash, zend_qsort,array_data_compare, 1 TSRMLS_CC);
````
array_data_compare是一個返回compare_func_t類型數據的函數,它將按照HashTable中zval*值的大小進行排序。
## links
* 8.1 [數組(C中的)與鏈表](<8.1.md>)
* 8.3 [在內核中操作PHP語言中數組](<8.3.md>)
- about
- 開始閱讀
- 目錄
- 1 PHP的生命周期
- 1.讓我們從SAPI開始
- 2.PHP的啟動與終止
- 3.PHP的生命周期
- 4.線程安全
- 5.小結
- 2 PHP變量在內核中的實現
- 1. 變量的類型
- 2. 變量的值
- 3. 創建PHP變量
- 4. 變量的存儲方式
- 5. 變量的檢索
- 6. 類型轉換
- 7. 小結
- 3 內存管理
- 1. 內存管理
- 2. 引用計數
- 3. 總結
- 4 動手編譯PHP
- 1. 編譯前的準備
- 2. PHP編譯前的config配置
- 3. Unix/Linux平臺下的編譯
- 4. 在Win32平臺上編譯PHP
- 5. 小結
- 5 Your First Extension
- 1. 一個擴展的基本結構
- 2. 編譯我們的擴展
- 3. 靜態編譯
- 4. 編寫函數
- 5. 小結
- 6 函數返回值
- 1. 一個特殊的參數:return_value
- 2. 引用與函數的執行結果
- 3. 小結
- 7 函數的參數
- 1. zend_parse_parameters
- 2. Arg Info 與類型綁定
- 3. 小結
- 8 使用HashTable與{數組}
- 1. 數組(C中的)與鏈表
- 2. 操作HashTable的API
- 3. 在內核中操作PHP語言中數組
- 4. 小結
- 9 PHP中的資源類型
- 1. 復合類型的數據——{資源}
- 2. Persistent Resources
- 3. {資源}自有的引用計數
- 4. 小結
- 10 PHP中的面向對象(一)
- 1. zend_class_entry
- 2. 定義一個類
- 3. 定義一個接口
- 4. 類的繼承與接口的實現
- 5. 小結
- 11 PHP中的面向對象(二)
- 1. 生成對象的實例與調用方法
- 2. 讀寫對象的屬性
- 3. 小結
- 12 啟動與終止的那點事
- 2. 小結
- 1. 關于生命周期
- 2. MINFO與phpinfo
- 3. 常量
- 4. PHP擴展中的全局變量
- 5. PHP語言中的超級全局變量
- 6. 小結
- 13 INI設置
- 1. 聲明和訪問ini設置
- 2. 小結
- 2. 小結
- 14 流式訪問
- 1. 概覽
- 2. 打開流
- 3. 訪問流
- 4. 靜態資源操作
- 5. 小結
- 15 流的實現
- 1. php流的表象之下
- 2. 包裝器操作
- 3. 實現一個包裝器
- 4. 操縱
- 5. 檢查
- 6. 小結
- 16 有趣的流
- 1. 上下文
- 2. 過濾器
- 3. 小結
- 17 配置和鏈接
- 1. autoconf
- 2. 庫的查找
- 3. 強制模塊依賴
- 4. Windows方言
- 5. 小結
- 18 擴展生成
- 1. ext_skel
- 2. PECL_Gen
- 3. 小結
- 19 設置宿主環境
- 1. 嵌入式SAPI
- 2. 構建并編譯一個宿主應用
- 3. 通過嵌入包裝重新創建cli
- 4. 老技術新用
- 5. 小結
- 20 高級嵌入式
- 1. 回調到php中
- 2. 錯誤處理
- 3. 初始化php
- 4. 覆寫INI_SYSTEM和INI_PERDIR選項
- 5. 捕獲輸出
- 6. 同時擴展和嵌入
- 7. 小結
- 約定