zoukankan      html  css  js  c++  java
  • PHP 中 parent、self、static、$this 的区别 & 后期静态绑定详解

    自 PHP 5.3.0 起,PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。 虽然也可以调用非静态方法,但是不会在运行时绑定。

    static 不再只是简单的静态修饰关键字。而是还可以调用类的静态方法,非静态方法,为什么静态非静态要分开说呢,因为调用的效果是不一样的。

    实例

     1 class  A  {
     2     
     3     protected $name = 'A';
     4     static $alias = 'a';
     5     const HASH = 'md5';
     6 
     7     public function dd() {
     8         echo 'name:' . $this->name . PHP_EOL;
     9         echo 'self-alias:' . self::$alias . PHP_EOL;
    10         echo 'static-alias:' . static::$alias . PHP_EOL;  // 后期静态绑定
    11         echo 'self-hash:' . self::HASH . PHP_EOL;
    12         echo 'static-hash:' . static::HASH . PHP_EOL;  // 后期静态绑定
    13 
    14         var_dump(new self);
    15         var_dump($this);
    16         var_dump(new static);
    17 
    18     }
    19 
    20 
    21     public static function  who () {
    22         echo  __CLASS__ ; 
    23         echo ' [ This is A ]';
    24         echo PHP_EOL;
    25     }
    26 
    27     public static function  test () {
    28         static:: who ();  // 后期静态绑定从这里开始
    29     }
    30 
    31     public static function  test2 () {
    32         self:: who ();
    33     }
    34 }
    35  
    36 class  B  extends  A  {
    37 
    38     protected $name = 'B';
    39     static $alias = 'b';
    40     const HASH = 'sha1';
    41 
    42     public static function  who () {
    43         echo  __CLASS__ ; 
    44         echo ' [ This is B ]';
    45         echo PHP_EOL;
    46     }
    47 }
    48 
    49 
    50 
    51 (new B)->dd()

    结果输出:

    name:B
    self-alias:a
    static-alias:b
    self-hash:md5
    static-hash:sha1
    
    object(admincontrollersA)
      protected 'name' => string 'A' (length=1)
    
    object(admincontrollersB)
      protected 'name' => string 'B' (length=1)
    
    object(admincontrollersB)
      protected 'name' => string 'B' (length=1)

    执行:

    B::who();
    B::test();
    B::test2();

    输出:

    B [ This is B ]
    B [ This is B ]
    A [ This is A ]

    总结说明:

    • self 和 __CLASS__,都是对当前类的静态引用,取决于定义当前方法所在的类。也就是说,self 写在哪个类里面, 它引用的就是谁。

    • $this 指向的是实际调用时的对象,也就是说,实际运行过程中,谁调用了类的属性或方法,$this 指向的就是哪个对象。但 $this 不能访问类的静态属性和常量,且 $this 不能存在于静态方法中。

    • static 关键字除了可以声明类的静态成员(属性和方法)外,还有一个非常重要的作用就是后期静态绑定。

    • parent,是对当前类的父类的静态引用。

    • self 可以用于访问类的静态属性、静态方法和常量,但 self 指向的是当前定义所在的类,这是 self 的限制。

    • $this 指向的对象所属的类和 static 指向的类相同。

    • static 可以用于静态或非静态方法中,也可以访问类的静态属性、静态方法、常量和非静态方法,但不能访问非静态属性。

    • 静态调用时,static 指向的是实际调用时的类;非静态调用时,static 指向的是实际调用时的对象所属的类。

    后期静态绑定

    后期静态绑定(也叫延迟静态绑定),可用于在继承范围内引用静态调用的类,也就是代码运行时最初调用的类。

    工作原理

    确切地说,static 后期静态绑定的工作原理是存储了上一个非转发调用(non-forwarding call)的类名。

    当进行静态方法调用时,该类名(static指向的类名)为明确指定的那个(通常是 :: 运算符的左侧部分),即实际调用时的类。

    如:上面例子中的

    A::test();  //A::test() 调用的是 static::who(),这里static指向的便是A,所以执行的就是A::who();
    B::test();  //A::test() 调用的是 static::who(),这里static指向的便是B,所以执行的就是B::who();
    static指向的类名,指向的就是实际调用的类

    对比

    static 和 self 的区别:

    • self 可以用于访问类的静态属性、静态方法和常量,但 self 指向的是当前定义所在的类,这是 self 的限制。

    • static 也可以用于访问类的静态属性、静态方法和常量,static 指向的是实际调用时的类。当进行非静态方法调用时,该类名(static指向的类名)为该对象所属的类,即实际调用时的对象所属的类。

    static 和 $this 有点类似,但又有区别:

    • $this 指向的对象所属的类和 static 指向的类相同。
    • $this 不能用于静态方法中,也不能访问类的静态属性和常量。
    • $this 指向的是实际调用的对象。
    • static 可以用于静态或非静态方法中,也可以访问类的静态属性、静态方法、常量和非静态方法,但不能访问非静态属性。
    • static 指向的是实际调用时的对象所属的类。

    转发调用(forwarding call)

    所谓的转发调用(forwarding call)指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call() 。

    可用 get_called_class() 函数来获取被调用的方法所在的类名。

    以下四种形式的调用,都是转发调用:

    self::
    parent::
    static::
    forward_static_call()

    除此之外的调用,就是非转发调用。

    非转发调用(non-forwarding call)

    后期静态绑定的工作原理是存储了上一个非转发调用(non-forwarding call)的类名。

    通过具体的类名或具体的对象进行的调用都是非转发调用。

    注意事项

    非静态环境下的私有方法的查找顺序

    在非静态环境下,在类的非静态方法中,使用 $this 和 static 调用类的私有方法时,执行方式有所不同。

    • $this 会优先寻找所在定义范围(父类)中的私有方法,如果存在就调用。
    • static 是先到它指向的类(子类)中寻找私有方法,如果找到了就会报错,因为私有方法只能在它所定义的类内部调用;如果没找到,再去所在定义范围(父类)中寻找该私有方法,如果存在就调用。

    具体来说,$this 会先到所在定义范围内寻找私有方法,再到它指向的对象所属的类中寻找私有方法,然后寻找公有方法,最后到所在定义范围内寻找公共方法。只要找到了匹配的方法,就调用,并停止查找。

    而 static 则是先到它指向的类中寻找私有方法,再寻找共有方法;然后到所在定义范围内寻找私有方法,再寻找共有方法。只要找到了匹配的方法,就调用,并停止查找。

    下面是一个例子:

     1 <?php
     2  class  A  {
     3     private function  foo () {
     4         var_dump($this); echo '--';
     5         var_dump(new static); echo '--';
     6  
     7         echo __CLASS__; echo '--';
     8         echo get_called_class();
     9         echo '<br>';
    10     }
    11  
    12     public function  test () {
    13         $this -> foo ();
    14         static:: foo ();
    15         echo '<br>';
    16     }
    17 }
    18  
    19 class  B  extends  A  { }
    20  
    21 class  C  extends  A  {
    22     private function foo () {
    23         echo 'this is C';
    24     }
    25 }
    26  
    27 (new  B())->test();
    28 (new  C())->test();
    29 输出结果为:
    30 
    31 object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
    32 object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
    33  
    34 object(C)#1 (0) { } --object(C)#2 (0) { } --A--C
    35  
    36 Fatal error: Uncaught Error: Call to private method C::foo() from context 'A'

    关于后期静态绑定的解析

    后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。如果静态调用使用了 parent:: 或者 self:: 等转发调用的形式,将会转发调用信息。

     1 <?php
     2 class  A  {
     3     public static function  foo () {
     4         static:: who ();
     5     }
     6  
     7     public static function  who () {
     8         echo  __CLASS__ . "
    " ;
     9     }
    10 }
    11  
    12 class  B  extends  A  {
    13     public static function  test () {
    14         A :: foo ();
    15         parent :: foo ();
    16         self :: foo ();
    17         static::foo();
    18         forward_static_call(['A', 'foo']);
    19         echo '<br>';
    20     }
    21  
    22     public static function  who () {
    23         echo  __CLASS__ . "
    " ;
    24     }
    25 }
    26  
    27 class  C  extends  B  {
    28     public static function  who () {
    29         echo  __CLASS__ . "
    " ;
    30     }
    31  
    32     public static function test2() {
    33         self::test();
    34     }
    35 }
    36  
    37 class  D  extends  C  {
    38     public static function  who () {
    39         echo  __CLASS__ . "
    " ;
    40     }
    41 }
    42  
    43 B::foo();
    44 B::test();
    45  
    46 C::foo();
    47 C::test();
    48  
    49 D::foo();
    50 D::test2();

    以上的输出结果为:

    B A B B B B 
    C A C C C C 
    D A D D D D

    static 后期静态绑定的工作原理是存储了上一个非转发调用(non-forwarding call)的类名。请记住这句话。

    下面的例子是非转发调用。

    A::foo();  // 输出 A
     
    B::foo();   // 输出 B
     
    C::foo();   // 输出 C

    后期静态绑定 static ,是定义在了 foo() 方法中,哪个类通过非转发调用的形式调用 foo() 方法, foo() 方法中的 static 指向的就是哪个类。

    但是,如果通过转发调用的形式,调用 foo() 方法,如:

    parent :: foo ();
    self :: foo ();
    static::foo();
    forward_static_call(['A', 'foo']);

    那么,就以转发调用代码所在的方法 test() 为准,哪个类通过非转发调用的形式调用 test() 方法, foo() 方法中的 static 指向的就是哪个类。

    假如调用 test() 方法时,也采用了转发调用的形式,如:

    public static function test2() {
        self::test();
    }

    那么,就以 test2() 方法为准 ... 依次类推。

    也就是说,在使用了后期静态绑定的基类中,后期静态绑定所在的方法如果被转发调用,则 static 的指向,会一直向上追溯,直到遇到非转发调用的形式。

    原贴地址:https://my.oschina.net/u/3683692/blog/3031106

  • 相关阅读:
    leetcode 1. Two Sum
    leetcode 168. Excel Sheet Column Title
    [LeetCode] Water and Jug Problem 水罐问题
    leetcode 80 Remove Duplicates from Sorted Array II
    leetcode 239. Sliding Window Maximum
    文件处理
    python网络编程 之 twisted
    ICMP & ping & traceroute
    Java String 转整形
    Java 字符数字得到整数
  • 原文地址:https://www.cnblogs.com/usays/p/10654432.html
Copyright © 2011-2022 走看看