因为已经有文档了,可能有些人觉得我写这个有些多余了。可是并不是每一个 PHPer 都会好好地去阅读文档,自然有一些函数可能都没有听说过(很不幸我也是这其中的一员)。我也希望能通过写这些文章,能够促使我完整地读完文档,同时,能够给其它的 PHPer 一个参考,“啊,原来还有这个函数” 的感觉。同时,我也希望我能通过写这些文章,去阅读各个函数的 C 语言实现。也实现自我驱动地学习。
函数原型
array array_change_key_case ( array $array [, int $case = CASE_LOWER ] )
该函数的具体作用是,将一个数组中的所有的英文字母转换为大写或小写。
我们可以看到,这个函数接收两个参数,返回一个数组。第一个参数数组没有使用引用的方式,那么说明该函数并不会改变原数组,它会生成新的数组作为返回值。而第二个参数是可选的,它控制着该函数是转换成大写还是小写。默认是转化为小写。
函数使用
第二个参数
函数的第二个参数传入的是一个预定义常量,分别是 CASE_LOWER 和 CASE_UPPER,前者是将 key 转换成小写,也是函数的默认值;后者是将 key 转换成大写。
使用
$arr = ['loWer' => 1,
];$toLower = array_change_key_case($arr, CASE_LOWER);
// 我认为,不管它的默认值是什么,我们都要写上这第二个参数。我们的代码写出来,是给人看的,不是给机器看的。
// 所以我们的代码应当尽量多的包含语义。$toUpper = array_change_key_case($arr, CASE_UPPER);var_dump($toLower);/**
['lower' => 1
]
*/var_dump($toUpper);/**
['LOWER' => 1
]
*/
不过,这个函数不是递归的。我们看一下下面这个例子。
$arr = ['loWer' => ['Lower' => 1,],
];$toLower = array_change_key_case($arr, CASE_LOWER);var_dump($toLower);/**
['lower' => ['Lower' => 1,],
]
*/
坑
这个函数的使用,是有个坑的,这个坑就是,当转换之后,如果结果中有两个相同的 key,那么就会保留最后的那个。举个例子。
$arr = ['key' => 1,'kEy' => 2,'keY' => 3,
];$toLower = array_change_key_case($arr, CASE_UPPER);var_dump($toLower); // ['key' => 3]
在这个例子中,我们发现,当执行转换之后,三个 key 变成相同的了,那么在这种情况下,只会保留最后一个元素作为 key。这里得到的数组是 ['key' => 3]
。
内核实现
该函数的源代码在 php-src/ext/standard/array.c
中。
源码
我们先来看一下源代码。
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_OPTIONALZ_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();
}
关于 PHP_FUNCTION 宏
熟悉 PHP 扩展开发的同学应该都知道,PHP_FUNCTION
这个宏,是定义一个 PHP 函数用的,参数就是 PHP 函数的函数名。关于这个宏,有兴趣的可以去看看源码,它其实是将 PHP_FUNCTION(array_change_key_case)
替换成了 void zif_array_change_key_case(zend_execute_data *execute_data, zval *return_value)
,这样的一个函数定义。注意里面的 return_value
变量,后面会用到这个变量。
逻辑代码
其实,真正的逻辑代码,是在 ZEND_HASH_FOREACH_KEY_VAL
宏和 ZEND_HASH_FOREACH_END
之间的。上面的几个宏,是为了检查并获取传参进 PHP 函数的变量。我们可以看到 zend_long change_to_upper=0;
这个是用来判断,是大写还是小写的。这里定义的默认值是 0,所以这个函数的默认是小写。而整个函数的最核心的代码是 php_string_toupper
和 php_string_tolower
这两个函数。
这是其中之一的代码。
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 = '\0';return res;}c++;}return zend_string_copy(s);
}
用 C 写过转换字符串大小写的同学都知道,其实和我们自己实现的思路基本都差不多。只是用了几个宏。c
就是字符串的首地址,e
是字符串 '\0'
的地址。从 c
到 e
循环,然后来对每一个地址的字符转换大小写。
这里用到的 islower
、isupper
、tolower
、toupper
都是 ANSI C
中提供的函数。
结语
PHP 的文档其实是很好的学习资料,但是很多 PHPer 都没有真正好好看过文档(包括我)。前面也说了,我写这个的目的,就是希望能够通过这种方式,来对文档进行全面的扫描。同时,也从每一个函数切入,逐步地去看 PHP 内核的实现。对于后面 C 的文字,我只能尽我自己所能来解释了,有不到的地方大家多包涵,毕竟我也是一个学习者。