关于静态变量和方法的问题也是面试中常常会出现的问题,这种问题多看手册搞明白原委就能解决,只是确实关于静态变量的问题仍是比较绕的,这里咱们就结合手册用实际的代码来看!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调用静态方法会报错,也就是说,$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
看着代码不少,其实都是在讲一件事儿,若是是普通的$a和$b,那么每次都在从新赋值,echo出来的都是0,可是静态属性可不同。静态属性是运行时计算的,只在第一次赋值的时候是真正的赋值操做,然后并不会进行赋值,能够至关于这一行代码不存在。工具
静态变量只在局部的做用域中存在,离开这个做用域也不会丢失,固然也不能再次初始化。学过前端的同窗必定会拍案而起,这不是闭包的做用域嘛??确实很像,并且用处也很是像,好比咱们作一个递归:测试
function test1() { static $count = 0; $count++; echo $count; if ($count < 10) { test(); } $count--; } test1();
在不了解static以前,结束递归咱们可能须要给方法传递一个数字进来,但如今彷佛是不须要了,使用内部的静态变量就能够解决了。this
引用对象问题
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 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静态的特性讲解的差很少了。在实际应用中仍是要综合考虑,不能由于静态属性方便就全都使用静态属性和方法或者彻底不使用,仍是要结合各路业务需求进行取舍。
具体代码:
https://github.com/zhangyue0503/php/blob/master/newblog/php-static.php
本文由博客群发一文多发等运营工具平台 OpenWrite 发布