• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 类中的静态属性、静态方法(static)

    PHP 类属性和方法都需要在类实例化后才能调用(常量属性除外),但是,PHP 还提供了静态属性和静态方法。用static关键字来修饰类的属性、方法,被为静态属性、静态方法。static也可用于定义静态变量以及后期静态绑定。声明类属性或方法为静态,就可以不实例化类而直接访问。可以在实例化的类对象中通过静态访问。

    静态变量

    静态变量仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。

    <?php
    function test()
    {
        static $a = 0;
        echo $a;
        $a++;
    }
    

    现在,变量$a仅在第一次调用test()函数时被初始化,之后每次调用test()函数都会输出$a的值并加一。

    <?php
    function test()
    {
        static $count = 0;
    
        $count++;
        echo $count;
        if ($count < 10) {
            test();
        }
        $count--;
    }
    

    函数递归计数到 10,使用静态变量$count来判断何时停止。

    <?php
    function foo(){
        static $int = 0;          // 正确
        static $int = 1+2;        // 正确
        static $int = sqrt(121);  // 错误(因为它是函数)
    
        $int++;
        echo $int;
    }
    

    常量表达式的结果可以赋值给静态变量,但是动态表达式(比如函数调用)会导致解析错误。


    从 PHP 8.1.0 开始,当继承(不是覆盖)使用有静态变量的方法时,继承的方法将会跟父级方法共享静态变量。这意味着方法中的静态变量现在跟静态属性有相同的行为。

    <?php
    class Foo {
        public static function counter() {
            static $counter = 0;
            $counter++;
            return $counter;
        }
    }
    
    class Bar extends Foo {}
    var_dump(Foo::counter()); // int(1)
    var_dump(Foo::counter()); // int(2)
    var_dump(Bar::counter()); // int(3),PHP 8.1.0 之前 int(1)
    var_dump(Bar::counter()); // int(4),PHP 8.1.0 之前 int(2)
    


    类中的静态属性和静态方法

    静态属性,使用范围解析操作符::)访问,不能通过对象操作符->)访问,若通过对象操作符访问,会失败无效,显示不出来静态属性。另外,通过变量来引用一个类是可行的,但这个变量的值不能是一个保留字(例如selfparentstatic

    由于静态方法不需要通过对象即可调用,所以伪变量$this在静态方法中不可用。从 PHP 8.0 开始,在静态方法中,通过$this调用一个非静态方法会抛出 Error。会导致一个 E_DEPRECATED 级别的警告。

    <?php
    class Foo
    {
        public static $my_static = 'foo';
        public function staticValue() {
            return self::$my_static;
        }
    }
    
    class Bar extends Foo
    {
        public function fooStatic() {
            return parent::$my_static;
        }
    }
    
    
    print Foo::$my_static . "\n";
    
    $foo = new Foo();
    print $foo->staticValue() . "\n";
    print $foo->my_static . "\n";      // 调用失败。未定义的 "属性" my_static
    
    print $foo::$my_static . "\n";
    $classname = 'Foo';
    print $classname::$my_static . "\n";
    
    print Bar::$my_static . "\n";
    $bar = new Bar();
    print $bar->fooStatic() . "\n";
    
    • 类中的静态属性和方法,支持访问限制修饰符设置。可以声明为privateprotectedpublic三种可见性级别。
    • 类中的常量,支持访问限制修饰符设置。可以声明为protectedpublic的可见性,使其只在其定义的类的层次范围内可用。


    静态与非静态的区别

    • 静态属性保存在类空间,非静态属性保存在对象空间。非静态方法可以访问类中的任何成员(包括静态),静态方法只能访问类中的静态成员。
    • 静态属性和方法可以直接通过类引用,所以又被称作类属性和类方法。非静态属性和非静态方法需要实例化后通过对象引用,因此被称作对象属性和对象方法。
    • 静态方法可以直接调用,类名调用和对象调用(类名或self::调用),但是非静态方法只能通过对象调用(对象名或$this->调用)。
    • 一个类的所有实例对象,共用类中的静态属性。如果修改了这个类静态属性,那么这个类的所有对象都能访问到这个新值。
    • 静态方法和属性的生命周期跟相应的类一样长,静态方法和静态属性会随着类的定义而被分配和装载入内存中。一直到线程结束,静态属性和方法才会被销毁。非静态方法和属性的生命周期和类的实例化对象一样长,只有当类实例化了一个对象,非静态方法和属性才会被创建,而当这个对象被销毁时,非静态方法也马上被销毁。
    • 静态方法和静态变量创建后始终使用同一块内存,而使用实例的方式会创建多个内存。
    • 但静态方法效率上要比实例化高,静态方法的缺点是不自动进行销毁,而实例化的则可以做销毁。


    应用场景:

    • 静态变量适合全局变量的定义。
    • 静态方法最适合工具类中方法的定义;比如文件操作,日期处理方法等。


    在类外调用静态属性/方法

    通过类名::属性/方法的方式调用。

    <?php
    class Mystatic {
      public static $staticvalue = 'zhangsan';
      public static function staticMethod() {
        $a = 'hello';
        return $a;
      }
    }
    echo '$staticvalue: '.Mystatic::$staticvalue.PHP_EOL;
    echo '$a: '.Mystatic::staticMethod().PHP_EOL;
    

    注:预定义常量 PHP_EOL 表示系统换行符。


    通过对象名::属性/方法的方式调用。

    <?php
    class Mystatic {
      public static $staticvalue = 'zhangsan';
      public static function staticMethod() {
        $a = 'hello';
        return $a;
      }
    }
    
    $obj = new Mystatic();
    echo '$staticvalue: '.$obj::$staticvalue.PHP_EOL;
    echo '$a: '.$obj::staticMethod();
    


    通过对象名->方法调用,会成功。而以对象名-> 属性,调用,会无效

    <?php
    class Mystatic {
      public static $staticvalue = 'zhangsan';
      public static function staticMethod() {
        $a = 'hello';
        return $a;
      }
    }
    
    $obj = new Mystatic();
    echo '$staticvalue: '.$obj -> staticvalue.PHP_EOL;
    echo '$a: '.$obj -> staticMethod();
    

    显示结果:

    $staticvalue:
    $a: hello
    


    在本类内调用本类的静态属性/方法

    • 在本类的方法中,访问本类的静态属性/方法,使用范围解析操作符self::属性/方法,不能通过对象操作符->)。self指向当前类,就像$this指向当前对象一样;而在没有实例化的情况下,$this指针指向的是空对象,所以不能动过它引用静态属性和方法。
    • 在本类的方法中,访问父类的静态属性/方法,使用范围解析操作符parent::属性/方法,不能通过对象操作符->)。
    • 从 PHP 8.0 开始,在静态方法中,通过$this调用一个非静态方法会抛出 Error。会导致一个 E_DEPRECATED 级别的警告。
    <?php
    class Mystatic {
      public static $staticvalue = 'zhangsan';
      public static function staticMethod() {
        $a = 'hello';
        return $a;
      }
      public function noStatic(){
        echo '$staticvalue: '.self::$staticvalue.PHP_EOL;
        echo '$a: '.self::staticMethod();
     
      }
      public static function staticMethod2(){
        echo '$staticvalue: '.self::$staticvalue.PHP_EOL;
        echo '$a: '.self::staticMethod1().PHP_EOL;
     
      }
    }
    $obj = new Mystatic();
    $obj -> noStatic();
    


    在本类中调用另一个类的静态属性/方法

    如果在一个类中调用其他类(非继承关系)的静态属性和方法,需要通过完整类名::进行调用。

    <?php
    class Mystatic1 {
      public static $staticvalue1 = 'xiaomin';
    }
    class Mystatic2 {
      public static $staticvalue2 = 'zhangsan';
      public static function staticMethod() {
        echo '$staticvalue1: '.Mystatic1::$staticvalue1.PHP_EOL;
        echo '$staticvalue2: '.self::$staticvalue2.PHP_EOL;
     
      }
    }
    Mystatic2::staticMethod();
    $obj = new Mystatic2();
    $obj -> staticMethod();
    


    静态属性支持动态修改

    在实际应用中会有一个类的多个对象,可能会共享一份数据。类常量和静态属性都可以实现。静态属性与类常量相似(相同),唯一的区分是类常量不可以更改,静态属性可以更改。访问方法是一样的,都可以使用::访问。静态属性需要加$,常量名前没有$

    注意:从 PHP 7.1.0 开始,类中的常量,支持访问限制修饰符设置。可以声明为protectedpublic的可见性,使其只在其定义的类的层次范围内可用。

    <?php
    class Myconst {
      const A = 1234;
    }
    
    $obj1 = new Myconst();
    echo 'A: '.$obj1::A.PHP_EOL;
    $obj1->A='aaa';
    
    //$obj1::A='aaa';  运行错误,导致停止
    
    echo "\r\n";
    $obj2 = new Myconst();
    echo 'A: '.$obj2::A.PHP_EOL;
    
    <?php
    class Mystatic {
      public static $A = 1234;
    }
    
    echo '$A: '.Mystatic::$A.PHP_EOL;
    
    Mystatic::$A = 6666;
    echo '$A: '.Mystatic::$A.PHP_EOL;
    
    $obj1 = new Mystatic();
    echo '$A: '.$obj1::$A.PHP_EOL;
    
    Mystatic::$A = 5555;
    $obj2 = new Mystatic();
    echo '$A: '.$obj2::$A.PHP_EOL;
    echo '$A: '.$obj1::$A.PHP_EOL;
    

    结果:

    $A: 1234
    $A: 6666
    $A: 6666
    $A: 5555
    $A: 5555
    


    静态成员的继承和重写

    和非静态属性/方法一样,静态属性和方法也可以被子类继承,静态属性和方法还可以被子类重写。

    静态属性

    子类可以重写父类的静态成员变量,但父类的静态变量依然存在,这两个静态成员变量是独立的。根据调用的类名分别进行访问。

    <?php
    class Mystatic
    {
        static public $a;           //定义一个静态变量
        static function test()      //定义静态方法来操作并输出静态变量
        {
            self::$a++;
            return self::$a;
        }
    }
    
    class Mystatic2 extends  Mystatic    //定义一个子类
    {
        static function test()           //定义子类的静态方法
        {
            self::$a++;           //访问并操作父类的静态变量
            return self::$a;
        }
    }
    
    $obj1=new Mystatic;                                   //新建父类对象
    echo '此时 $a 的值为: '.$obj1->test().PHP_EOL;         //通过对象调用静态方法 test,静态属性 $a 的值 +1
    
    $obj2=new Mystatic;                                   //新建另一个父类对象
    echo '此时 $a 的值为: '.$obj2->test().PHP_EOL;        //新父类对象调用静态方法 test,静态属性 $a 的值+1+1
    
    $obj3=new Mystatic2;                                  //新建子类对象
    echo '此时 $a 的值为: '.$obj3->test().PHP_EOL;         //子类对象调用同名静态方法 test, 静态属性 $a 的值+1+1+1
    
    echo Mystatic::$a.PHP_EOL;    //通过父类::直接访问静态成员$a变量
    echo $obj1::$a.PHP_EOL;       //通过对象名::可以直接访问静态成员$a变量
    

    结果:

    此时 $a 的值为: 1
    此时 $a 的值为: 2
    此时 $a 的值为: 3
    3
    3
    


    静态方法

    子类可以重写父类的静态方法。

    <?php
    class Mystatic1 {
        public static function getclassName() {
            return __CLASS__;
        }
     
        public static function whoclassName() {
            echo self::getclassName().PHP_EOL;
        }
    }
     
    class Mystatic2 extends Mystatic1{
        public static function getclassName() {
            return __CLASS__;
        }
    }
     
    echo Mystatic1::getclassName().PHP_EOL;
    echo Mystatic2::getclassName().PHP_EOL;
    

    通过__CLASS__可以获取当前类的类名,我们分别调用两个类的getClassName方法:

    结果:

    Mystatic1
    Mystatic2
    

    说明子类重写了父类的同名静态方法,同样我们在子类上也可以调用父类中的whoclassName方法:

    <?php
    class Mystatic1 {
        public static function getclassName() {
            return __CLASS__;
        }
     
        public static function whoclassName() {
            echo self::getclassName().PHP_EOL;
        }
    }
     
    class Mystatic2 extends Mystatic1{
        public static function getclassName() {
            return __CLASS__;
        }
    }
     
    echo Mystatic1::whoclassName();
    echo Mystatic2::whoclassName();
    

    结果:

    Mystatic1
    Mystatic1
    

    为什么第二个打印的结果是父类名 Mystatic1 而不是子类名 Mystatic2?这是因为,$this指针始终指向持有它的引用对象,而self指向的是定义时持有它的类而不是调用时的类,为了解决这个问题,从 PHP 5.3 开始,新增了一个叫做延迟静态绑定的特性。


    后期静态绑定

    PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。

    准确说,后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名。当进行静态方法调用时,该类名即为明确指定的那个(通常在::运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类。所谓的“转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::parent::static::以及forward_static_call()。可用get_called_class()函数来得到被调用的方法所在的类名,static::则指出了其范围。

    该功能从语言内部角度考虑被命名为“后期静态绑定”。“后期绑定”的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为“静态绑定”,因为它可以用于(但不限于)静态方法的调用。

    <?php
    class A {
        public static function who() {
            echo __CLASS__;
        }
        public static function test() {
            static::who();   // 后期静态绑定从这里开始
        }
    }
    
    class B extends A {
        public static function who() {
            echo __CLASS__;
        }
    }
    
    B::test();
    

    以上例程会输出:B