### 7.7.6 數組操作
#### 7.7.6.1 創建數組
創建一個新的HashTable分為兩步:首先是分配zend_array內存,這個可以通過`ZVAL_NEW_ARR()`宏分配,也可以自己直接分配;然后初始化數組,通過`zend_hash_init()`宏完成,如果不進行初始化數組將無法使用。
```c
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent) \
_zend_hash_init((ht), (nSize), (pDestructor), (persistent) ZEND_FILE_LINE_CC)
```
* __ht:__ 數組地址HashTable*,如果內部使用可以直接通過emalloc分配
* __nSize:__ 初始化大小,只是參考值,這個值會被對齊到2^n,最小為8
* __pHashFunction:__ 無用,設置為NULL即可
* __pDestructor:__ 刪除或更新數組元素時會調用這個函數對操作的元素進行處理,比如將一個字符串插入數組,字符串的refcount增加,刪除時不是簡單的將元素的Bucket刪除就可以了,還需要對其refcount進行處理,這個函數就是進行清理工作的
* __persistent:__ 是否持久化
示例:
```c
zval array;
uint32_t size;
ZVAL_NEW_ARR(&array);
zend_hash_init(Z_ARRVAL(array), size, NULL, ZVAL_PTR_DTOR, 0);
```
#### 7.7.6.2 插入、更新元素
數組元素的插入、更新主要有三種情況:key為zend_string、key為普通字符串、key為數值索引,相關的宏及函數:
```c
// 1) key為zend_string
//插入或更新元素,會增加key的refcount
#define zend_hash_update(ht, key, pData) \
_zend_hash_update(ht, key, pData ZEND_FILE_LINE_CC)
//插入或更新元素,當Bucket類型為indirect時,將pData更新至indirect的值,而不是更新Bucket
#define zend_hash_update_ind(ht, key, pData) \
_zend_hash_update_ind(ht, key, pData ZEND_FILE_LINE_CC)
//添加元素,與zend_hash_update()類似,不同的地方在于如果元素已經存在則不會更新
#define zend_hash_add(ht, key, pData) \
_zend_hash_add(ht, key, pData ZEND_FILE_LINE_CC)
//直接插入元素,不管key存在與否,如果存在也不覆蓋原來元素,而是當做哈希沖突處理,所有會出現一個數組中key相同的情況,慎用!!!
#define zend_hash_add_new(ht, key, pData) \
_zend_hash_add_new(ht, key, pData ZEND_FILE_LINE_CC)
// 2) key為普通字符串:char*
//與上面幾個對應,這里的key為普通字符串,會自動生成zend_string的key
#define zend_hash_str_update(ht, key, len, pData) \
_zend_hash_str_update(ht, key, len, pData ZEND_FILE_LINE_CC)
#define zend_hash_str_update_ind(ht, key, len, pData) \
_zend_hash_str_update_ind(ht, key, len, pData ZEND_FILE_LINE_CC)
#define zend_hash_str_add(ht, key, len, pData) \
_zend_hash_str_add(ht, key, len, pData ZEND_FILE_LINE_CC)
#define zend_hash_str_add_new(ht, key, len, pData) \
_zend_hash_str_add_new(ht, key, len, pData ZEND_FILE_LINE_CC)
// 3) key為數值索引
//插入元素,h為數值
#define zend_hash_index_add(ht, h, pData) \
_zend_hash_index_add(ht, h, pData ZEND_FILE_LINE_CC)
//與zend_hash_add_new()類似
#define zend_hash_index_add_new(ht, h, pData) \
_zend_hash_index_add_new(ht, h, pData ZEND_FILE_LINE_CC)
//更新第h個元素
#define zend_hash_index_update(ht, h, pData) \
_zend_hash_index_update(ht, h, pData ZEND_FILE_LINE_CC)
//使用自動索引值
#define zend_hash_next_index_insert(ht, pData) \
_zend_hash_next_index_insert(ht, pData ZEND_FILE_LINE_CC)
#define zend_hash_next_index_insert_new(ht, pData) \
_zend_hash_next_index_insert_new(ht, pData ZEND_FILE_LINE_CC)
```
#### 7.7.6.3 查找元素
```c
//根據zend_string key查找數組元素
ZEND_API zval* ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key);
//根據普通字符串key查找元素
ZEND_API zval* ZEND_FASTCALL zend_hash_str_find(const HashTable *ht, const char *key, size_t len);
//獲取數值索引元素
ZEND_API zval* ZEND_FASTCALL zend_hash_index_find(const HashTable *ht, zend_ulong h);
//判斷元素是否存在
ZEND_API zend_bool ZEND_FASTCALL zend_hash_exists(const HashTable *ht, zend_string *key);
ZEND_API zend_bool ZEND_FASTCALL zend_hash_str_exists(const HashTable *ht, const char *str, size_t len);
ZEND_API zend_bool ZEND_FASTCALL zend_hash_index_exists(const HashTable *ht, zend_ulong h);
//獲取數組元素數
#define zend_hash_num_elements(ht) \
(ht)->nNumOfElements
//與zend_hash_num_elements()類似,會有一些特殊處理
ZEND_API uint32_t zend_array_count(HashTable *ht);
```
#### 7.7.6.4 刪除元素
```c
//刪除key
ZEND_API int ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key);
//與zend_hash_del()類似,不同地方是如果元素類型為indirect則同時銷毀indirect的值
ZEND_API int ZEND_FASTCALL zend_hash_del_ind(HashTable *ht, zend_string *key);
ZEND_API int ZEND_FASTCALL zend_hash_str_del(HashTable *ht, const char *key, size_t len);
ZEND_API int ZEND_FASTCALL zend_hash_str_del_ind(HashTable *ht, const char *key, size_t len);
ZEND_API int ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h);
ZEND_API void ZEND_FASTCALL zend_hash_del_bucket(HashTable *ht, Bucket *p);
```
#### 7.7.6.5 遍歷
數組遍歷類似foreach的用法,在擴展中可以通過如下的方式遍歷:
```c
zval *val;
ZEND_HASH_FOREACH_VAL(ht, val) {
...
} ZEND_HASH_FOREACH_END();
```
遍歷過程中會把數組元素賦值給val,除了上面這個宏還有很多其他用于遍歷的宏,這里列幾個比較常用的:
```c
//遍歷獲取所有的數值索引
#define ZEND_HASH_FOREACH_NUM_KEY(ht, _h) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h;
//遍歷獲取所有的key
#define ZEND_HASH_FOREACH_STR_KEY(ht, _key) \
ZEND_HASH_FOREACH(ht, 0); \
_key = _p->key;
//上面兩個的聚合
#define ZEND_HASH_FOREACH_KEY(ht, _h, _key) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h; \
_key = _p->key;
//遍歷獲取數值索引key及value
#define ZEND_HASH_FOREACH_NUM_KEY_VAL(ht, _h, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h; \
_val = _z;
//遍歷獲取key及value
#define ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _key, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_key = _p->key; \
_val = _z;
#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h; \
_key = _p->key; \
_val = _z;
```
#### 7.7.6.6 其它操作
```c
//合并兩個數組,將source合并到target,overwrite為元素沖突時是否覆蓋
#define zend_hash_merge(target, source, pCopyConstructor, overwrite) \
_zend_hash_merge(target, source, pCopyConstructor, overwrite ZEND_FILE_LINE_CC)
//導出數組
ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source);
```
```c
#define zend_hash_sort(ht, compare_func, renumber) \
zend_hash_sort_ex(ht, zend_sort, compare_func, renumber)
```
數組排序,compare_func為typedef int (*compare_func_t)(const void *, const void *),需要自己定義比較函數,參數類型為Bucket*,renumber表示是否更改鍵值,如果為1則會在排序后重新生成各元素的h。PHP中的sort()、rsort()、ksort()等都是基于這個函數實現的。
#### 7.7.6.7 銷毀數組
```c
ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);
```
- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考