2017年5月21日

優化 Unity 的編譯時間

先講測試結果,進行簡單的檔案搬移使得編譯時間減少了25%,
從原本的16秒變成12秒。
因為某些原因的關係這個結果還不是最好的結果,這個後續會說明。

做法:

1. 觀察所需的編譯時間,可以透過 compile-time-tracker 。
2. 搬移不常改動的 C# 檔案搬到 Assets/Plugins 或 Assets/Standard Assets 資料夾。
例如:第三方的插件 ( NGUI ),某某框架之類的。

原因:

利用 Unity 第一階段 (Phase 1)編譯工作範圍內的腳本,如果沒有改變的話,不會重新編譯。
https://docs.unity3d.com/Manual/ScriptCompileOrderFolders.html

注意事項:

1. 根據參考資料 3 此優化技巧可能在某些 Unity 版本失效,我的版本是 Unity 5.3.6p7。
2. 在  Assets/Plugins 與 Assets/Standard Assets 內的程式碼無法引用外部的程式碼。

後記:

因為注意事項第二條的關係,專案內開發框架因為有互相引用的關係,導致無法進行簡單的搬移,必須對程式碼進行調整。

參考資料:

1. http://qiankanglai.me/2016/11/27/unity-compiler/
2. http://forum.china.unity3d.com/thread-13028-1-1.html
3. https://medium.com/@darrentsung/the-clocks-ticking-how-to-optimize-compile-time-in-unity-45d1f200572b

2016年9月16日

Use luacheck In Sublime Text 3

安裝 Lua 在 Windows
https://code.google.com/archive/p/luaforwindows/

安裝 luacheck
https://github.com/mpeterv/luacheck
透過 Manual installation 來安裝

利用小工具新增 luacheck.bat 路徑到 PATH
http://patheditor2.codeplex.com/

安裝兩個 sublime text 外掛
https://packagecontrol.io/packages/SublimeLinter
https://packagecontrol.io/packages/SublimeLinter-luacheck

2014年7月6日

用資料表導向法改寫 switch。

簡單的 switch 改用資料表導向法改寫,重複代碼更少。

原本的寫法如下,此方法大概使用了30行左右,且有大部份的重複代碼。
function getDayOfWeek1($type)
{
    switch ($type) {
        case 1:
            $string = "一";
            break;
        case 2:
            $string = "二";
            break;
        case 3:
            $string = "三";
            break;
        case 4:
            $string = "四";
            break;
        case 5:
            $string = "五";
            break;
        case 6:
            $string = "六";
            break;
        case 7:
            $string = "七";
            break;
        default:
            throw new Exception("unknown item type");
            break;
    }
 
    return $string;
}

改用資料表導向的方式改寫後,方法的行數約20行,且新增一個方法所需要新增的程式碼更少。
function getDayOfWeek2($type)
{
    $table = array(
        1 => "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
    );
 
    if (isset($type, $table)) {
        $string = $table[$type];
    } else {
        throw new Exception("unknown item type");
    }
 
    return $string;
}

接著測試一下兩者的效能,利用PHP執行下列的結果

$start = microtime(true);
for ($i=1; $i <= 90000; $i++) {
    $type = mt_rand(1, 7);
    getDayOfWeek1($type);
}
$end = microtime(true) - $start;
echo $end."\n";
// 0.042860984802246
 
$start = microtime(true);
for ($i=1; $i <= 90000; $i++) {
    $type = mt_rand(1, 7);
    getDayOfWeek2($type);
}
$end = microtime(true) - $start;
echo $end."\n";
// 0.12082099914551

方法一的執行效率比方法二高得多,幾乎是二到三倍,隨著判斷式的增加,甚至可以到五倍左右。

雖然在閱讀上方法二比方法一可能更一目了然,但是實際執行的結果卻是方法一比較高效率。

隔天早上起床後,想想為什麼會變慢的原因,赫然發現應該是建立資料表查詢用的陣列每次都重新建立,接著將該變數加上 static 後,兩個方式的查詢時間就差不多了,用空間換時間的典型例子。

2014年6月29日

php 日期比較

在看 code 的時候第一次看到有人使用下列的判斷式來判斷時間是否在區間內
<?php
if (date("Y-m-d H:i:s") >= "2014-06-29 00:00:00" 
    && date("Y-m-d H:i:s") <= "2014-06-29 23:59:59") {
    // 在活動時間內 做某些事
}

1. 兩個判斷式需要與現在時間進行判斷,卻呼叫兩次相同的 date function,有可能造成效能上的耗損,類似的情況如下面程式碼,每次迴圈執行 count 都會被執行,這個例子是在 $arr 不會因為迴圈而使得大小變動,那麼就不應該每次都執行。
<?php
$arr = rang(1,10000);
// bad
for ($i=0;$i<count($arr);$i++) {
    // do something
}
 
// good
$arrLength = count($arr);
for ($i=0;$i<$arrLength;$i++) {
    // do something
}
2. 雖然說在此,透過相同的時間字串格式進行字串比對達到判斷現在時間是否為活動進行時間是正確的,但是因為數字比對比字串比對來得更有效率,一般是將時間轉換成時間戳記後進行比較。

3. 在字串比較上,是一個一個字元進行比較,因為兩者的時間字串格式相同,加上數字在 ASCII 上的順序剛好等於數字大小順序。

4. 想要找出西元99年來讓此判斷失誤,查閱文件後發現 date 是有範圍限制的。

date function doc
http://php.net/manual/zh/function.date.php

初探 IL 藉由不同判斷寫法

在看別人的 code 時候發現對於布林變數判斷兩種不同的寫法

// 方法 1
using System;
class MainClass{
    public static void Main(){
        bool isOk = false;
        if (isOk) {
            // do something
        }
    }
}

// 方法 2
using System;
class MainClass{
    public static void Main(){
        bool isOk = false;
        if (Ok == true) {
            // do something
        }
    }
}

方法 1 是我常用的寫法,一直以來都是習慣這麼寫,對於方法 2 感覺似乎有點多餘,但不可否認,方法 2 的確是比較明顯。

想確認一下兩者寫法對於 IL 是否有任何差異。
c# 程式碼透過編譯器變成 IL(Intermediate Language) 然後在VM上面執行
於是我透過 Ildasm.exe (IL 反組譯工具) 將 exe 檔案進行反組譯看看裡面的 IL

結果如下

方法 1
.method public hidebysig static void  Main() cil managed
{
    .entrypoint
    // 程式碼大小       11 (0xb)
    .maxstack  1
    .locals init (bool V_0,
             bool V_1)
    IL_0000:  nop
    IL_0001:  ldc.i4.0
    IL_0002:  stloc.0
    IL_0003:  ldloc.0
    IL_0004:  stloc.1
    IL_0005:  ldloc.1
    IL_0006:  brtrue.s   IL_000a
 
    IL_0008:  nop
    IL_0009:  nop
    IL_000a:  ret
} // end of method MainClass::Main

方法 2
.method public hidebysig static void  Main() cil managed
{
    .entrypoint
    // 程式碼大小       11 (0xb)
    .maxstack  1
    .locals init (bool V_0,
             bool V_1)
    IL_0000:  nop
    IL_0001:  ldc.i4.0
    IL_0002:  stloc.0
    IL_0003:  ldloc.0
    IL_0004:  stloc.1
    IL_0005:  ldloc.1
    IL_0006:  brtrue.s   IL_000a
 
    IL_0008:  nop
    IL_0009:  nop
    IL_000a:  ret
} // end of method MainClass::Main
兩者沒有任何差異

Ilasm.exe (IL 組譯工具) 說明
http://msdn.microsoft.com/zh-tw/library/496e4ekx(v=vs.110).aspx

IL 範例說明
http://60-251-1-52.hinet-ip.hinet.net/taiwan/msdn/columns/DoNet/IL.htm

IL 說明
http://hi.baidu.com/fastlei/item/2cad855555b4f6444eff20fd

2014年6月14日

文字編輯器 Sublime Text Part 3 外掛介紹與安裝

很多人提供了自己寫的 Sublime Text 外掛
因為安裝太麻煩,所以有人就寫了一個外掛管理器 Package Control

Package Control 網站
https://sublime.wbond.net/
網站提供了一些不同選項的排名以及搜尋

安裝方式
到這邊 https://sublime.wbond.net/installation 找要輸入的指令
用 Ctrl + ` 打開主控台然後根據不同的 Sublime Text 版本複製指令貼上後按下 ENTER 送出

如何安裝其他的外掛?
1. 按下 Ctrl + Shift + P
2. 輸入 Package Control 會看到 Package Control 相關的選項 (此輸入框可以使用模糊搜尋,例如 pcip)
3. 選擇 Package Control:Install Package
4. 輸入你要安裝的外掛名稱按下 Enter 就會自動安裝了


目前我常用的外掛(依照使用的頻繁度排序)
IMESUPPORT - 用來解決中文輸入法選字介面不在打字浮標附近的問題
Alignment - 程式碼對齊 選取後 Ctrl + Alt + A / Cmd+Ctrl+A
WordHighlight - 因為修改 $導致不會選取
SFTP - 遠端同步編程
AutoFileName - 檔案路徑智慧提示
DocBlockr - 快速註解
All Autocomplete - 全部自動完成
BracketHighlighter - 括號{}高亮顯示(安裝後需設定參數)
SublimeCodeIntel - 智慧提示
SyncedSideBar - 側邊攔與tab頁籤同步
SublimeLinter - PHP 語法檢查
{
    "sublimelinter":"save-oly",
    "sublimelinter_popup_errors_on_save": true,
    "sublimelinter_fill_outlines": true,
    "sublimelimt_executable_map":
    {
        "php":"C:\\xampp\\php\\php.exe"
    }
}
JsFormat - JS格式化插件 Ctrl + Alt + F(需自行定義 keybinding)
Pretty JSON
ConvertToUTF8 - 可開啟 UTF8 以外檔案
Goto Documentation - 快速查看函數說明(需自行定義 keybinding)
ApacheConf.tmLanguage - 支援 .conf 語法顏色
INI - 支援 .ini 語法顏色
Change Tracker - 修改的足跡
Clipboard History - 剪貼簿記錄
AdvancedNewFile

註冊Window右鍵選單
我是使用打包版本,有時候會常用右鍵選單對文件指定我要用哪一個應用程式開啟
但是 Sublime Text 沒有註冊選單,所以我們可以使用下列的文字自訂選單
開啟純文字檔案後貼入以下文字,另存副檔名為 .reg 後,點兩下即可註冊
必須要注意以下文字有設定 Sublime Text 的執行檔位置,必須要先修改成你放的位置才可以正常
內容如下

Windows Registry Editor Version 5.00

; This will make it appear when you right click ON a folder
; The "Icon" line can be removed if you don't want the icon to appear
[HKEY_CLASSES_ROOT\Directory\shell\sublime]
@="Open Folder as Sublime Project"
"Icon"="\"C:\\Sublime Text 2.0.2 x64\\sublime_text.exe\",0"

[HKEY_CLASSES_ROOT\Directory\shell\sublime\command]
@="\"C:\\Sublime Text 2.0.2 x64\\sublime_text.exe\" \"%1\""

; This will make it appear when you right click INSIDE a folder
; The "Icon" line can be removed if you don't want the icon to appear

[HKEY_CLASSES_ROOT\Directory\Background\shell\sublime]
@="Open Folder as Sublime Project"
"Icon"="\"C:\\Sublime Text 2.0.2 x64\\sublime_text.exe\",0"

[HKEY_CLASSES_ROOT\Directory\Background\shell\sublime\command]
@="\"C:\\Sublime Text 2.0.2 x64\\sublime_text.exe\" \"%V\""

; Notepadd++ like open with Sublime Text 2.0.2 x64

[HKEY_CLASSES_ROOT\*\shell\Open with Sublime Text]
@="Open with Sublime Text"
"Icon"="\"C:\\Sublime Text 2.0.2 x64\\sublime_text.exe\",0"

[HKEY_CLASSES_ROOT\*\shell\Open with Sublime Text\command]
@="\"C:\\Sublime Text 2.0.2 x64\\sublime_text.exe\" \"%1\""

文字編輯器 Sublime Text Part 2 我常用的設定

1. 修改編輯器設定
使用選單列打開 Preferences\Settings-User,貼上以下文字
{
    "bold_folder_labels": true,
    "color_scheme": "Packages/Color Scheme - Default/Monokai.tmTheme",
    "default_line_ending": "unix",
    "detect_slow_plugins": false,
    "draw_minimap_border": true,
    "ensure_newline_at_eof_on_save": true,
    "file_exclude_patterns":
    [
        "*.bmp",
        "*.jpg",
        "*.png",
        "*.psd",
        "*.tga",
        "*.mp3",
        "*.wav",
        "*.ttf"
    ],
    "folder_exclude_patterns":
    [
        ".git",
        ".hg",
        ".svn"
    ],
    "font_face": "Inconsolata",
    "font_options":
    [
        "directwrite"
    ],
    "font_size": 11,
    "highlight_line": false,
    "ignored_packages":
    [
        "Vintage"
    ],
    "rulers":
    [
        120
    ],
    "tab_size": 4,
    "translate_tabs_to_spaces": true,
    "tree_animation_enabled": false,
    "trim_trailing_white_space_on_save": true,
    "use_tab_stops": true,
    "word_separators": "./\\()\"'-:,.;<>~!@#%^&*|+=[]{}`~?",
    "word_wrap": "true"
}

2. 修改快速鍵設定
使用選單列打開 Preferences\Key Bindings-User,貼上以下文字
[
    { "keys": ["ctrl+q"], "command": "toggle_comment", "args": { "block": false } },
    { "keys": ["ctrl+shift+q"], "command": "toggle_comment", "args": { "block": true } },
    { "keys": ["ctrl+shift+w"], "command": "close_all" },
    { "keys": ["alt+d"], "command": "open_dir", "args": {"dir": "$file_path", "file": "$file_name"} },
    { "keys": ["alt+h"], "command": "reindent", "args": {"single_line": false} },
    { "keys": ["alt+j"], "command": "js_format"},
    { "keys": ["ctrl+shift+r"], "command": "reveal_in_side_bar"}
]

可以看到在基本設定以及快速鍵設定都有 Default 與 User
Preferences\Settings-Default
Preferences\Settings-User
Preferences\Key Bindings-Default
Preferences\Key Bindings-User

1. 當編輯器進行升級的時候會直接覆蓋 Default 的檔案,所以不建議把設定設定在 Default 的檔案
2. 可以從 Default 的檔案看到有哪些數值可以進行調整,接下來在 User 的檔案進行設定覆寫,因為先載入 Defautl 後載入 User。

文字編輯器 Sublime Text Part 1 特色介紹


官方網站
http://www.sublimetext.com/

中文手冊網站
http://docs.sublimetext.tw/

特色
1. GoTo Anythiong
透過 Ctrl + P 呼叫出輸入框,輸入一部份的檔案名稱或者是利用@跳到某一個符號,利用:跳到某一行。

2. Command Palette
透過指令 Ctrl + Shift + P,可以呼叫出指令面板功能。

3. Split Editing
視窗分隔編輯,可以透過 View/Layout 來選擇不同的切割方式。

4. 客製化
快速鍵,選單,巨集,片段,自動完成等都可以透過簡單的 JSON 檔案進行設定。

5. Multiple Selections
透過 Ctrl + Shift + L 一次選取多行後再利用方向鍵移動可以一次編輯多行。
透過 Ctrl + D 選取一個字,連續的輸入可以同時選取相同的字進行修改,透過 Ctrl + K + Ctrl + D 可以跳過,使用 Ctrl + U 復原。
透過 Ctrl + D 選取一個字,再加上 Shift + F3 就可以馬上選取所有相同的字

6. Distraction Free Mode
F11 可以進入 Full Screen Mode (一般常見)。
Shift + F11 可以進入 Distraction Free Mode (專注於你所編輯的地方)。

7. Instant Project Switch
可以將多個不同路徑的資料夾拖曳進左側 Side Bar 並且將它們存成一個 project,方便後續進行操作。

8. Plugin API
可以利用 Python 來寫編輯器的外掛,並且提供交互式的主控台,可以利用 Ctrl + ` 呼叫主控台。

9. Cross Platform
可以在 OS X ,Windows 以及 Linux 的環境使用

2013年7月20日

Callback 回呼類型

Callback 回呼類型

自PHP 5.4起可用callable型態指定回呼型態callback。
一些函數如call_user_func()或usort()可以接受使用者自行定義回呼函數作為參數。
回呼函數可以是簡單函數,也可以是物件的方法,包括靜態類別方法。

傳遞
一個PHP的函數以字串型態的方式傳遞其回呼函式的名稱。
可以使用任何內建或使用者自定義函數,但是除了語言結構例如array(),echo,empty(),eval(),exit(),isset(),list(),print()或unset()。
一個已經實體化物件的方法做為陣列傳遞,陣列索引0代表該物件,陣列索引1代表方法名稱。
靜態類別方法也可不經實體化該物件而傳遞,只要在陣列索引0中包含類別名稱而不是物件。自PHP 5.2.3起,也可以傳遞'ClassName::methodName'。
除了普通的使用者自定義函數外,create_function()可以用來建立一個匿名回呼函數。自PHP 5.3.0起也可以傳遞closure給回呼函數

<?php 
// An example callback function
function my_callback_function() {
    echo 'hello world!';
}
 
// An example callback method
class MyClass {
    static function myCallbackMethod() {
        echo 'Hello World!';
    }
}
 
// Type 1: Simple callback
call_user_func('my_callback_function'); 
 
// Type 2: Static class method call
call_user_func(array('MyClass', 'myCallbackMethod')); 
 
// Type 3: Object method call
$obj = new MyClass();
call_user_func(array($obj, 'myCallbackMethod'));
 
// Type 4: Static class method call (As of PHP 5.2.3)
call_user_func('MyClass::myCallbackMethod');
 
// Type 5: Relative static class method call (As of PHP 5.3.0)
class A {
    public static function who() {
        echo "An";
    }
}
 
class B extends A {
    public static function who() {
        echo "Bn";
    }
}
 
call_user_func(array('B', 'parent::who')); // A
?>

使用Closure的範例
<?php
// Our closure
$double = function($a) {
    return $a * 2;
};
 
// This is our range of numbers
$numbers = range(1, 5);
 
// Use the closure as a callback here to 
// double the size of each element in our 
// range
$new_numbers = array_map($double, $numbers);
 
print implode(' ', $new_numbers);
?>

Note:在PHP4中,需要使用一個參照來建立一個指向具體物件的回呼函數,而不是一個複製物件,參閱參照的解釋

Note:在函數中註冊多個回呼函數(如使用call_user_func()call_user_func_array()時,如果在前一個調用中有未捕捉的異常,其後將不再被調用。

NULL 變數型態

NULL
NULL代表一個變數沒有值。

下列的情況變數的值被認為NULL
1. 被賦值為NULL
2. 尚未被賦值
3. 變數被unset()

語法
NULL類型只有一個值,就是不區分大小寫的常數NULL。

轉換到NULL
使用(unset)$var將一個變數轉換成NULL將不會刪除該變數或unset()該值。
僅是返回NULL值而已。

Resourece 資源型態

Resourece 資源型態

資源resource是一種特殊變數,保存了外部資源的一個參照。
透過特殊的函式來建立resource。所有的函數以及相對應的資源類型請見附表

轉換成資源型態
由於資源變數存放著打開的文件、資料庫連接、圖形數據區域和其他類似處理的特殊處理器,因此轉換成資源是沒有意義的。

釋放資源
由於PHP第四代Zend引擎引進了參照計數系統,可以自動檢測到一個資源不再被參照了。
這種情況下此資源使用的所有外部資源都會被垃圾回收器所釋放,因此很少需要手動釋放。

Note:持續性資料庫連接比較特殊,它們不會被垃圾回收器所銷毀。請參閱資料庫持續連接章節

Object 物件型態

Object 物件型態

物件初始化
要創建一個新的物件object,使用new語法實體化一個類別。
<?php
class foo
{
    function do_foo()
    {
        echo "Doing foo."; 
    }
}
 
$bar = new foo;
$bar->do_foo();
?>

詳細討論請看類別與物件

轉換成物件
如果將一個物件轉換成物件,它將不會有任何變化。
如果其他變數類型的值被轉換成物件,將會建立一個內建類別stdClass的實例。
如果該值為NULL,則新的實例為空。
陣列轉換成物件將使鍵名稱成為屬性名稱並具有相對應的值。
對於任何其他的值,名為scalar的成員變數將包含該值。
<?php
$obj = (object) 'ciao';
echo $obj->scalar;  // outputs 'ciao'
?>

Array 陣列型態

Arrays 陣列
PHP的Array實際上是一個有序的映射。
映射是一種把values關連到keys的類型。

它可以被當作陣列、列表(list)、向量(vector)、雜湊表(hash table)(是映射的一種實現)、字典(dictionary)、集合(collection)、堆疊(stack), 序列(queue)以及其他更多的可能。

語法
array(
key => value
, ......
,key2 => value2
,key3 => value3
)
鍵(key)可以是一個整數或字串。
值(value)可以是任何型態。

最後一個元素之後的逗號可以省略。即使加上逗號,在計算陣列元素上不會造成影響。
<?php
// 最後一個沒加上逗號
$a = array(1, 2);
echo count($a);  // 2
 
// 最後一個加上逗號
$a = array(1, 2, );
echo count($a);  // 2
?>

自PHP5.4起可以使用簡單定義語法,用[]取代array()。
<?php
$array = array(
    "foo" => "bar",
    "bar" => "foo",
);
 
// 自 PHP 5.4 起
$array = [
    "foo" => "bar",
    "bar" => "foo",
];
?>

Key會有下列提到的強制轉換
1. 包含有合法整數型態的字串值會被轉換為整數型態。例如字串"8"實際會被儲存為整數8。但是"08"則不會被強制轉換,因為不是一個合法的十進位數值。
2. 浮點數會被轉換為整數,小數點部分無條件捨去。例如8.7會被儲存為8。
3. 布林值會被轉換成整數,true會儲存為1,false會被儲存為0。
4. Null會被轉換成空字串,null會被儲存為""。
5. 陣列和物件不能被當作key。如果使用會出現警告Illegal offset type。

注意事項
1. 一個陣列中多個元素使用同一個鍵,則後者會覆蓋前者。
2. 陣列中可以同時擁有字串以及整術類型的鍵。
3. 如果陣列沒有指定鍵,則取當前最大的整數索引值,新的鍵則是該值加一。
4. 如果指定的鍵已經有值,則該值會被覆蓋。
5. 可以只對某些元素指定鍵,其他元素不指定。
6. 取當前最大的整數索引值是指曾經存在過即可。

讀取陣列元素
透過array[key]或者array{key}

Note:試著讀取一個未定義的陣列索引與讀取任何未定義的變數一樣,會導致E_NOTICE等級的錯誤訊息,其結果為NULL。

用[]的語法新增或修改陣列
1. 直接指定該陣列內的key-value,$array[key] = value
2. 利用[]新增一個元素到陣列內,$array[] = value

第二種方法如果陣列不存在則會新建一個。但是不建議使用該方式來新建陣列。初始化變數的最好方式是直接給值。

用unset和鍵刪除某個元素
unset($array[key])

為什麼$foo[bar]錯了?
當你使用這樣的方式存取一個元素時,會出現一個E_NOTICE的錯誤訊息(Notice: Use of undefined constant name - assumed 'foo'),但是程式還是可以正常運作,那是因為PHP先試著尋找一個定義為foo的常數,找不到的時候將foo自動轉換成字串"foo"並使用它。

Note:但是這不代表你總是要將鍵加上引號。如果鍵是一個常數或變數則不需要。

轉換為陣列
對於任意整數類型、浮點數類型、字串類型、布林類型和資源類型,如果將其轉換為陣列,將得到一個僅有一個元素的陣列,其鍵為0,該元素的值為原來的值。

將物件轉換為陣列,則結果為一個陣列,其元素為該物件的屬性,鍵將為屬性變數名稱。但是有幾點要注意,整數屬性不可讀取,私有屬性前面會加上類別名稱作為前綴字,保護屬性前面會加上"*"作為前綴字。這些前綴字的前後都各有一個NULL字元,這會導致一些不可預期的行為。
<?php
// 整數屬性不可讀取
$obj = new stdClass();
$obj->{111} = 222;
var_dump($obj);
 
$arr = (array)$sobj;
var_dump($arr);
 
echo $arr[111]; // 產生 Notice: Undefined offset: 111 錯誤
echo $arr["111"]; // 產生 Notice: Undefined offset: 111 錯誤
?>

下面的例子印出後會看到兩個鍵為AA,但是實際上其中一個是"\0A\0A"。
<?php
class A {
    private $A; // This will become 'AA'
}
 
class B extends A {
    private $A; // This will become 'BA'
    public $AA; // This will become 'AA'
}
var_dump((array) new B());
?>
將NULL轉換陣列會得到一個空陣列。

比較陣列
可以用array_diff()陣列運算子來比較陣列。

直接改變陣列的值自PHP5開始可以透過參照傳遞做到。之前的版本需要採取變通的方法。
注意當使用此方法,該變數可能會影響到原有陣列。
<?php
// PHP 5
foreach ($colors as &$color) {
    $color = strtoupper($color);
}
unset($color); /* ensure that following writes to
$color will not modify the last array element */
 
// Workaround for older versions
foreach ($colors as $key => $color) {
    $colors[$key] = strtoupper($color);
}
 
print_r($colors);
?>

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規格。
例如一個程序如果假設只有大寫跟小寫字母,那可是大錯特錯。