1

因为已经有文档了,可能有些人觉得我写这个有些多余了。可是并不是每一个 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_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();
}

关于 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_toupperphp_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' 的地址。从 ce 循环,然后来对每一个地址的字符转换大小写。

这里用到的 islowerisuppertolowertoupper 都是 ANSI C 中提供的函数。

结语

PHP 的文档其实是很好的学习资料,但是很多 PHPer 都没有真正好好看过文档(包括我)。前面也说了,我写这个的目的,就是希望能够通过这种方式,来对文档进行全面的扫描。同时,也从每一个函数切入,逐步地去看 PHP 内核的实现。对于后面 C 的文字,我只能尽我自己所能来解释了,有不到的地方大家多包涵,毕竟我也是一个学习者。


daryl
4.7k 声望185 粉丝

2018 年上半年要做的事情: