在實際開發中遇到關於 trim
函數的2個問題:
1:使用trim
函數不能去除2個以上的連續點號(.)
2 : 使用trim
函數去除字符串的問題
先說一下第一個問題。
下面的一段代碼:
php -r "echo trim('abcdcba...','...');"
個人本意是要將字符串abcdcba...
最後三個點去掉,結果是報錯。php
PHP Warning: trim(): Invalid '..'-range, no character to the left of '..' in Command line code on line 1 Warning: trim(): Invalid '..'-range, no character to the left of '..' in Command line code on line 1 PHP Warning: trim(): Invalid '..'-range, no character to the right of '..' inCommand line code on line 1 Warning: trim(): Invalid '..'-range, no character to the right of '..' in Command line code on line 1
這個問題其實很好解釋,由於 trim
函數本書能夠範圍操做,例如 若是trim
函數的第二個參數 a..d
,它就會把a b c d
都去掉。由於省略號的緣由,因此trim
函數的第二個參數不能用..
開頭或者結尾。數組
第二個問題:
再看一個例子:php -r 'echo trim("abcdcba","abc")."\n";'
個人本意是將字符串abcdcba
最前面的abc
去掉保留dcba
,但結果倒是這樣的:d
也就是說他會把a b c
分別去掉。這應該算是個坑吧。函數
經過對底層源代碼的分析來講一下爲何會出現這2種狀況。trim
函數的源代碼師在php
代碼根目錄開始的 ext/standard/string.c
函數的定義以下:指針
PHP_FUNCTION(trim) { php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); }
能夠看到,定義調用了另外的函數,函數體以下:code
static void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode) { char *str; char *what = NULL; int str_len, what_len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRM\_CC, "s|s", &str, &str_len, &what, &what_len) == FAILURE) { return; } php_trim(str, str_len, what, what_len, return_value, mode TSRMLS_CC); }
zend_parse_parameters
函數的做用就是接受參數,有興趣的同窗能夠查閱相關資料。從代碼能夠看到,函數接受了2個字符串類型的參數,一個str
,就是須要處理的字符串,第二個參數是what
,用來表示須要去除的字符。
這個函數在最後用調用了另一個函數,函數php_trim
,函數體以下:開發
PHPAPI char *php_trim(char *c, int len, char *what, int what_len, zval *return_value, int mode TSRMLS_DC) { register int i; int trimmed = 0; char mask[256]; if(what) { php_charmask((unsigned char*)what, what_len, mask TSRMLS_CC); } else { php_charmask((unsigned char*)" \n\r\t\v\0", 6, mask TSRMLS_CC); } if (mode & 1) { for (i = 0; i = 0; i--) { if (mask[(unsigned char)c[i]]) { len--; } else { break; } } } if (return_value) { RETVAL_STRINGL(c, len, 1); } else { return estrndup(c, len); } return ""; }
這個函數就是php
真正處理去除操做的結構。
剛開始就是定義了簡單的變量,再下面對變量what
有一個判斷,來判斷是否傳遞了要去除的字符。能夠看到,根據是否是傳遞了what
,函數傳遞給php_charmask
函數的參數不同,從這兒能夠看出,若是trim
沒有傳要去除的字符,默認狀況是去除" \n\r\t\v\0"
六個字符的,下面來看看php_charmask
函數進行了哪些操做。rem
static inline int php\_charmask(unsigned char *input, int len, char *mask TSRMLS_DC) { unsigned char *end; unsigned char c; int result = SUCCESS; memset(mask, 0, 256); for (end = input+len; input = c) { memset(mask+c, 1, input[3] - c + 1); input+=3; } else if ((input+1 = input) { /\* there was no 'left' char \*/ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range, no character to the left of '..'"); result = FAILURE; continue; } if (input+2 >= end) { /\* there is no 'right' char \*/ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range, no character to the right of '..'"); result = FAILURE; continue; } if (input[-1] > input[2]) { /\* wrong order \*/ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range, '..'-range needs to be incrementing"); result = FAILURE; continue; } /* FIXME: better error (a..b..c is the only left possibility?) */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid '..'-range"); result = FAILURE; continue; } else { mask[c]=1; } } return result; }
這個函數的做用主要是,建立要去除的字符的哈希對應關係,剛開始考慮了特殊狀況像a..d
這樣的狀況(從這兒也能看出來爲何trim
函數不能處理...
的狀況)。後面就是創建hash
結構的過程。最後的結果是一個數組,以要去除的字符是 abc
爲例:字符串
mask['a'] = 1; mask['b'] = 1; mask['c'] = 1;
這樣的hash結構,最後返回的就是這個 mask
(實際沒有返回,使用引用變量傳值的方式作到數據的返回)
前面的都是準備工做,後面的就是真正處理去除操做了。
經過源代碼能夠看到,下面的操做先對mode
這個變量作了判斷,那麼mode
這個變量是幹嗎的?答案就是用來處理 ltrim rtirm trim
3個函數的。
下面師一段C語言代碼:input
#includeint main(){ printf("%d\n",1&1); printf("%d\n",2&2); printf("%d\n",3&1); printf("%d\n",3&2); return 0; }
這段代碼的輸出結果以下:string
1 2 1 2
經過這個你們能夠看出來,trim
的底層是怎麼處理的。先對mode
分別取模,再作相應的操做。
實際的去除操做就很簡單了。
定義一個len
來存儲字符串的長度,c
是一個字符指針,剛開始從左邊開始去除,判斷c
中的字符是否在hashmask
中存在,若是存在,就將c
的指針向後移動一位,將len
減去一位,若是發現*c
的字符不存在於hashmask
中,中止操做(可能和實際代碼邏輯不不一致,但思想師同樣的)。相關代碼以下:
for (i = 0; i
左邊操做完成之後,右邊的操做比較簡單,從*c
最右邊開始匹配,若是匹配到,就將len
的長度減1,若是沒有舊中止操做。相關的代碼以下:
for (i = len - 1; i >= 0; i--) { if (mask[(unsigned char)c[i]]) { len--; } else { break; } }
最後就是一個簡單返回操,把c
指針如今指向的位置之後的len
個字符返回。實現返回的操做。整個過程完成。
相關代碼以下:
if (return_value) { RETVAL_STRINGL(c, len, 1); } else { return estrndup(c, len); }
最後感嘆一下:全部的事情最重要的仍是你本身.