月度归档:2010年01月

PHP源码阅读笔记四:count函数

PHP源码阅读笔记之count函数

在一些面试或考试中经常会看到count函数的身影,于是一探其究竟

对于非数组的count处理
在其代码中可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
PHP_FUNCTION(count)
{
    zval *array;
    long mode = COUNT_NORMAL;
    if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE)
        return;
    switch (Z_TYPE_P(array)) {
        case IS_NULL:    //    空值处理
            RETURN_LONG(0);
            break;
        case IS_ARRAY:    //    处理数组,包括其是递归
            RETURN_LONG (php_count_recursive (array, mode TSRMLS_CC));
            break;
        case IS_OBJECT: {    
    #ifdef HAVE_SPL
        /* it the object implements Countable we call its count() method */
            zval *retval;
 
            if (Z_OBJ_HT_P(array)->get_class_entry && instanceof_function(Z_OBJCE_P(array), spl_ce_Countable TSRMLS_CC)) {
                zend_call_method_with_0_params(&array, NULL, NULL, "count", &retval);
                if (retval) {
                    convert_to_long_ex(&retval);
                    RETVAL_LONG(Z_LVAL_P(retval));
                    zval_ptr_dtor(&retval);
                }
            return;
            }
    #endif
        /* if not we return the number of properties (not taking visibility into account) */
            if (Z_OBJ_HT_P(array)->count_elements) {
                RETVAL_LONG(1);
                if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value) TSRMLS_CC)) {
                    return;    
              }
            }
        }
    default:    //    其它情况,如考试中常见的字符串等
        RETURN_LONG(1);
        break;
    }
}

在手册中有说明:
此函数对于对象,如果安装了 SPL,可以通过实现 Countable 接口来调用 count()。该接口只有一个方法 count(),此方法返回 count() 函数的返回值。

对于数组长度的统计,如果mode使用默认值(0),则仅显示第一维数组的长度
如下所示代码

1
2
3
4
5
6
7
8
$arr = array(1, 2, 3);
$arr2 = array($arr, $arr);
echo count($arr2), '<br />';
echo count($arr2, 1);
/*   输出结果:
2
8
*/

如果只是显示第一维数组,则直接返回保存数组的HashTable的nNumOfElements属性即可
其实现的代码为:

1
2
3
4
5
6
7
8
9
//    php_count_recursive函数 array.c 251行
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
 
ZEND_API int zend_hash_num_elements(HashTable *ht)
{
    IS_CONSISTENT(ht);
 
    return ht->nNumOfElements;
}

如果想直接运行HashTable的一些简单操作,猛击PHP源码中HashTable的简单示例
如果想查看了解数组存储或遍历的方式,猛击鸟哥的深入理解PHP之数组(遍历顺序)

如果启动了递归,即使用count($arr, 1)
则程序会递归调用php_count_recursive函数
针对HashTable,程序会遍历包含这一维数组的双向链表,然后再递归遍历每个节点存储的pData,真到遍历完所有的节点

【感受】
HashTable很强大,如果要计算数组的长度还是调用count函数,重复发明轮子会是一种吃力不讨好的事情!

PHP源码阅读笔记三:strrchr, strstr, stristr函数

PHP源码阅读笔记三:strrchr, strstr, stristr函数

string strrchr ( string haystack, string needle )
返回haystack中最后一个needle(字符)所在位置以后的字符串
如果needle为数字,将这个数字转化为这个值所对应的字符
如果needle多于一个字符串,则取第一个字符
如果haystack为一个数字,会将这个数字直接转化成字符串
程序中调用convert_to_string_ex(haystack);

在此函数的实现中,基本上是对一些特殊情况的处理(如上),
到最后就是定位最后一个needle出现的位置,返回根据位置返回此位置以后的字符串,如果此位置不存在,同返回false

string strstr ( string haystack, string needle )
此函数功能与strrchr类似,只不过它的needle允许为字符串,并且是查找needle第一个出现的位置,根据此位置返回之后的字符串,如果不存在则返回FALSE

在其代码中有一些特别的细节值得学习,如下所示代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static inline char *zend_memnstr(char *haystack, char *needle, int needle_len, char *end)
{
    char *p = haystack;
    char ne = needle[needle_len-1];
 
    if(needle_len > end-haystack) {
        return NULL;
    }
    end -= needle_len;   //    优化细节一,仅查找end-needle_len长度的字符串
 
    while (p <= end) {
        //    优化细节二,此处先判断字符串的开头和结尾的字符是否一样,如果一样则再判断整个字符串,提升性能
        if ((p = (char *)memchr(p, *needle, (end-p+1))) && ne == p[needle_len-1]) {    
            if (!memcmp(needle, p, needle_len-1)) {
                return p;
            }
        }
 
        if (p == NULL) {
            return NULL;
        }
 
        p++;
    }
 
    return NULL;
}

以上代码是strstr和stristr函数实现的核心代码,功能:查找needle在haystack中首次出现的位置

string stristr ( string haystack, string needle )
stristr函数的功能与strstr类似,所不同的是其不区分大小写。
在PHP源码实现中主要区别是添加了将所有字符串转化为小写的操作,程序实现是在查找之前添加了如下代码:

1
2
3
php_strtolower(s, s_len);
php_strtolower(t, t_len);
return php_memnstr(s, t, t_len, s + s_len);

PS:在看源码的过程中,再次看了《再再认指针》的前面一段话,多了一些体会;
指针能够进行加减法,原因并不是因为它是指针,加减法则不是属于指针这种变量的,而是地址这种数据类型的本能,正是因为地址具有加减的能力,所以才使指针作为存放地址的变量能够进行加减运算。
这跟整数变量因为整数能够进行加减乘除因而它也能进行加减乘除一个道理。
查看《再再论指针》,请猛击再再论指针

PHP源码阅读笔记二:strlen, strtolower, strtoupper, ord, chr函数

PHP源码阅读笔记二:strlen, strtolower, strtoupper, ord, chr函数

int strlen ( string string )
返回字符串的长度
在标准扩展中并没有相关的实现,在其它扩展函数中使用Z_STRLEN、Z_STRLEN_P或Z_STRLEN_PP取得长度

string strtolower ( string str )
将一个字符串变为小写,其实现如下代码
【经典源代码】

1
2
3
4
5
6
7
8
9
10
11
12
13
char *php_strtolower(char *s, size_t len)
{
              unsigned char *c, *e;
 
              c = s;
              e = c+len;
 
              while (c < e) {
                            *c = tolower(*c);
                            c++;
              }
              return s;
}

这虽然是一个简单的遍历字符串并将每个字符变为小写的程序,但是这是一个使用指针的标程。

string strtoupper ( string string )
将一个字符串变为大写
代码实现与上面的程序类似只是将tolower函数变成了toupper
string chr ( int ascii )
返回相对应于 ascii 所指定的单个字符。
其本质是返回一个长度为1的字符串
【源代码】

1
2
3
              temp[0] = (char) Z_LVAL_PP(num);
              temp[1] = 0;
              RETVAL_STRINGL(temp, 1, 1);

int ord ( string string )
返回字符的ASCII值
其本质上是返回字符串第一个字符的ASCII值
【源代码】

1
RETURN_LONG((unsigned char) Z_STRVAL_PP(str)[0]);