先前同事詢問有關 PHP static 關鍵字的用法,這裡我簡單整理一下。

static 主要用途在於定義一個變數空間,讓函式或類別可以保留住該變數的值,直到下次的存取。

以下就各別來探討 static 在函式與類別中的用法。

函式裡的 static 關鍵字

先來看看以下的例子:

function getCount()
{
static $count = 0;
$count ++;
return $count;
}
for ($i = 0; $i < 10; $i ++) {
echo getCount(), "\n";
}
/* output:
1
2
3
4
5
6
7
8
9
10
*/

首先我們在 getCount() 這個方法中定義了一個 static 變數 $count ,然後每次呼叫 getCount() 時,就會對 $count 作累加的動作。

接著我們透過迴圈執行十次 getCount() 方法,便可得到了 1 ~ 10 的輸出結果。

這是因為將變數宣告為 static 後,第一次呼叫 getCount() 這個方法時,會為 $count 保留一塊記憶體空間;而當脫離了 getCount() 的變數作用域後,這個記憶體空間並不會被消滅掉,而會在下一次呼叫 getCount() 方法時,再次被配置進來,並還原先前的變數值。

因此,除了第一次呼叫 getCount() 方法外,接下來的每次呼叫都會讓 $count 值累加,而得到 1 ~ 10 的輸出結果;如果把 static 關鍵字拿掉,就會輸出十次的 1 。

應用在遞迴上的 static 關鍵字

瞭解函式中的 static 關鍵字用法後,我們來看一個應用的例子:

function fibV1($n)
{
if ($n <= 2) {
return 1;
} else {
return fibV1($n - 2) + fibV1($n - 1);
}
}

這是 PHP 版的 Fibonacci Sequence 遞迴函式,原理我就不多說明了。先來看看它在參數為 20 時所需要的執行時間:

$start_time = (float) microtime(true);
echo fibV1(20), "\n";
$end_time = (float) microtime(true);
echo "Spent Time: ", ($end_time - $start_time), "(s)\n";
/* output:
6765
Spent Time: 0.26100397109985(s)
*/

這裡代入的數字越大,執行時間會越長。

現在我們用 static 關鍵字來改寫:

function fibV2($n)
{
static $result = array();
if (!isset($result[$n])) {
if ($n <= 2) {
$result[$n] = 1;
} else {
$result[$n] = fibV2($n - 2) + fibV2($n - 1);
}
}
return $result[$n];
}

然後再來看執行的時間:

$start_time = (float) microtime(true);
echo fibV2(20), "\n";
$end_time = (float) microtime(true);
echo "Spent Time: ", ($end_time - $start_time), "(s)\n";
/* output:
6765
Spent Time: 0.0009009838104248(s)
*/

速度竟然差了快三百倍!為什麼?

其實是因為這裡的 static 關鍵字扮演了 cache 的角色 () ,讓程式不用重新計算已經算好的結果。而使用了 static 關鍵字後,也使得執行時間不會再隨著代入數字變大而變長。

註:不是任何遞迴函式都可以用 static 變數來做 cache ,在使用上要特別注意這一點。

類別裡的 static 關鍵字

在類別裡的 static 關鍵字,也扮演了類似的角色。我們利用 static 在類別裡定義的屬性,會佔用類別的記憶體空間。而透過同一類別所生成的物件,會彼此共享這個 static 屬性;所以不論我們產生多少同類別的物件,它們都會存取到同一個 static 類別屬性。

來看看以下的例子:

class DB
{
private static $_instance = null;
private static $_instanceCount = 0;
private function __construct()
{
self::$_instanceCount ++;
}
public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public function getInstanceCount()
{
return self::$_instanceCount;
}
}
$db1 = DB::getInstance();
echo $db1->getInstanceCount(), "\n"; // 1
$db2 = DB::getInstance();
echo $db2->getInstanceCount(), "\n"; // 1

範例即為經典的 Singleton 模式,原理這裡先不多做說明。

這裡我們定義了兩個類別 static 屬性,分別是 $_instance$_instanceCount ;而透過 static 定義的類別屬性,在類別裡存取時要用 self 關鍵字加上雙冒號 (::) ,例如 self::$_instance

接著我們可以看到在 getInstance() 方法中,如果 self::$_instance 是 null 的話,表示是第一次呼叫,那麼程式就會透過私有的建構式產生一個 DB 物件指定給 self::$_instance 變數,最後再將它回傳出去。這時雖然 getInstance() 裡的變數作用域已經結束,但 self::$_instance 卻會保留下來。

下一次 getInstance() 呼叫時,因為 self::$_instance 已經不再是 null 值,所以就會直接將其內容回傳給呼叫的程式了。

也因為這個原因,所以整個程式執行下來, DB 的 __construct() 方法也只被執行過一次,使得 self::$_instanceCount 也只累加一次,其結果永遠為 1 。

結論

一般 PHP 開發者很少會去使用 static 關鍵字,因為平常會用到 static 的場合其實也不多。這裡我再做一次 static 使用時機的重點整理:

  • 需要記住上一次函式執行的結果。
  • 某些可以保留執行結果的遞迴函式。
  • 不希望因為物件個體不同,進而被影響的類別屬性。
  • 類別的 Singleton 模式。

希望透過上面的說明,能讓大家對 static 關鍵字有進一步的瞭解。