關於靜態變量和方法的問題也是面試中常常會出現的問題,這種問題多看手冊搞明白原委就能解決,只是確實關於靜態變量的問題仍是比較繞的,這裏咱們就結合手冊用實際的代碼來看!php
class Test {
static $v = 'a';
static function showV() {
echo self::$v;
}
function showVV() {
echo self::$v;
}
static function showVVV() {
// $this->showVV(); // 會直接報錯
}
}
複製代碼
先準備一個類,這裏面有靜態變量、靜態方法,其中showV()方法是靜態方法調用靜態變量,showVV()方法是普通方法調用靜態變量,showVVV()方法是普通方法調用靜態方法。前端
從註釋中能夠看出第一個問題,普通方法使用this這個東東對於一切靜態的東西都是不友好的,不信您打開註釋試試,也能夠去調用靜態的$v變量,直接就是語法錯誤的提示。git
接下來,咱們實例化類,並開始一些測試github
$t = new Test();
$t->showV();
//echo $t->v; // 報異常
echo Test::$v;
//Test::showVV(); // 報異常
$t->showVV();
複製代碼
那麼問題來了,靜態方法中不能使用$this,如何得到變量內容呢?請參考單例模式,未來咱們會在設計模式的系列文章中講到,這裏先賣個關子,你們也能夠本身研究下。面試
上面是正常來講一些比較簡單的靜態屬性和方法的演示,接下來好玩的東西就來了。設計模式
初始化特性閉包
class Calculate {
function cacl() {
static $a = 1;
echo $a;
$a++;
}
static function cacl2() {
static $a = 1;
echo $a;
$a++;
}
static $b = 1;
static function cacl3() {
echo self::$b;
self::$b++;
}
}
$calculate = new Calculate();
$calculate->cacl(); // 1
$calculate->cacl(); // 2
Calculate::cacl2(); // 1
Calculate::cacl2(); // 2
Calculate::cacl3(); // 1
Calculate::cacl3(); // 2
複製代碼
看着代碼不少,其實都是在講一件事兒,若是是普通的b,那麼每次都在從新賦值,echo出來的都是0,可是靜態屬性可不同。靜態屬性是運行時計算的,只在第一次賦值的時候是真正的賦值操做,然後並不會進行賦值,能夠至關於這一行代碼不存在。測試
**靜態變量只在局部的做用域中存在,離開這個做用域也不會丟失,固然也不能再次初始化。**學過前端的同窗必定會拍案而起,這不是閉包的做用域嘛??確實很像,並且用處也很是像,好比咱們作一個遞歸:this
function test1() {
static $count = 0;
$count++;
echo $count;
if ($count < 10) {
test();
}
$count--;
}
test1();
複製代碼
在不瞭解static以前,結束遞歸咱們可能須要給方法傳遞一個數字進來,但如今彷佛是不須要了,使用內部的靜態變量就能夠解決了。spa
引用對象問題
class Foo {
public $a = 1;
}
function getRefObj($o) {
static $obj;
var_dump($obj);
if (!isset($obj)) {
$obj = &$o;
}
$obj->a++;
return $obj;
}
function getNoRefObj($o) {
static $obj;
var_dump($obj);
if (!isset($obj)) {
$obj = $o;
}
$obj->a++;
return $obj;
}
$o = new Foo;
$obj1 = getRefObj($o); // NULL
$obj2 = getRefObj($o); // NULL
$obj3 = getNoRefObj($o); // NULL
$obj4 = getNoRefObj($o); // Foo
複製代碼
又是一大串代碼,啥也不說,先複製下來運行一下看看結果是否是同樣。在使用引用對象時,咱們賦值的是內存引用地址。可是一樣的緣由,靜態屬性是運行時產生的,而引用地址不是靜態地存儲,因而,賦不上值了唄,永遠會是NULL。不信你接着用getRefObj()再生成幾個試試。實際應用中反正要記住,這種狀況下千萬不要把引用值賦給靜態變量就好了,而上面緣由的理解確實仍是比較繞的,能講明白最好,講不明白就記住這個事兒。
後期靜態綁定
class A {
static function who() {
echo __CLASS__ . "\n";
}
static function test() {
self::who();
}
}
class B extends A {
static function who() {
echo __CLASS__ . "\n";
}
}
B::test(); // A
複製代碼
先看這一段,使用self輸出的結果會是A,但若是使用普通的類實例化,而且使用普通方法的話,輸出的會是B,你們能夠嘗試下。緣由呢,就是self是取決於當前定義方法所在的類。這就是靜態屬性方法的另外一大特色,不實例化,跟隨着類而不是實例。
class A{...},這個東西叫作類,是對現實的抽象,咱們能夠理解爲一個模板,這裏面的東西是假的,沒有生命的。a纔是對象,至關因而複製一了個模板作了一個真的東西出來,是有生命的。就好像咱們作一個錘子,須要一個模具,這玩意就是類,而後澆鑄金屬後成型拿出來,這玩意就是對象。一個對象有真正的內存地址空間的。
非靜態的屬性和方法是在對象中的,是咱們澆進去的金屬。也就是new了以後纔有的東西,而靜態屬性和方法是依附於class A的,是運行時進行編譯讀取的。
如今咱們回過頭來看最先的例子,普通方法中調用靜態方法或變量,實際上就是在這個實例化對象中調用了Test::showV(),只是咱們使用了self關鍵字而已。依然是走的靜態過程而不是這個對象中真的包含了showV()這個方法,所以,$this固然取不到啦!
那麼,如何讓父類A中test()方法去調用到子類的who()方法呢?
class AA {
static function who() {
echo __CLASS__ . "\n";
}
static function test() {
static::who();
}
}
class BB extends AA {
static function who() {
echo __CLASS__ . "\n";
}
}
BB::test(); // BB
複製代碼
沒錯,使用static::關鍵字這種形式調用,static表示運行最初時的類,不是方法定義時的類。這樣就完成了後期靜態綁定。另外,parent::和self::是會轉發這個鏈條的。
class AAA {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__ . "\n";
}
}
class BBB extends AAA {
public static function test() {
AAA::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__ . "\n";
}
}
class CCC extends BBB {
public static function who() {
echo __CLASS__ . "\n";
}
}
CCC::test(); // AAA、CCC、CCC
複製代碼
這個例子看着很繞,但其實結論就一個,若是父類使用了static關鍵字來調用父子類都有的內容,那麼就是以哪一個子類在外面進行調用了爲準,就像普通類的方法調用 同樣。反過來,self就是以這個self關鍵字所在的類爲準。
說了這麼多,也算是把static靜態的特性講解的差很少了。在實際應用中仍是要綜合考慮,不能由於靜態屬性方便就全都使用靜態屬性和方法或者徹底不使用,仍是要結合各路業務需求進行取捨。
具體代碼: github.com/zhangyue050…