注:源碼版本:php5.6.33。
函數簡介
str_split
原型:
array str_split ( string $string [, int $split_length = 1 ] )
說明:將一個字符串轉換為數組。 參數:string
為輸入字符串。split_length
是每一段的長度。
str_split()
使用范例 :
$str = "Hello Friend" ;
$arr1 = str_split ( $str );
$arr2 = str_split ( $str , 3 );
print_r ( $arr1 );
print_r ( $arr2 );
以上例程會輸出:
Array
(
[0] => H
[1] => e
[2] => l
[3] => l
[4] => o
[5] =>
[6] => F
[7] => r
[8] => i
[9] => e
[10] => n
[11] => d
)Array
(
[0] => Hel
[1] => lo
[2] => Fri
[3] => end
)
對應的C源碼在 ext/standard/string.c
5568行。這里我貼出來:
PHP_FUNCTION(str_split)
{
char *str;
int str_len;
long split_length = 1;
char *p;
int n_reg_segments;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &split_length) == FAILURE) {
return;
}
if (split_length <= 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "The length of each segment must be greater than zero");
RETURN_FALSE;
}
array_init_size(return_value, ((str_len - 1) / split_length) + 1);
if (split_length >= str_len) {
add_next_index_stringl(return_value, str, str_len, 1);
return;
}
n_reg_segments = str_len / split_length;
p = str;
while (n_reg_segments-- > 0) {
add_next_index_stringl(return_value, p, split_length, 1);
p += split_length;
}
if (p != (str + str_len)) {
add_next_index_stringl(return_value, p, (str + str_len - p), 1);
}
}
zend_parse_parameters
首先看參數解析部分:
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &split_length)
1、第一個參數我們使用默認值。下面是原因:
傳遞給 zend_parse_parameters() 的第一個參數是用戶實際傳遞到函數的參數數量。此數值做為 ht 參數傳給函數,但就像上面討論的那樣,應使用做為實現細節的
ZEND_NUM_ARGS()
。為了與 PHP 的線程隔離、線程安全資源管理器兼容,還要用TSRMLS_CC
傳遞線程上下文。與其他函數不同,它不能是最后的參數,因為在zend_parse_parameters
內要求有不定數量的參數——依賴於要讀取的用戶參數的數量。
2、第二個參數定義所要求的參數。
每個參數都由字符串中的一個字符表示其類型。 如果希望一個字符串參數,則在此類型說明只不過是個 "s"。
這里的s|l
表示接受一個字符串和它的長度,另外再取得一個可選的長整數。|
表示可選。
相關所有類型說明符和對應的附加的 C 語言類型的文檔可在源代碼發布包中的文件 README.PARAMETER_PARSING_API 中找到。大多數重要類型可見下表。
zend_parse_parameters()
類型說明符
修飾符 | 對應C里的數據類型 | 描述 |
---|---|---|
b | zend_bool | Boolean 值 |
l | long | integer (long) 值 |
d | double | float (double) 值 |
s | char*, int | 二進制的安全串。前者接收指針,后者接收長度 |
h | HashTable* | 數組的哈希表 |
r | zval* | Resource 資源 |
a | zval* | Array 數組 |
o | zval* | Object instance 對象 |
O | zval, zend_class_entry | Object instance of a specified type 特定類型的對象 |
z | zval* | Non-specific zval 任意類型~ |
Z | zval** | zval**類型 |
f | zval** | 表示函數、方法名稱,PHP5.3之前沒有的 |
bldsh
這幾個比較常用,需要熟記。s
比較特殊,需要用兩個參數來接收。
如果有多個參數,類型說明符可以有多個。例如lsz
表示取得一個長整數,一個字符串和它的長度,再取得一個 zval
值。類型說明符還有幾個特殊標記:
| - 表明剩下的參數都是可選參數。如果用戶沒有傳進來這些參數值,那么這些值就會被初始化成默認值。
/ - 表明參數解析函數將會對剩下的參數以 SEPARATE_ZVAL_IF_NOT_REF() 的方式來提供這個參數的一份拷貝,除非這些參數是一個引用。
! - 表明剩下的參數允許被設定為 NULL(僅用在 a、o、O、r和z身上)。如果用戶傳進來了一個 NULL 值,則存儲該參數的變量將會設置為 NULL。
3、最后一個參數是傳遞一個或多個指針給要填充變量值的 C 變量,或提供更多細節。比如字符串,事實上的字符串,總是以 NULL 結尾,以 char*
,且其長度是除 NULL
字節外的 int 型值。
參考:
-
PHP: 函數的編寫 - Manual
http://php.net/manual/zh/internals2.funcs.php -
關於zend_parse_parameters函數 - 踏雪無痕SS - 博客園
https://www.cnblogs.com/chenpingzhao/p/4498829.html
函數返回值
PHP擴展開發里不是直接以return
的形式返回值的,zend引擎在每個zif函數聲明里加了一個zval*
類型的形參,名為return_value
,專門來解決返回值這個問題。
ZEND_FUNCTION
本身並沒有通過return
關鍵字返回任何有價值的東西,它只不過是在運行時修改了return_value
指針所指向的變量的值而已,而內核則會把return_value
指向的變量作為用戶端調用此函數
后的得到的返回值。ZVAL_LONG()
等宏是對一類操作的封裝,展開后應該就是下面這樣:
Z_TYPE_P(return_value) = IS_LONG;
Z_LVAL_P(return_value) = 42;
//更徹底的講,應該是這樣的:
return_value->type = IS_LONG;
return_value->value.lval = 42;
其它的還有:
//這些宏都定義在Zend/zend_API.h文件里
#define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b)
#define RETVAL_NULL() ZVAL_NULL(return_value)
#define RETVAL_LONG(l) ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE ZVAL_BOOL(return_value, 1)
#define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
#define RETURN_NULL() { RETVAL_NULL(); return;}
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE { RETVAL_FALSE; return; }
#define RETURN_TRUE { RETVAL_TRUE; return; }
再回頭看str_split
里的實現,我們發現沒有使用RETURN_*
相關的宏進行返回。這是怎么回事呢?仔細看,發現使用array_init_size
修改了return_value
指針,我們追蹤array_init_size
代碼:
#define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)
繼續展開:
ZEND_API int _array_init(zval *arg, uint size ZEND_FILE_LINE_DC) /* {{{ */
{
ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg));
_zend_hash_init(Z_ARRVAL_P(arg), size, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC);
Z_TYPE_P(arg) = IS_ARRAY;
return SUCCESS;
}
原來array_init_size
底層已經實現了RETURN_*
的功能。
php_error_docref
php_error_docref
是一個錯誤拋出函數。還有一個zend_error
函數,它主要被Zend Engine使用,但也經常出現在擴展代碼中。
兩個函數都使用sprintf函數,比如格式化信息,因此錯誤信息可以包含占位符,那些占位符會被后面的參數填充。下面有一個例子:
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write %d bytes to %s", Z_STRLEN_PP(tmp), filename);
// %d is filled with Z_STRLEN_PP(tmp)
// %s is filled with filename
參考:
- 【譯】理解PHP內部函數的定義(給PHP開發者的PHP源碼-第二部分)
http://www.hoohack.me/2016/02/10/understanding-phps-internal-function-definitions-ch
array_init_size
#define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)
初始化一個數組,指定初始化數組的元素個數。該函數定義在Zend_API.h里。
代碼里:
array_init_size(return_value, ((str_len - 1) / split_length) + 1);
初始化了一個數組,大小為字符串分段長度:最終分為幾部分,使用向上取整方法。
用向上取整的計算公式為 : (a-1)/b+1 。
參考:
- C語言中 整數除法 向上取整的數學證明 https://blog.csdn.net/shadandeajian/article/details/81812202
array_init
該函數與array_init_size用法相似,只是不用指定數組大小。該函數用於初始化一個空數組。
#define array_init(arg) _array_init((arg), 0 ZEND_FILE_LINE_CC)
示例:
ZEND_FUNCTION(sample_array)
{
array_init(return_value);
}
//return_value是zval*類型的,所以我們直接對它調用array_init()函數即可,即把它初始化成了一個空數組。
增!
add_next_index_stringl
將數組初始化后,接下來就要向其添加元素了。
函數原型:
int add_next_index_stringl(zval *arg, const char *str, uint length, int duplicate)
該函數就是給指定數組增加一個元素,該元素是字符串類型,其中length參數指的是截取的str的長度。該函數是二進制安全的。
代碼里多次用到這個函數:
//參數指定長度大於字符串長度,不用分割了,直接返回字符串本身即可
if (split_length >= str_len) {
add_next_index_stringl(return_value, str, str_len, 1);
return;
}
//分段長度
n_reg_segments = str_len / split_length;
p = str;
//字符串指針p每次往后移動split_length長度
while (n_reg_segments-- > 0) {
add_next_index_stringl(return_value, p, split_length, 1);
p += split_length;
}
//當str_len / split_length不能整除的時候, str_len > split_length * n_reg_segments
if (p != (str + str_len)) {
add_next_index_stringl(return_value, p, (str + str_len - p), 1);
}
擴展閱讀:給數組添加元素
上面介紹的add_next_index_stringl
函數是add_next_index_string
的變種,l
表示length
。其實類似的還有很多。因為PHP語言中有多種類型的變量,所以也對應的有多種類型的add_assoc()
、add_index()
、add_next_index*()
函數。如:
array_init(arrval);
add_assoc_long(zval *arrval, char *key, long lval);
add_index_long(zval *arrval, ulong idx, long lval);
add_next_index_long(zval *arrval, long lval);
這三個函數的第一個參數都要被操作的數組指針,然后是索引值,最后是變量,唯一不同的是add_next_index_long()
函數的索引值是其自己計算出來的。
這三個函數分別在內部使用了zend_hash_update()
、zend_hash_index_update()
與zend_hash_next_index_insert()
函數。
//add_assoc_*系列函數:
add_assoc_null(zval *aval, char *key);
add_assoc_bool(zval *aval, char *key, zend_bool bval);
add_assoc_long(zval *aval, char *key, long lval);
add_assoc_double(zval *aval, char *key, double dval);
add_assoc_string(zval *aval, char *key, char *strval, int dup);
add_assoc_stringl(zval *aval, char *key,char *strval, uint strlen, int dup);
add_assoc_zval(zval *aval, char *key, zval *value);
//備注:其實這些函數都是宏,都是對add_assoc_*_ex函數的封裝。
//add_index_*系列函數:
ZEND_API int add_index_long (zval *arg, ulong idx, long n);
ZEND_API int add_index_null (zval *arg, ulong idx );
ZEND_API int add_index_bool (zval *arg, ulong idx, int b );
ZEND_API int add_index_resource (zval *arg, ulong idx, int r );
ZEND_API int add_index_double (zval *arg, ulong idx, double d);
ZEND_API int add_index_string (zval *arg, ulong idx, const char *str, int duplicate);
ZEND_API int add_index_stringl (zval *arg, ulong idx, const char *str, uint length, int duplicate);
ZEND_API int add_index_zval (zval *arg, ulong index, zval *value);
//add_next_index_long函數:
ZEND_API int add_next_index_long (zval *arg, long n );
ZEND_API int add_next_index_null (zval *arg );
ZEND_API int add_next_index_bool (zval *arg, int b );
ZEND_API int add_next_index_resource (zval *arg, int r );
ZEND_API int add_next_index_double (zval *arg, double d);
ZEND_API int add_next_index_string (zval *arg, const char *str, int duplicate);
ZEND_API int add_next_index_stringl (zval *arg, const char *str, uint length, int duplicate);
ZEND_API int add_next_index_zval (zval *arg, zval *value);
總結:
上述這些函數都是給指定數組增加元素的。add_index_*
、add_assoc_*
系列函數的第一個參數都要被操作的數組指針,然后是索引值,最后是變量;add_next_index_*
系列函數無需指定索引值。
下面讓我們通過一個例子來演示下它們的用法:
ZEND_FUNCTION(sample_array)
{
zval *subarray;
array_init(return_value);
/* Add some scalars */
add_assoc_long(return_value, "life", 42);
add_index_bool(return_value, 123, 1);
add_next_index_double(return_value, 3.1415926535);
/* Toss in a static string, dup'd by PHP */
add_next_index_string(return_value, "Foo", 1);
/* Now a manually dup'd string */
add_next_index_string(return_value, estrdup("Bar"), 0);
/* Create a subarray */
MAKE_STD_ZVAL(subarray);
array_init(subarray);
/* Populate it with some numbers */
add_next_index_long(subarray, 1);
add_next_index_long(subarray, 20);
add_next_index_long(subarray, 300);
/* Place the subarray in the parent */
add_index_zval(return_value, 444, subarray);
}
這時如果我們用戶端var_dump這個函數的返回值便會得到:
<?php
var_dump(sample_array());
輸出:
array(6)
{
["life"]=> int(42)
[123]=> bool(true)
[124]=> float(3.1415926535)
[125]=> string(3) "Foo"
[126]=> string(3) "Bar"
[444]=> array(3)
{
[0]=> int(1)
[1]=> int(20)
[2]=> int(300)
}
}
參考:
在內核中操作PHP語言中數組 - PHP 擴展開發及內核應用相關內容 - 極客學院Wiki
http://wiki.jikexueyuan.com/project/extending-embedding-php/8.3.html