程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> PHP內核探索之變量(7)- 不平凡的字符串,內核不平凡

PHP內核探索之變量(7)- 不平凡的字符串,內核不平凡

編輯:關於PHP編程

PHP內核探索之變量(7)- 不平凡的字符串,內核不平凡


切,一個字符串有什麼好研究的。

別這麼說,看過《平凡的世界》麼,平凡的字符串也可以有不平凡的故事。試看:

(1)       在C語言中,strlen計算字符串的時間復雜度是?PHP中呢?

(2)       在PHP中,怎樣處理多字節字符串?PHP對unicode的支持如何?

同樣是字符串,為什麼c語言與C++/PHP/Java的均不相同?

數據結構決定算法,這句話一點不假。

那麼我們今天就來掰一掰,PHP中的字符串結構,以及相關字符串函數的實現。

一、  字符串基礎

  字符串可以說是PHP中遇到最多的數據結構之一了(另外一個比較常用的是數組,見PHP內核探索之變量(4)- 數組操作)。而由於PHP語言的特性和應用場景,使得我們日常的很多工作,實際上都是在處理字符串。也正是這個原因,PHP為開發者提供了豐富的字符串操作函數(初步統計約有100個,這個數量相當可觀)。那麼,在PHP中,字符串是怎樣實現的呢?與C語言又有什麼區別呢?

 1.  PHP中字符串的表現形式

  在PHP中使用字符串有四種常見的形式:

(1)    雙引號

         這種形式比較常見:$str=”this is \0 a string”; 而且以雙引號包含的字符串中可以包含變量、控制字符等:$str = "this is $name, aha.\n";

(2)     單引號

  單引號包含的字符都被認為是raw的,因此不會解析單引號中的變量,控制字符等:

$string = "test";
$str = 'this is $string, aha\n';
echo $str;

(3)     Heredoc

Heredoc比較適合較長的字符串表示,且對於多行的字符串表示更加靈活多樣。與雙引號表示形式類似,heredoc中也可以包含變量。常見的形式是:

$string ="test string";
$str = <<<STR
This is a string \n,
My string is $string
STR;

echo $str;

(4)     nowdoc(5.3+支持)

nowdoc和heredoc是如此的類似,以至於我們可以把它們當做是一對兒親兄弟。nowdoc的起始標志符是用單引號括起來的,與單引號相似,它不會解析其中的變量,格式控制符等:

$s = <<<'EOT'
this is $str
this is \t test;
EOT;

echo $s;

2.       PHP中字符串的結構

   之前提到過,PHP中變量是用Zval(PHP內核探索之變量(1)Zval)這樣一個結構體來存儲的。Zval的結構是:

struct _zval_struct {
    zvalue_value value;       /* value */
    zend_uint refcount__gc;   /* variable ref count */
    zend_uchar type;          /* active type */
    zend_uchar is_ref__gc;    /* if it is a ref variable */
};

   而變量的值是zvalue_value這樣一個共用體:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {                    /* string */
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

我們從中抽取出字符串的結構:

struct {
    char *val;
    int len;
} str;

現在比較清楚了,PHP中字符串在底層實際上是一個結構體,該結構體包含了指向字符串的指針和該字符串的長度。

那麼為什麼這麼做呢?換句話說,這樣做有什麼好處呢?我們接下來,將PHP的字符串與C語言的字符串做一個對比,以解釋采用這樣一種結構來存儲字符串的優勢。

3.  與c語言字符串的比較

         我們知道,在c語言中,一個字符串可以用兩種常見的形式存儲,一種是使用指針方式,另一種是使用字符數組。我們接下來的說明,都以c語言的字符數組的方式來存儲字符串。

(1)       PHP字符串是二進制安全的,而C字符串不是

     我們經常會提到”二進制安全”這一術語,那麼二進制安全究竟是什麼意思呢?

     wikipedia中對二進制安全(Binary Safe)的定義是:

Binary-safe is a computer programming term mainly used in connection with string manipulating functions. 
A binary-safe function is essentially one that treats its input as a raw stream of data without any specific format.
It should thus work with all 256 possible values that a character can take (assuming 8-bit characters).

  翻譯過來就是:

     二進制安全是計算機編程的術語,主要用於字符串操作函數。一個二進制安全的函數,本質上是指它將輸入看做是原始的數據流(raw)而不包含任何特殊的格式。

     那麼為什麼C字符串不是二進制安全的?我們知道,在C語言中,以字符數組表示的字符串總是以\0結尾的,這個\0便是C字符串的specific format, 用於標識字符串的結束。更近一步說,如果一個字符串中本身包含了\0且並不是該字符串的結尾,那麼在C中,\0後面的所有數據都會被忽略(感覺就像是 字符串被莫名其妙的截斷了)。這也意味著,C字符串只合適保存簡單的文本,而不能用於保存圖片、視頻、其他文件等二進制數據。而在PHP中,我們可以使用$str = file_get_contents(“filename”);保存圖片、視頻等二進制數據。

(2)   效率對比

     由於C字符串中使用\0來標志字符串的結束,因此,對於strlen函數而言,獲取字符串長度的操作需要順序遍歷字符串,直到遇到\0為止,因此strlen函數的時間復雜度是O(n)。而在PHP中,字符串是以:

struct{
      char *val;
      int len;
} str;

 這樣一種結構體來表示的,因而獲取字符串的長度只需要通過常量的時間便可以完成:

#define Z_STRLEN(zval)          (zval).value.str.len

當然,僅僅是strlen函數的性能,無法支持“PHP中string比c字符串的效率更高”的結論(一個很明顯的原因是PHP是構建在C語言之上的高級語言),而僅僅說明,在時間復雜度上,PHP字符串比C字符串更加高效。

(3)      很多C字符串函數存在緩沖區溢出的漏洞

緩存區溢出是C語言中常見的漏洞,這種安全隱患經常是致命的。一個典型的緩存區溢出的例子如下:

void str2Buf(char *str) {
    char buffer[16];
    strcpy(buffer,str);
}

 這個函數將str的內容copy到buffer數組中,而buffer數組的大小是16,因此如果str的長度大於16,便會發生緩沖區溢出的問題。

除了strcpy,還有gets, strcat, fprintf等字符串函數也會有緩沖區溢出的問題。

PHP中並沒有strcpy與strcat之類的函數,實際上由於PHP語言的簡潔性,並不需要提供strcpy和strcat之類的函數。例如我們要復制一個字符串,直接使用=即可:

$str = "this is a string";
$str_copy = $str;

  由於PHP中變量共享zval的特性,並不會有空間浪費.而簡單的.連接符可以輕松實現字符串連接:

$str = "this is";
$str .= "test string";
echo $str;

  關於字符串連接符過程中的內存分配和管理,可以查看zend引擎部分的實現,這裡暫時忽略。

二、   字符串操作相關函數(部分)

         毫無疑問,研究字符串的目的並不只是為了知道它的結構和特性,而是為了更好的使用它。我們日常的工作中,恐怕有一般以上的工作都是在與字符串打交道:如處理一個日期串、加密一個密碼、獲取用戶信息、正則表達式匹配替換、字符串替換、格式化一個串等等。可以說,在PHP開發中,你無法避免與字符串的直接或者間接接觸(就像無法擺脫呼吸)。正因為如此,PHP為開發者提供了大量的、豐富的字符串操作函數( http://cn2.php.net/manual/en/ref.strings.php),這對於90%以上的字符串操作,已經基本足夠。

         由於字符串函數眾多,不可能一一說明。這裡只挑選幾個比較典型的字符串操作函數     來做簡單的說明(我相信80%以上的PHPer對於字符串的操作函數掌握的非常的好)。

在開始說明之前,有必要強調一下字符串函數的使用原則,理解和掌握這些原則對於高效、熟練使用字符串函數非常關鍵,這些原則包括(不僅限於):

(1)       如果你的操作既可以使用正則表達式,也可以使用字符串。那麼優先考慮字符串操作。

         正則表達式是處理文本的絕好工具,尤其對於模式查找、模式替換這一類應用,正則可以說是無往不利。正因為如此,正則表達式在很多場合都被濫用。如果對於你的字符串操作,既可以使用字符串函數完成,也可以使用正則表達式完成,那麼,請優先選擇字符串操作函數,因為正則表達式在一定場合下會有嚴重的性能問題。

(2)       注意false與0

    PHP是弱變量類型,相信不少phper開始都深受其害

var_dump( 0 == false);//bool(true)
var_dump( 0 === false);//bool(false)

  等等,這與字符串操作函數有什麼關系?

  在PHP中,有一類函數用於查找(如strpos, stripos),這類查找函數在查找成功時,返回的是子串在原串中的index,如strpos:

var_dump(strpos("this is abc", "abc"));

  而在查找不成功時,返回的是false:

var_dump(strpos("this is abc", "angle"));//false

  這裡便有一個坑:字符串的索引也是以0開始的!如果子串剛好在源串的起始位置出現,那麼,簡單的==比較便無法區分究竟strpos是不是成功:

var_dump(strpos("this is abc", "this"));

  因此我們一定是要用===來比較的:

if((strpos("this is abc", "this")) === false){
    // not found
}

  (3)       多看手冊,避免重復造輪子。

  相信不少PHPer面試都碰到過這樣的問題:如何翻轉一個字符串?由於題目中只提及“如何“,而並沒有限制”不使用PHP內置函數“。那麼對於本題,最簡潔的方法自然是使用strrev函數。另一個說明不應該重復造輪子的函數是levenshtein函數,這個函數如同其名字一樣,返回的是兩個字符串的編輯距離。作為動態規劃(DP)的典型代表案例之一,我想編輯距離很多人都不陌生。碰到這類問題,你還准備DP搞起嗎?一個函數搞定它:

$str1 = "this is test";
$str2 = "his is tes";
echo levenshtein($str1, $str2);

在某些情況下,我們都應該盡可能的“懶“,不是嗎。

以下是字符串操作函數節選(對於最常見的操作,請直接參考手冊)

1.  strlen

此標題一出,我猜想大多數人的表情是這樣的:

或者是這樣的:

 

我要說的,並不是這個函數本身,而是這個函數的返回值。

int strlen ( string $string )
Returns the length of the given string.

雖然手冊上明確指出“strlen函數返回給定字符串的長度”,但是,並沒有對長度單位做任何說明,長度究竟是指”字符的個數“還是說”字符的字節數“。而我們要做的,並不是臆想,而是測試:

在GBK編碼格式下:

echo strlen(“這是中文”);//8

說明strlen函數返回的是字符串的字節數。那麼又有問題了,如果是utf-8編碼,由於中文在utf8編碼的情況下,每個中文使用3個byte,因而,我們期望的結果應該是12:

echo strlen(“這是中文”);//12

這說明:strlen計算字符串的長度依賴於當前的編碼格式,其值並不是唯一的!這在某些情況下,自然是無法滿足要求的。這時,多字節擴展mbstring便有它的發揮余地了:

echo mb_strlen("這是中文", "GB2312");//4

關於這點,在多字節處理中會有相應說明,這裡略過。

2. str_word_count

str_word_count是另一個比較強大的且容易忽略的字符串函數。

mixed str_word_count ( string $string [, int $format = 0 [, string $charlist ]] )

其中$format的不同值可以使str_word_count函數有不同的行為。 現在,我們手頭有這樣的文本:

When I am down and, oh my soul, so weary
When troubles come and my heart burdened be
Then, I am still and wait here in the silence
Until you come and sit awhile with me
You raise me up, so I can stand on mountains
You raise me up, to walk on stormy seas
I am strong, when I am on your shoulders

You raise me up… To more than I can ber
You raise me up, so I can stand on mountains
You raise me up, to walk on stormy seas
I am strong, when I am on your shoulders
You raise me up, To more than I can be。

那麼:

(1)$format = 0

$format=0, $format返回的是文本中的單詞的個數

echo str_word_count(file_get_contents(“word”)); //112

(2)$format = 1

$format=1時,返回的是文本中全部單詞的數組:

print_r(file_get_contents(“word”),1 );
Array
(
    [0] => When
    [1] => I
    [2] => am
    [3] => down
    [4] => and
    [5] => oh
    [6] => my
    [7] => soul
    [8] => so
    [9] => weary
    [10] => When
    [11] => troubles
......
)

這一特性有什麼作用呢?比如英文分詞。還記得“單詞統計”的問題麼?str_word_count可以輕松完成單詞統計TopK的問題:

$s = file_get_contents("./word");
$a = array_count_values(str_word_count($s, 1)) ;
arsort( $a );
print_r( $a );

/*
Array
(
    [I] => 10
    [me] => 7
    [raise] => 6
    [up] => 6
    [You] => 6
    [am] => 6
    [on] => 6
    [can] => 4
    [and] => 4
    [be] => 3
    [so] => 3
    ……
);*/

(3)$format = 2

$format=2時,返回的是一個關聯數組:

$a = str_word_count($s, 2);
print_r($a);

/*
Array
(
    [0] => When
    [5] => I
    [7] => am
    [10] => down
    [15] => and
    [20] => oh
    [23] => my
    [26] => soul
    [32] => so
    [35] => weary
    [41] => When
    [46] => troubles
    [55] => come
    ...
)*/

配合其他數組函數,可以實現更加多樣化的功能.例如,配合array_flip,可以計算某個單詞最後一次出現的位置:

$t = array_flip(str_word_count($s, 2));
print_r($t);

而如果配合了array_unique之後再array_flip,則可以計算某個單詞第一次出現的位置:

$t = array_flip( array_unique(str_word_count($s, 2)) );
print_r($t);

Array
(
    [When] => 0
    [I] => 5
    [am] => 7
    [down] => 10
    [and] => 15
    [oh] => 20
    [my] => 23
    [soul] => 26
    [so] => 32
    [weary] => 35
    [troubles] => 46
    [come] => 55
    [heart] => 67
    ...
)

3.  similar_text

這是除了levenshtein()函數之外另一個計算兩個字符串相似度的函數:

int similar_text ( string $first , string $second [, float &$percent ] )
$t1 = "You raise me up, so I can stand on mountains";
$t2 = "You raise me up, to walk on stormy seas";
$percent = 0;

echo similar_text($t1, $t2, $percent).PHP_EOL;//26
echo $percent;// 62.650602409639

撇開具體的使用不談,我很好奇底層對於字符串的相似度是如何定義的。

Similar_text函數實現位於 ext/standard/string.c 中,摘取其關鍵代碼:

 

PHP_FUNCTION(similar_text){
    char *t1, *t2;
    zval **percent = NULL;
    int ac = ZEND_NUM_ARGS();
    int sim;
    int t1_len, t2_len;
       
    /* 參數解析 */
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|Z", &t1, &t1_len, &t2, &t2_len, &percent) == FAILURE) {
        return;
    }
        
    /* set percent to double type */
    if (ac > 2) {
        convert_to_double_ex(percent);
    }

   /* t1_len == 0 && t2_len == 0 */
    if (t1_len + t2_len == 0) {
        if (ac > 2) {
            Z_DVAL_PP(percent) = 0;
        }
        RETURN_LONG(0);
    }       
    
    /* 計算字符串相同個數 */
    sim = php_similar_char(t1, t1_len, t2, t2_len);
    
    /* 相似百分比 */
    if (ac > 2) {
        Z_DVAL_PP(percent) = sim * 200.0 / (t1_len + t2_len);
    }
 
    RETURN_LONG(sim);
}

 

 

可以看出,字符串相似個數是通過 php_similar_char 函數實現的,而相似百分比則是通過公式:

percent = sim * 200 / (t1串長度 + t2串長度)

來定義的。

php_similar_char的具體實現:

 

static int php_similar_char(const char *txt1, int len1, const char *txt2, int len2)
{
    int sum;
    int pos1 = 0, pos2 = 0, max;

    php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);
    if ((sum = max)) {
        if (pos1 && pos2) {
            sum += php_similar_char(txt1, pos1,txt2, pos2);
        }

        if ((pos1 + max < len1) && (pos2 + max < len2)) {
            sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,txt2 + pos2 + max, len2 - pos2 - max);
        }
    }

    return sum;
}

這個函數通過調用php_similar_str來完成字符串相似個數的統計,而php_similar_str返回字符串s1與字符串s2的最長相同字符串長度:

static void php_similar_str(const char *txt1, int len1, const char *txt2, int len2, int *pos1, int *pos2, int *max)
{
    char *p, *q;
    char *end1 = (char *) txt1 + len1;
    char *end2 = (char *) txt2 + len2;
    int l;
    *max = 0;
    
    /* 查找最長串 */
    for (p = (char *) txt1; p < end1; p++) {
        for (q = (char *) txt2; q < end2; q++) {
            for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);
            if (l > *max) {
                *max = l;
                *pos1 = p - txt1;
                *pos2 = q - txt2;
            }
        }
    }
}

   php_similar_str匹配完成之後,原始的串被劃分為三個部分:

第一部分是最長串的左邊部分,這一部分含有相似串,但是卻不是最長的;

第二部分是最長相似串部分;

第三部分是最長串的右邊部分,與第一部分相似,這一部分含有相似串,但是也不是最長的。因而要遞歸對第一部分和第三部分求相似串的長度:

 

/* 最長的串左邊部分相似串 */
if (pos1 && pos2) {
    sum += php_similar_char(txt1, pos1,txt2, pos2);
}

/* 右半部分相似串 */
if ((pos1 + max < len1) && (pos2 + max < len2)) {
    sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max, txt2 + pos2 + max, len2 - pos2 - max);
}

匹配的過程如下圖所示:

 

對於字符串函數的更多解釋,可以參考PHP的在線手冊,這裡不再一一列舉。

三、多字節字符串

  迄今為止,我們討論的所有的字符串和相關操作函數都是單字節的。然而這個世界是如此的豐富多彩,就好比有紅瓤的西瓜也有黃瓤的西瓜一樣,字符串也不例外。如我們常用的中文漢字在GBK編碼的情況下,實際上是使用兩個字節來編碼的。多字節字符串不僅僅局限於中文漢字,還包括日文,韓文等等多個國家的文字。正因為如此,對於多字節字符串的處理顯得異常重要。

  字符和字符集是編程過程中不可避免總是要遇到的術語。如果有童鞋對於這一塊的內容並不是特別清晰,建議移步《編碼大事1字符編碼基礎-字符和字符集,》

  由於我們日常中使用較多的是中文,因而我們以中文字符串截取為例, 重點研究中文字符串的問題。

中文字符串的截取

    中文字符串截取一直是個相對來說比較麻煩的問題,原因在於:

(1) PHP原生的substr函數只支持單字節字符串的截取,對於多字節的字符串略顯無力

(2) PHP的擴展mbstring需要服務器的支持,事實上,很多開發環境中並沒有開啟mbstring擴展,對於習慣使用mbstring擴展的童鞋非常遺憾。

(3) 一個更為復雜的問題是,在UTF-8編碼的情況下,雖然中文是3個字節的,但是中文的某些特殊字符(如脫字符·)實際上是雙字節編碼的。這無疑加大了中文字符串截取的難度(畢竟,中文字符串中不可能完全不包含特殊字符)。

     頭疼之余,還是要自己撸一個中文的字符串截取的庫,這個字符串截取函數應該與substr有相似的函數參數列表,而且要支持中文GBK編碼和UTF-8編碼情況下的截取,為了效率起見,如果服務器已經開啟了mbstring擴展,那麼就應該直接使用mbstring的字符串截取。

API:

String cnSubstr(string $str, int $start, int $len, [$encode=’GBK’]);//注意參數中$start, $len都是字符數而不是字節數。

我們以UTF-8編碼為例,來說明UTF8編碼下中文的截取思路。

(1)     編碼范圍:

UTF-8的編碼范圍(utf-8使用1-6個字節編碼字符,實際上只使用了1-4字節):

1個字節:00——7F
2個字節:C080——DFBF
3個字符:E08080——EFBFBF
4個字符:F0808080——F7BFBFBF

據此, 可以根據第一個字節的范圍確定該字符所占的字節數:

$ord = ord($str{$i});
$ord < 192 單字節和控制字符
192 <= $ord < 224 雙字節
224<= $ord < 240  三字節
中文並沒有四個字節的字符

(2)$start為負的情況

if( $start < 0 ){
    $start += cnStrlen_utf8( $str );
 
    if( $start < 0 ){
        $start = 0;
    }
}

網上大多數字符串截取版本都沒有處理$start< 0的情況,按照PHP substr的API設計,在$start <0 時,應該加上字符串的長度(多字節指字符數)。

其中cnStrlen_utf8用於獲取字符串在utf8編碼下的字符數:

function cnStrlen_utf8( $str ){
    $len  = 0;
    $i    = 0;
    $slen = strlen( $str );

    while( $i < $slen ){
        $ord = ord( $str{$i} );
        if( $ord < 127){
            $i ++;
        }else if( $ord < 224 ){
            $i += 2;
        }else{
            $i += 3;
        }
        $len ++;
    }
    
    return $len;
}

因此UTF-8的截取算法為:

function cnSubstr_utf8( $str, $start, $len ){
    if( $start < 0 ){
        $start += cnStrlen_utf8( $str );
        
        if( $start < 0 ){
            $start = 0;
        }
    }    
    
    $slen = strlen( $str );
    
    if( $len < 0 ){
        $len += $slen - $start;
        
        if($len < 0){
            $len = 0;
        }
    }

    $i = 0;    
    $count = 0;
    
    /* 獲取開始位置 */
    while( $i < $slen && $count < $start){
        $ord = ord( $str{$i} );
        
        if( $ord < 127){
            $i ++;
        }else if( $ord < 224 ){
            $i += 2;
        }else{
            $i += 3;
        }
        $count ++;
    }
    
    $count  = 0;
    $substr = '';    
    
    /* 截取$len個字符 */
    while( $i < $slen && $count < $len){
        $ord = ord( $str{$i} );
        
        if( $ord < 127){
            $substr .= $str{$i};
            $i ++;
        }else if( $ord < 224 ){
            $substr .= $str{$i} . $str{$i+1};
            $i += 2;
        }else{
            $substr .= $str{$i} . $str{$i+1} . $str{$i+2};
            $i += 3;
        }
        $count ++;
    }
    
    return $substr;
}

而最終的cnSubstr()可以設計如下(程序還有很多優化的余地):

function cnSubstr( $str, $start, $len, $encode = 'gbk' ){
    if( extension_loaded("mbstring") ){
        //echo "use mbstring";
        //return mb_substr( $str, $start, $len, $encode );
    }

    $enc = strtolower( $encode );
    switch($enc){
		case 'gbk':
		case 'gb2312':
			return cnSubstr_gbk($str, $start, $len);
			break;
		case 'utf-8':
		case 'utf8':
			return cnSubstr_utf8($str, $start, $len);
			break;
		default:
			//do some warning or trigger error;
	}

}

簡單的測試一下:

$str = "這是中文的字符串string,還有abs· ";
for($i = 0; $i < 10; $i++){
         echo cnSubstr( $str,  $i, 3, 'utf8').PHP_EOL;
}

最後貼一下ThinkPHP extend中提供的msubstr函數(這是用正則表達式做的substr):

function msubstr($str, $start=0, $length, $charset="utf-8", $suffix=true) {
    if(function_exists("mb_substr"))
        $slice = mb_substr($str, $start, $length, $charset);
    elseif(function_exists('iconv_substr')) {
        $slice = iconv_substr($str,$start,$length,$charset);
        if(false === $slice) {
            $slice = '';
        }
    }else{
        $re['utf-8']   = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
        $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
        $re['gbk']    = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
        $re['big5']   = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
        preg_match_all($re[$charset], $str, $match);
        $slice = join("",array_slice($match[0], $start, $length));
    }
    return $suffix ? $slice.'...' : $slice;
}

由於文章篇幅問題,更多的問題,這裡不再細說。還是那句話,有任何問題,歡迎指出。

參考文獻:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved