antonio's blog

antonio's blog


Блог о всяком разном, связанном с разработкой ПО. Пишу редко, когда есть время и желание.

Anton Dobkin
Author

Share


Tags


Разработка расширений для PHP. Хэш-таблицы (HashTables). Часть 3

Anton DobkinAnton Dobkin

В данной части будут рассмотрены способы перебора(итерации) хэш-таблиц. Для перебора элементов хэш-таблиц существует два подхода. Первый - это “ручной” перебор с перемещением указателя позиции, второй - автоматический перебор с применением callback-функций к каждому элементу хэш-таблицы.

Итерация с перемещением указателя позиции

Для перебора элементов хэш-таблиц существует два подхода. Первый - это "ручной" перебор с перемещением указателя позиции, второй - автоматический перебор с применением callback-функций к каждому элементу хэш-таблицы.

Для прохода по элементам хэш-таблицы с перемещением в Zend Engine используется указатель позиции, который имеет тип HashPosition. Указатель позиции может быть внутренний или внешний. Ниже дается описание функций для перебора элементов хэш-таблиц:

void zend_hash_internal_pointer_reset(HashTable *ht) - Устанавливает внутренний указатель на его первый элемент хэш-таблицы

Параметры:

Параметры:

Функция возвращает одно из следующих значений:

КонстантаОписание
HASH_KEY_IS_STRING Функция возвращает данное значение, если текущий элемент имеет ассоциативный индекс. При этом название индекса будет занесено в strIdx. Если параметр duplicate имеет не нулевое значение, то название ключа будет c дублировано в strIdx, под строку будет выделен участок памяти, который должен быть освобожден в взывающий программе
HASH_KEY_IS_LONG Функция возвращает данное значение, если текущий элемен имеет числовой индекс. При этом номер индекса будет занесен в numIdx
HASH_KEY_NON_EXISTANT Функция возвращает данное значение, когда внутренний указатель достиг конца хэш-таблице и в таблице нет больше элементов

В функцию необходимо передавать все указатели!

int zend_hash_get_current_data(HashTable *ht, void **pData) - достает текущие, на который ссылается внутренний указатель, данные хэш-таблицы

Параметры:

Функция возвращает SUCCESS если данные были получены, иначе FAILURE

int zend_hash_move_forward(HashTable *ht) - Передвигает внутренний указатель на следующий элемент хэш-таблицы. ht - указатель на хэш-таблицу

Функция возвращает SUCCESS если внутренний указатель был перемещен, иначе FAILURE

int zend_hash_move_backwards(HashTable *ht) - передвигает внутренний указатель на предыдущий элемент хэш-таблицы. ht - указатель на хэш-таблицу

Функция возвращает SUCCESS если внутренний указатель был перемещен, иначе FAILURE

void zend_hash_internal_pointer_end(HashTable *ht) - Устанавливает внутренний указатель на последний элемент хэш-таблицы. ht - указатель на хэш-таблицу

int zend_hash_get_current_key_type(HashTable *ht) - Возвращает тип индекса:

ht - указатель на хэш-таблицу

int zend_hash_has_more_elements(HashTable *ht) - Возвращает SUCCESS если в хэш-таблице есть еще элементы, иначе возвращает FAILURE. ht - указатель на хэш-таблицу

Пример перебора хэш-таблицы с перемещением внутреннего указателя

void foreach_active_symbol_table() {  
    HashTable *at = NULL;
    char *key = NULL;
    unsigned int key_len;
    int key_type;
    unsigned long num_index;
    zval **data = NULL;
    zval tmp;

    at =  EG(active_symbol_table);

    /* Итерация по хэш-таблице с использованием цикла for */
    for(zend_hash_internal_pointer_reset(at);
               zend_hash_has_more_elements(at) == SUCCESS; zend_hash_move_forward(at)) {

        /* Определяет тип индекса элемента */
        key_type = zend_hash_get_current_key(at, &key, &num_index, 0);
        /* Выводим тип индекса элемента и название/значение */
        if(key_type == HASH_KEY_IS_STRING) {
            key_len = strlen(key);
            zend_printf("Found associative index: ");
            ZEND_WRITE(key, key_len);
        } else if(key_type == HASH_KEY_IS_LONG) {
            zend_printf("Found numeric index: %lu", num_index);
        }

        /* Получаем данные и копируем их во временную
           переменную */
        zend_hash_get_current_data(at, (void**)&data);
        tmp = **data;
        zval_copy_ctor(&tmp);

        /* Выставляем счетчик ссылок */
        INIT_PZVAL(&tmp);

        /* Выводим тип и значение данных */
        if( Z_TYPE(tmp) == IS_STRING) {
            zend_printf(", Type: string, Value: ");
            ZEND_WRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
        } else if(Z_TYPE(tmp) == IS_LONG) {
            zend_printf(", Type: int, Value: %ld", Z_LVAL(tmp));
        } else if(Z_TYPE(tmp) == IS_DOUBLE) {
            zend_printf(", Type: double, Value: %f", Z_DVAL(tmp));
        } else if(Z_TYPE(tmp) == IS_NULL) {
            zend_printf(", Type: null, Value: NULL");
        } else if(Z_TYPE(tmp) == IS_ARRAY) {
            zend_printf(", Type: array");
        } else if(Z_TYPE(tmp) == IS_ARRAY) {
            zend_printf(", Type: array");
        } else if(Z_TYPE(tmp) == IS_OBJECT) {
            zend_printf(", Type: object");
        } else if(Z_TYPE(tmp) == IS_RESOURCE) {
            zend_printf(", Type: resource");
        }

        zend_printf("<br /><hr/>");

        /* Уничтожаем временную переменную */
        zval_dtor(&tmp);
    }
}

Данный пример можно переписать с использованием цикла while для перебора:

void foreach_active_symbol_table() {  
    ...
    at =  EG(active_symbol_table);
    zend_hash_internal_pointer_reset(at);

    /* Итерация по хэш-таблице с использованием цикла for */
    while(zend_hash_has_more_elements(at) == SUCCESS) {
        ...
        zval_dtor(&tmp);
        /* Перемещаемся на следующий элемент */
        zend_hash_move_forward(at);
    }
}

Помимо выше приведенных функций также существуют их расширенные аналоги с суффиксом _ex. Данные функции принимают дополнительный параметр pos - внешний указатель позиции, который имеет тип HashPosition. При указании в качестве значения NULL, функции ведут себя аналогично своим аналогам без суффикса _ex, т.е используется внутренний указатель хэш-таблицы, иначе будет использован переданный указатель.

Вот эти функции:

Заглянув в файл Zend/zend_hash.h вы увидите, что функции без суффикса _ex являются макросами-обвертками над одноименными функциями с суффиксами _ex в которые в качестве значения параметра pos передается NULL

#define zend_hash_has_more_elements(ht) \
    zend_hash_has_more_elements_ex(ht, NULL)
#define zend_hash_move_forward(ht) \
    zend_hash_move_forward_ex(ht, NULL)
#define zend_hash_move_backwards(ht) \
    zend_hash_move_backwards_ex(ht, NULL)
#define zend_hash_get_current_key(ht, str_index, num_index, duplicate) \
    zend_hash_get_current_key_ex(ht, str_index, NULL, num_index, duplicate, NULL)
#define zend_hash_get_current_key_type(ht) \
    zend_hash_get_current_key_type_ex(ht, NULL)
#define zend_hash_get_current_data(ht, pData) \
    zend_hash_get_current_data_ex(ht, pData, NULL)
#define zend_hash_internal_pointer_reset(ht) \
    zend_hash_internal_pointer_reset_ex(ht, NULL)
#define zend_hash_internal_pointer_end(ht) \
    zend_hash_internal_pointer_end_ex(ht, NULL)

Пример использования функций с суффиксом _ex:

// Функция отличается от выше приведенных примеров только тем, что используется внешний указатель позиции
void foreach_active_symbol_table() {  
    HashTable *at = NULL;
    char *key = NULL;
    unsigned int key_len;
    int key_type;
    unsigned long num_index;
    zval **data = NULL;
    zval tmp;
    HashPosition pos = NULL;

    at =  EG(active_symbol_table);

    /* Инициализирует внешний указатель и устанавливает его на первый элемент хэш-таблицы */
    zend_hash_internal_pointer_reset_ex(at, pos);

    while(zend_hash_has_more_elements_ex(at, &pos) == SUCCESS) {
        key_type = zend_hash_get_current_key_ex(at, &key, &key_len, &num_index, 0, &pos);
        if(key_type == HASH_KEY_IS_STRING) {
            zend_printf("Found associative index: ");
            ZEND_WRITE(key, key_len);
        } else if(key_type == HASH_KEY_IS_LONG) {
            zend_printf("Found numeric index: %lu", num_index);
        }
        /* Получаем данные и копируем их во временную переменную*/
        zend_hash_get_current_data_ex(at, (void**)&data, &pos);
        tmp = **data;
        zval_copy_ctor(&tmp);

        /* Выставляем счетчик ссылок */
        INIT_PZVAL(&tmp);

        /* Выводим тип и значение данных */
        if( Z_TYPE(tmp) == IS_STRING) {
            zend_printf(", Type: string, Value: ");
            ZEND_WRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
        } else if(Z_TYPE(tmp) == IS_LONG) {
            zend_printf(", Type: int, Value: %ld", Z_LVAL(tmp));
        } else if(Z_TYPE(tmp) == IS_DOUBLE) {
            zend_printf(", Type: double, Value: %f", Z_DVAL(tmp));
        } else if(Z_TYPE(tmp) == IS_NULL) {
            zend_printf(", Type: null, Value: NULL");
        } else if(Z_TYPE(tmp) == IS_ARRAY) {
            zend_printf(", Type: array");
        } else if(Z_TYPE(tmp) == IS_ARRAY) {
            zend_printf(", Type: array");
        } else if(Z_TYPE(tmp) == IS_OBJECT) {
            zend_printf(", Type: object");
        } else if(Z_TYPE(tmp) == IS_RESOURCE) {
            zend_printf(", Type: resource");
        }

        zend_printf("<br /><hr/>");

        /* Уничтожаем временную переменную */
        zval_dtor(&tmp);
        /* Перемещаемся на следующий элемент */
        zend_hash_move_forward_ex(at,&pos);
    }
}

Дополнительны функции для работы с указателем перемещения

Итерация с применением callback-функций

Второй способ прохода по элементам хэш-таблицы - это автоматический перебор всех элементов с применением пользовательской callback-функции к каждому элементу. Данный способ является более простым и отличается от рассмотренного ранее в отсутствии необходимости организовывать цикл для прохода по элементам хэш-таблицы. Для перебора хэш-таблиц таким способом необходимо воспользоваться одной из трех функций Zend Engine:

void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC) - Функция организует проход по всем элементам хэш-таблицы ht и вызывается callback-функцию apply_func передавая в эту функцию в качестве единственного параметра - указатель на текущий элемент

Параметры:

После параметра apply_func всегда необходимо передавать макрос TSRMLS_DC, запятая между параметром и макросом не ставиться

Прототип apply_func:

typedef int (*apply_func_t)(void *pDest TSRMLS_DC)  

где pDest - указатель на текущий элемент

void zend_hash_apply_with_argument(HashTable *ht, apply_func_arg_t apply_func, void *argument TSRMLS_DC) - Данная функция отличается от zend_hash_apply() только тем, что в сallback-функцию можно передать произвольные данные. Функция организует проход по всем элементам хэш-таблицы ht и вызывается callback-функцию apply_func передавая в эту функцию в качестве первого параметра - указатель на текущий элемент, а в качестве второго параметра произвольные данные через пареметр argument

Параметры:

После параметра argument всегда необходимо передавать макрос TSRMLS_Dc, запятая между параметром и макросом не ставиться

Прототип apply_func:

typedef int (*apply_func_t)(void *pDest, void * argument TSRMLS_DC)  

где pDest - указатель на текущий элемент, а atgument - произвольные данные

void zend_hash_apply_with_arguments(HashTable *ht TSRMLS_DC, apply_func_args_t apply_func, int num_args, ...) - Данная функция отличается от двух предыдущих только тем, что в сallback-функцию можно передать произвольное кол-во параметров. apply_func - callback-функция с переменным числом аргументов. Функция организует проход по всем элементам хэш-таблицы ht и вызывается callback-функцию apply_func передавая в эту функцию в качестве первого параметра - указатель на текущий элемент, в качестве второго параметра число указывающее на кол-во последующих произвольных параметров

Параметры:

После параметра ht всегда необходимо передавать макрос TSRMLS_DС, запятая между параметром и макросом не ставиться

Прототип apply_func:

int (*apply_func_args_t)(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)  

где pDest - указатель на текущий элемент, num_args - кол-во произвольных параметров, после параметра num_args в функцию передается произвольное кол-во параметров. Последним параметров в callback-функцию передается hash_key - данные об индексе

Сallback-функции должны возвращать одно из следующих значений:

КонстантаОписание
ZEND_HASH_APPLY_KEEP Если функция данное значение, то цикл перебора элементов хэш-таблицы перейдет к следующий итерации
ZEND_HASH_APPLY_STOP Если функция данное значение, то цикл перебора элементов хэш-таблицы будет остановлен
ZEND_HASH_APPLY_REMOVE Если функция данное значение, то цикл перебора элементов хэш-таблицы перейдет к следующий итерации, но при этом текущий элемент будет удален из хэш-таблицы

Если функция вернет значение отличное от выше указанных, то подразумевается, что функция вернула значение ZEND_HASH_APPLY_KEEP

Примеры использования:

/* callback-функция */
int print_data(zval **data TSRMLS_DC) {  
    zval tmp;
    tmp = **data;
    zval_copy_ctor(&tmp);

    /* Выставляем счетчик ссылок */
    INIT_PZVAL(&tmp);

    /* Выводим тип и значение данных */
    if( Z_TYPE(tmp) == IS_STRING) {
        zend_printf("Type: string, Value: ");
        ZEND_WRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
    } else if(Z_TYPE(tmp) == IS_LONG) {
        zend_printf("Type: int, Value: %ld", Z_LVAL(tmp));
    } else if(Z_TYPE(tmp) == IS_DOUBLE) {
        zend_printf("Type: double, Value: %f", Z_DVAL(tmp));
    } else if(Z_TYPE(tmp) == IS_NULL) {
        zend_printf("Type: null, Value: NULL");
    } else if(Z_TYPE(tmp) == IS_ARRAY) {
        zend_printf(Type: array");
    } else if(Z_TYPE(tmp) == IS_ARRAY) {
        zend_printf(Type: array");
    } else if(Z_TYPE(tmp) == IS_OBJECT) {
        zend_printf("Type: object");
    } else if(Z_TYPE(tmp) == IS_RESOURCE) {
        zend_printf("Type: resource");
    }

    zend_printf("<br /><hr/>");

    /* Уничтожаем временную переменную */
    zval_dtor(&tmp);

    return ZEND_HASH_APPLY_KEEP;
}

void foreach_active_symbol_table() {  
    HashTable *at = NULL;
    at =  EG(active_symbol_table);
    zend_hash_apply(at, (apply_func_t)print_data TSRMLS_CC);
}
/* callback-функция */
int print_data(zval **data, char *argument TSRMLS_DC) {  
    zval tmp;
    tmp = **data;
    zval_copy_ctor(&tmp);

    /* Выставляем счетчик ссылок */
    INIT_PZVAL(&tmp);

    if(argument) {
        zend_printf("Argument: %s<br />", argument);
    }

    /* Выводим тип и значение данных */
    if( Z_TYPE(tmp) == IS_STRING) {
        zend_printf("Type: string, Value: ");
        ZEND_WRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
    } else if(Z_TYPE(tmp) == IS_LONG) {
        zend_printf("Type: int, Value: %ld", Z_LVAL(tmp));
    } else if(Z_TYPE(tmp) == IS_DOUBLE) {
        zend_printf("Type: double, Value: %f", Z_DVAL(tmp));
    } else if(Z_TYPE(tmp) == IS_NULL) {
        zend_printf("Type: null, Value: NULL");
    } else if(Z_TYPE(tmp) == IS_ARRAY) {
        zend_printf("Type: array");
    } else if(Z_TYPE(tmp) == IS_ARRAY) {
        zend_printf("Type: array");
    } else if(Z_TYPE(tmp) == IS_OBJECT) {
        zend_printf("Type: object");
    } else if(Z_TYPE(tmp) == IS_RESOURCE) {
        zend_printf("Type: resource");
    }

    zend_printf("<br /><hr/>");

    /* Уничтожаем временную переменную */
    zval_dtor(&tmp);

    return ZEND_HASH_APPLY_KEEP;
}

void foreach_active_symbol_table() {  
    HashTable *at = NULL;
    char *argument = "My argument";
    at =  EG(active_symbol_table);
    zend_hash_apply_with_argument(at, (apply_func_arg_t)print_data, (void *)argument TSRMLS_CC);
}
/* callback-функция */
int print_data(zval **data, int num_args, va_list args, zend_hash_key *hash_key) {  
    zval tmp;
    tmp = **data;
    char *f_argument = NULL;
    int *s_argument = NULL;

    zval_copy_ctor(&tmp);

    /* Получаем значения наших произвольных параметров и выводим их */
    f_argument = va_arg(args, char *);
    s_argument = va_arg(args, int *);

    zend_printf("First argument: %s, Second argument: %d<br />", f_argument, *s_argument);

    /* Выставляем счетчик ссылок */
    INIT_PZVAL(&tmp);

    /* Определяем тип индекса */
    if(hash_key->arKey && hash_key->nKeyLength) {
       zend_printf("Associative index: ");
       ZEND_WRITE(hash_key->arKey, hash_key->nKeyLength);
    } else {
       zend_printf("Numeric index: %lu", hash_key->h);
    }

    zend_printf("<br />");

    /* Выводим тип и значение данных */
    if( Z_TYPE(tmp) == IS_STRING) {
        zend_printf("Type: string, Value: ");
        ZEND_WRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
    } else if(Z_TYPE(tmp) == IS_LONG) {
        zend_printf("Type: int, Value: %ld", Z_LVAL(tmp));
    } else if(Z_TYPE(tmp) == IS_DOUBLE) {
        zend_printf("Type: double, Value: %f", Z_DVAL(tmp));
    } else if(Z_TYPE(tmp) == IS_NULL) {
        zend_printf("Type: null, Value: NULL");
    } else if(Z_TYPE(tmp) == IS_ARRAY) {
        zend_printf("Type: array");
    } else if(Z_TYPE(tmp) == IS_ARRAY) {
        zend_printf("Type: array");
    } else if(Z_TYPE(tmp) == IS_OBJECT) {
        zend_printf("Type: object");
    } else if(Z_TYPE(tmp) == IS_RESOURCE) {
        zend_printf("Type: resource");
    }

    zend_printf("<br /><hr/>");

    /* Уничтожаем временную переменную */
    zval_dtor(&tmp);

    return ZEND_HASH_APPLY_KEEP;
}

void foreach_active_symbol_table() {  
    HashTable *at = NULL;
    char *f_argument = "My first argument";
    int s_argument = 26;
    at =  EG(active_symbol_table);
    zend_hash_apply_with_arguments(at, (apply_func_args_t)print_data, 2, (void *)f_argument, (void)s_argument);
}
Anton Dobkin
Author

Anton Dobkin

Comments