2013年7月14日

String 字串型態

String 字串型態
一個字串(string)就是由一系列的字元(character)組成,一個字元等於一個位元組(byte)。
這意味了PHP只支援256字元集,因此不支援unicode。

語法
一個字串可以用四種方式宣告:
1. 單引號
2. 雙引號
3. Heredoc 語法
4. Nowdoc 語法(自PHP 5.3.0起)

單引號
定義一個字串最簡單的方法是用單引號將他包圍起來。
如果字串內有使用到單引號或者是反斜線,需要在它的前面加上反斜線(\)來跳脫。
其他用反斜線所要表示的跳脫字元,反斜線只會被當作一個反斜線。
意思是當你使用例如\n或者是\r並不代表任何跳脫字元,單純的就是指這兩個字元。

Note:不像雙引號或者是heredoc語法,在單引號字串中的變數和跳脫序列中的特殊字元將不會被替換。

雙引號
如果字串是用雙引號包住,PHP將對特殊字元進行解析。

以下說明跳脫序列以及所代表的意義
\n, 換行符號 (LF or 0x0A (10) in ASCII)
\r, 回車(返回)符號 (CR or 0x0D (13) in ASCII)
\t, 水平定位點符號 (HT or 0x09 (9) in ASCII)
\v, 垂直定位點符號 (VT or 0x0B (11) in ASCII) (自 PHP 5.2.5 起)
\e, 跳脫符號 (ESC or 0x1B (27) in ASCII) (自 PHP 5.4.0 起)
\f, 換頁符號 (ASCII 字符集中的 FF 或 0x0C (12))(自 PHP 5.2.5 起)
\\, 反斜線
\$, 美元符號
\", 雙引號
\[0-7]{1,3}, 用符合該正規表示法的八進位來表示某個字元,例如\101代表A
\x[0-9A-Fa-f]{1,2}, 用符合該正規表示法的十六進位來表示某個字元,例如\x41代表A(大寫X也可以,PHP5.4版本測試)

除了上述所提到的跳脫序列,其他使用反斜線跳脫的情況,都會導致反斜線被顯示出來。P
HP5.1.1前,\{$var}中的反斜線還不會被顯示出來。
用雙引號定義的字串最重要的特點是變數的內容會被解析。

Heredoc語法
使用<<<運算子,緊接在後面加上一個識別符,接著換行。接下來就是字串String本身,最後要用前面定義的識別符號作為結束標誌。
結束時的識別符號必須在該行的第一列,而且識別符號的命名也要遵守PHP的規格,只能使用字母、數字和底線,並且必須是底線或字母開頭。

Warning
要注意的是結束識別符號這行除了可能有一個分號外,絕對不能包含其他字元,這意味識別符號不能使用縮排,分號的前後也不可以有任何空白或定位符號(\t,\v)。更重要的是結束識別符號的前面必須是一個被本地作業系統認可的換行符號,比如在UNIX系統是\n,Mac OS X是\r,而結束識別符號(可能會接著分號)之後也必須緊接著一個換行符號。

如果不遵守該規則導致結束識別符號不乾淨,PHP將認為它不是結束識別符號而繼續尋找。如果在該文件結束前沒有找到一個正確的結束識別符號,PHP將會在最後一行產生一個解析錯誤。

Heredoc語法不能用來初始化類別的屬性。自PHP 5.3起,此限制僅對 heredoc語法內有變數時有效。

Heredoc語法就像是沒有使用雙引號的雙引號字串,在Heredoc語法內的單引號不需要使用反斜線跳脫。
上面所列出的跳脫序列也可以使用,變數將被替換成變數內容,但在Heredoc語法有複雜的變數時要格外小心

<?php
$str = <<<EOD
Example of string
spanning multiple lines
using heredoc syntax.
EOD;
 
/* 含有變數的更複雜範例 */
class foo
{
    var $foo;
    var $bar;
 
    function foo()
    {
        $this->foo = 'Foo';
        $this->bar = array('Bar1', 'Bar2', 'Bar3');
    }
}
 
$foo = new foo();
$name = 'MyName';
 
echo <<<EOT
My name is "$name". I am printing some $foo->foo.
Now, I am printing some {$foo->bar[1]}.
This should print a capital 'A': x41
EOT;
?>

自PHP 5.3.0起還可以用Heredoc語法用雙引號標示出識別符號

Nowdoc語法
Nowdoc語法是類似於單引號字串的。Nowdoc語法類似Heredoc語法,但是在Nowdoc語法中不進行解析操作。這種語法很適合用於嵌入PHP代碼,或其他大量文字而無需對內容中的特殊字元進行解析。與SGML的<![CDATA[]]>語法是用來宣告大量不用解析的文字段落類似。

Nowdoc語法的宣告方式跟Heredoc語法一樣,只是開始的識別符號必須用單引號括起來,即<<<'EOT',其餘規則同Heredoc語法。

<?php
$str = <<<'EOD'
Example of string
spanning multiple lines
using nowdoc syntax.
EOD;
 
/* 含有變數的更複雜的例子 */
class foo
{
    public $foo;
    public $bar;
 
    function foo()
    {
        $this->foo = 'Foo';
        $this->bar = array('Bar1', 'Bar2', 'Bar3');
    }
}
 
$foo = new foo();
$name = 'MyName';
 
echo <<<'EOT'
My name is "$name". I am printing some $foo->foo.
Now, I am printing some {$foo->bar[1]}.
This should not print a capital 'A': x41
EOT;
?>

Note:Nowdoc語法可以用在任意的靜態數據環境中,最典型的例子是用來初始化類別的屬性或者是變數。
Note:Nowdoc語法是在PHP5.3.0中加入的

變數解析
當字串用雙引號或Heredoc語法定義時,其中的變數將會被解析為變數內容。
這裡有兩種語法規則,一種簡單規則,另一種複雜規則。
複雜規則語法的標記是用大括號包圍的表達示。

簡單語法
當PHP解析器遇到一個$符號時,會去組合盡量多的字元形成一個合法的變數名稱。可以用大括號來明確表示變數名稱的範圍。

<?php
// 上述的情況會出現一個E_NOTICE的錯誤,
// 找不到一個變數名稱為$log_filename_error的變數。
 
$log_filename = "2013-07-04";
echo "log_$log_filename_error.log";
?>

複雜語法
之所以稱作複雜語法是因為它可以使用複雜的表達式。
任何具有string表達的變數,陣列單元或者是物件屬性都可以使用此語法,只需要簡單在string以外的地方寫出表達式,然後用大括號將它括起來即可。
由於{無法被解析,只有$緊接著{才會被識別。

<?php
// 顯示所有錯誤
error_reporting(E_ALL);
 
$great = 'fantastic';
 
// 無效,輸出: This is { fantastic}
echo "This is { $great}";
 
// 有效,輸出: This is fantastic
echo "This is {$great}";
echo "This is ${great}";
 
// 有效
echo "This square is {$square->width}00 centimeters broad."; 
 
// 有效,只有通過花括弧語法才能正確解析帶引號的鍵名
echo "This works: {$arr['key']}";
 
// 有效
echo "This works: {$arr[4][3]}";
 
// 這是錯誤的運算式,因為就象 $foo[bar] 的格式在字串以外也是錯的一樣。
// 換句話說,只有在 PHP 能找到常量 foo 的前提下才會正常工作;這裡會產生一個
// E_NOTICE (undefined constant) 級別的錯誤。
echo "This is wrong: {$arr[foo][3]}"; 
 
// 有效,當在字串中使用多重陣列時,一定要用括弧將它括起來
echo "This works: {$arr['foo'][3]}";
 
// 有效
echo "This works: " . $arr['foo'][3];
 
echo "This works too: {$obj->values[3]->name}";
 
echo "This is the value of the var named $name: {${$name}}";
 
echo "This is the value of the var named by the return value of getName(): {${getName()}}";
 
echo "This is the value of the var named by the return value of $object->getName(): {${$object->getName()}}";
 
// 無效,輸出: This is the return value of getName(): {getName()}
echo "This is the return value of getName(): {getName()}";
?>

Note:函數、方法、靜態類別變數和類別常數只有在PHP5以後才可以在{$}中使用。指單一使用大括號({})無法處理從函數或方法的回傳值或者類別常數以及類別靜態變數的值。

存取或修改字串中的字元
string中的字元可以透過一個從0開始的偏移量,用類似array中的方括號加上對應的數字來存取或修改,比如$str[42]。

Warning
用超出字串長度的偏移量將會使得該字串變長,沒有指定的部分用空格填充。
非整數的偏移量會被轉換成整數,並產生一個E_NOTICE等級錯誤。用負數偏移量讀取字串回傳空字串。
只有第一個字元會被賦值。(指得是可能是多字節字元)
將空字串賦值給的值是NULL。

Warning
在內部,PHP的字串是位元組組合的陣列。所以透過陣列括號方式存取或修改多位元組字串是不安全的,應該只能用在單位元組編碼的字串,例如ISO-8859-1。
自PHP5.4起,字串偏移量只能使用整數或者是可被轉換成整數的字串,否則會產生警告。在此版本之前,自動轉換而不會產生警告。

Note:用[]或{}存取任何其他類型(不包含陣列或具有相對應接口的物件)的變數只會無聲地回傳NULL。

有用的函數和運算符號
字串可以用"."(點)運算符號進行連接,注意"+"(加號)運算符號沒有這個功能。

轉換成字串
一個值可以透過在前面加上(string)或用strval()函數來轉換成字串。在一個需要字串的表達式中,會自動轉換為字串。
比如在使用函數echo或print時,或在一個變數和另外一個字串進行比較時,就會發生這種轉換。

一個布林值的true被轉換成字串的"1",布林的false被轉換成""(空字串)。
一個整數或浮點數被轉換為數字的字面樣式的字串(包含浮點數中的指數部分)。使用指數表示法的浮點數(4.1E+6)也可以轉換。

Note:在指令稿的區域(category LC_NUMERIC)中定義了十進位小數點的字元,詳情參見setlocate()。

陣列array總是轉換成字串"Array",因此echo和print無法顯示出該陣列的內容。要顯示某個單元,可以用echo $arr["foo"]。要顯示整個陣列內容請看下文。
在PHP4中物件object總是被轉換成字串"Object",如果為了測試需要印出物件的值,請繼續閱讀下文。為了得到物件的類別名稱,可以用get_class()函數。自PHP5起,適當時可以用__toString方法。

資源resource總是會被轉換成字串"Resource id #1"這種結構的字串,其中的1是PHP在執行階段分配給該resource的唯一值。不要依賴此結構,可能會有變更。要得到一個resource的類型,可以用函數get_resource_type()

NULL總是被轉換成空字串。

如上所說,直接把array、object或resource轉換成string不會得到除了其類型之外的任何有用訊息。可以使用函數print_r()和var_dump()列出這些類型的內容。
大部分的PHP值可以轉變成string來永久保存,這被稱為序列化,可以用函數serialize()來實現。如果PHP引擎設定支援WDDX,PHP值也可以被序列化為隔式良好的XML文件。

字串轉換為數值
當一個字串被當作一個數值來取值,其結果和類型如下
如果該字串沒有包含"."、"e"和"E",並且其數字值在整數類型的範圍之類(由PHP_INT_MAX所定義),該字串將被當成整數來取值。其他所有情況下都被當作浮點數來取值。

該字串的開始部分決定了它的值。如果該字串以合法的數值開始,則使用該數值。否則其值為0(零)。合法數值由可選的正負號,後面跟著一個或多個數字(可能有小數點),再跟著可選的指數部分。指數部分由"E"或"e"後面跟著一個或多個數字構成。

在C語言中,直接將一個字元轉換成整數可以直接取得ASCII代碼。
但是在PHP必須使用函數ord()或chr()實現ASCII和字元間的轉換。

字串類型詳解(重要)
PHP中的string的實作是一個由位元組組成的陣列再加上一個整數指定緩衝區長度。並沒有儲存如何將位元組轉換成字元的資訊,這部分交由程式設計師來決定。
字串由什麼值來組成並無限制。特別是,其值為0("NUL  Bytes")的位元組可以處於字串任何位置(不過有幾個函數,在本手冊中被稱為非"二進制安全"的,也許會把NUL位元組之後的數據全都忽略)。

字串類型的此性質解釋了為什麼PHP中沒有單獨的"byte"類型。已經用字串來代替了。
返回非文字的函數 - 例如從網路socket讀取的任意數據 - 仍會回傳字串。
(意思是從socket傳送過來的任何數據,透過functions讀取會回傳字串。)

由於PHP並不特別指名字串的編碼,那字串到底是怎樣編碼的呢?例如字串"á"到底是等於"\xE1"(ISO-8859-1)、"\xC3\xA1"(UTF-8,C form),"\x61\xCC\x81"(UTF-8,D form)還是任何其他可能的方式呢?答案是字串會被按照該指令稿文件(PHP檔案)相同的編碼方式來編碼。因為如果一個指令稿的編碼是ISO-8859-1,則其中的字串也會被編碼為ISO-8859-1,以此類推。不過這並不適用於啟動了Zend Multibyte時。此時指令稿可以是以任何方式編碼的(明確指定或自動檢測),然後被轉換為某種內部編碼,然後字串將被用此方式編碼。注意指令稿的編碼有一些約束(如果啟動了Zend Multibyte且是其內部編碼),這意味著此編碼應該是ASCII的兼容超集,例如UTF-8或ISO-8859-1,不過要注意,依賴狀態的編碼其中相同的位元組可以用於首字母和非首字母而轉換狀態,這可能會造成問題。

PS:有沒有開啟Zend Multibyte可以透過phpifo()函式查看Configure Command欄位是否有--enable-zend-multibyte。此選項是Zend引擎的一部分,被用來自動偵測指令稿以及檔案是否為萬國碼編碼,它搜尋檔案的開始BOM來分辨是否編碼。

當然,要使之作用正確,操作文字的函數必須假設字串是如何編碼的,不幸的是,PHP關於此的函數有很多變種情況:
* 某些函數假設字串是以單位元組編碼,並不需要將位元組解釋為特定的字元,例如substr()、strpos()、strlen()和strcmp()。理解這些函數的另一種方法是它們作用於記憶體緩衝區,即按照位元組和位元組偏移量操作。
* 某些函數被傳入了字串的編碼方式,也可能會預設無此資訊。例如htmlentities()和mbstring()擴充函式庫中的大部分函數。
* 其他函數使用了目前本機設定(可看setlocate()),但是逐位元組操作。例如strcasecmp()、strtoupper() 和 ucfirst()。這意味著這些函數只能用於單位元組編碼,而且編碼要與區域符合。例如strtoupper("á") 在本機設定正確並且á是單位元組編碼時會回傳"Á"。如果是用UTF-8編碼則不會回傳正確結果,其結果根據目前本機設定有可能回傳損毀的值。
* 最後一些函數會假設字串是使用某特定編碼的,通常是UTF-8。intl擴充函式庫和PCRE(上例中僅在使用了u修飾符號)擴展函式庫中的大部分函數都是這樣。儘管這是由於特殊用途, utf8_decode()會預設UTF-8編碼,而utf8_encode()會預設ISO-8859-1編碼。

最後,要書寫能夠正確使用unicode的程序取決於很小心地避免那些可能會損壞數據的函數。要使用來自於intl和mbstring擴充函式庫的函數。
不過使用能處理unicode編碼的函數只是個開始。不管用何種語言提供的函數,最基本的還是了解unicode規格。
例如一個程序如果假設只有大寫跟小寫字母,那可是大錯特錯。

沒有留言:

張貼留言