摘要:關(guān)于宏熟悉擴(kuò)展開(kāi)發(fā)的同學(xué)應(yīng)該都知道,這個(gè)宏,是定義一個(gè)函數(shù)用的,參數(shù)就是函數(shù)的函數(shù)名。關(guān)于這個(gè)宏,有興趣的可以去看看源碼,它其實(shí)是將替換成了,這樣的一個(gè)函數(shù)定義。上面的幾個(gè)宏,是為了檢查并獲取傳參進(jìn)函數(shù)的變量。
函數(shù)原型因?yàn)橐呀?jīng)有文檔了,可能有些人覺(jué)得我寫這個(gè)有些多余了??墒遣⒉皇敲恳粋€(gè) PHPer 都會(huì)好好地去閱讀文檔,自然有一些函數(shù)可能都沒(méi)有聽(tīng)說(shuō)過(guò)(很不幸我也是這其中的一員)。我也希望能通過(guò)寫這些文章,能夠促使我完整地讀完文檔,同時(shí),能夠給其它的 PHPer 一個(gè)參考,“啊,原來(lái)還有這個(gè)函數(shù)” 的感覺(jué)。同時(shí),我也希望我能通過(guò)寫這些文章,去閱讀各個(gè)函數(shù)的 C 語(yǔ)言實(shí)現(xiàn)。也實(shí)現(xiàn)自我驅(qū)動(dòng)地學(xué)習(xí)。
array array_change_key_case ( array $array [, int $case = CASE_LOWER ] )
該函數(shù)的具體作用是,將一個(gè)數(shù)組中的所有的英文字母轉(zhuǎn)換為大寫或小寫。
我們可以看到,這個(gè)函數(shù)接收兩個(gè)參數(shù),返回一個(gè)數(shù)組。第一個(gè)參數(shù)數(shù)組沒(méi)有使用引用的方式,那么說(shuō)明該函數(shù)并不會(huì)改變?cè)瓟?shù)組,它會(huì)生成新的數(shù)組作為返回值。而第二個(gè)參數(shù)是可選的,它控制著該函數(shù)是轉(zhuǎn)換成大寫還是小寫。默認(rèn)是轉(zhuǎn)化為小寫。
函數(shù)使用 第二個(gè)參數(shù)函數(shù)的第二個(gè)參數(shù)傳入的是一個(gè)預(yù)定義常量,分別是 CASE_LOWER 和 CASE_UPPER,前者是將 key 轉(zhuǎn)換成小寫,也是函數(shù)的默認(rèn)值;后者是將 key 轉(zhuǎn)換成大寫。
使用$arr = [ "loWer" => 1, ]; $toLower = array_change_key_case($arr, CASE_LOWER); // 我認(rèn)為,不管它的默認(rèn)值是什么,我們都要寫上這第二個(gè)參數(shù)。我們的代碼寫出來(lái),是給人看的,不是給機(jī)器看的。 // 所以我們的代碼應(yīng)當(dāng)盡量多的包含語(yǔ)義。 $toUpper = array_change_key_case($arr, CASE_UPPER); var_dump($toLower); /** [ "lower" => 1 ] */ var_dump($toUpper); /** [ "LOWER" => 1 ] */
不過(guò),這個(gè)函數(shù)不是遞歸的。我們看一下下面這個(gè)例子。
$arr = [ "loWer" => [ "Lower" => 1, ], ]; $toLower = array_change_key_case($arr, CASE_LOWER); var_dump($toLower); /** [ "lower" => [ "Lower" => 1, ], ] */坑
這個(gè)函數(shù)的使用,是有個(gè)坑的,這個(gè)坑就是,當(dāng)轉(zhuǎn)換之后,如果結(jié)果中有兩個(gè)相同的 key,那么就會(huì)保留最后的那個(gè)。舉個(gè)例子。
$arr = [ "key" => 1, "kEy" => 2, "keY" => 3, ]; $toLower = array_change_key_case($arr, CASE_UPPER); var_dump($toLower); // ["key" => 3]
在這個(gè)例子中,我們發(fā)現(xiàn),當(dāng)執(zhí)行轉(zhuǎn)換之后,三個(gè) key 變成相同的了,那么在這種情況下,只會(huì)保留最后一個(gè)元素作為 key。這里得到的數(shù)組是 ["key" => 3]。
內(nèi)核實(shí)現(xiàn)該函數(shù)的源代碼在 php-src/ext/standard/array.c 中。
源碼我們先來(lái)看一下源代碼。
PHP_FUNCTION(array_change_key_case) { zval *array, *entry; zend_string *string_key; zend_string *new_key; zend_ulong num_key; zend_long change_to_upper=0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY(array) Z_PARAM_OPTIONAL Z_PARAM_LONG(change_to_upper) ZEND_PARSE_PARAMETERS_END(); array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array))); ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_key, string_key, entry) { if (!string_key) { entry = zend_hash_index_update(Z_ARRVAL_P(return_value), num_key, entry); } else { if (change_to_upper) { new_key = php_string_toupper(string_key); } else { new_key = php_string_tolower(string_key); } entry = zend_hash_update(Z_ARRVAL_P(return_value), new_key, entry); zend_string_release(new_key); } zval_add_ref(entry); } ZEND_HASH_FOREACH_END(); }關(guān)于 PHP_FUNCTION 宏
熟悉 PHP 擴(kuò)展開(kāi)發(fā)的同學(xué)應(yīng)該都知道,PHP_FUNCTION 這個(gè)宏,是定義一個(gè) PHP 函數(shù)用的,參數(shù)就是 PHP 函數(shù)的函數(shù)名。關(guān)于這個(gè)宏,有興趣的可以去看看源碼,它其實(shí)是將 PHP_FUNCTION(array_change_key_case) 替換成了 void zif_array_change_key_case(zend_execute_data *execute_data, zval *return_value),這樣的一個(gè)函數(shù)定義。注意里面的 return_value 變量,后面會(huì)用到這個(gè)變量。
邏輯代碼其實(shí),真正的邏輯代碼,是在 ZEND_HASH_FOREACH_KEY_VAL 宏和 ZEND_HASH_FOREACH_END 之間的。上面的幾個(gè)宏,是為了檢查并獲取傳參進(jìn) PHP 函數(shù)的變量。我們可以看到 zend_long change_to_upper=0; 這個(gè)是用來(lái)判斷,是大寫還是小寫的。這里定義的默認(rèn)值是 0,所以這個(gè)函數(shù)的默認(rèn)是小寫。而整個(gè)函數(shù)的最核心的代碼是 php_string_toupper 和 php_string_tolower 這兩個(gè)函數(shù)。
這是其中之一的代碼。
PHPAPI zend_string *php_string_toupper(zend_string *s) { unsigned char *c, *e; c = (unsigned char *)ZSTR_VAL(s); e = c + ZSTR_LEN(s); while (c < e) { if (islower(*c)) { register unsigned char *r; zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0); if (c != (unsigned char*)ZSTR_VAL(s)) { memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s)); } r = c + (ZSTR_VAL(res) - ZSTR_VAL(s)); while (c < e) { *r = toupper(*c); r++; c++; } *r = "