php继承相关的一个问题

Xavier
  • 407
class A {
    private function foo() {
        echo "a";
    }
    public function test() {
        $this->foo();
    }
}

class B extends A {
    public function foo() {
        echo 'b';
    }
}

$b = new B();
$b->test();

结果是打印了a,而不是b。

我先阐明我的逻辑,答主们回答前务必要明白我的意思啊:

  1. A类的private function foo根据官方文档的解释是私有方法不会被继承到子类。那么,如果是这样的话,也就是说B类只继承了A类的test方法,B类现在有两个方法,自己的foo方法和从A继承过来的test方法。
  2. 但是,网上还有一种说法是,无论什么访问属性,都会被子类继承,只是private的方法或者属性子类并访问不到。那么,如果是这样的话,B类继承了A类的两个方法,并重写了从A类继承过来的foo方法。
  3. 无论以上两种可能哪一种是正确的,也就是说,无论B是有自己的foo方法还是B重写了foo方法,B的foo的函数体都是echo 'b';

那么问题来了,为什么执行b的test方法,明明是执行的$this->foo();,为什么不是B的foo,而是A的foo呢?


已经采纳回答,采纳 @代码宇宙 的原因请看答案评论区域,同时,推荐这片文章:https://www.2cto.com/kf/20100...


3.16 更新
昨天陷入了思维的怪圈,今天再来总结一下:

  • 当子类继承父类之后,this的指向是动态绑定的,也就是说,当父类的方法被重写之后,调用的就是子类的方法,没有重写,就调用父类的方法。就是这么简单。
  • 在这个问题中,父类的foo方法是私有的,并没有继承给子类,也就没有重写之说,所以,根据前面的定论,没有重写就调用父类中的方法。所以就打印了'a'。
  • 另外还有三种绑定方式,分别是 self parent static

    • self静态绑定,它的指向始终是在编译时所在的那个类。
    • parnet也是静态绑定,它始终指向编译时所在类的父类,而这个父类是声明类时就指定了的,所以指向是明确的。
    • static属于后期静态绑定,或者说是动态绑定或运行时绑定,它的指向与调用者有关。若调用者是所在类的实例,那么它就指向本类;若调用者是所在类子类的实例,它则指向子类。

2019.10.26 更新

如果想要通过 $b->test() 调用 Bfoo 方法,可以将 A 中的 test 方法体改为 static::foo() 。因为存在如下定理:

当父类中的方法被子类调用时,方法中的 $this-> 会首先在其所在范围(代码上下文)里寻找私有方法或属性,如果寻找不到才会调用其子类的方法或属性。而 static 属于后期静态绑定,它直接指向方法的调用者。

请仔细阅读官方文档:https://www.php.net/manual/en/language.oop5.late-static-bindings.php

回复
阅读 2.4k
4 个回答

B没有从A继承私有方法!

继承是什么意思?我继承了,我就有了所有权,我可以使用并修改,这才是继承。使用就是调用,修改就是重写,缺任何一个都不叫继承。

那A的私有方法与B有什么关系呢?就是B只有使用权,并且只能通过A去使用它,只有这么一点关系。B既无法直接调用,也无法修改。

所以,官网是正确的,严谨的。网上的说法则是不严谨的。

原因是这样的,你B继承了Atest这个方法你继承下来了,但是test本身是在A类里面的,也就是说,你继承了test,但是test依旧是属于A的,那么此时test里的$this指的就是A这个对象。

如果你想要在B里调用B自己的foo的话,你可以这么做:

class B extends A {
    public function foo() {
        echo 'b';
    }
    public function test() {
        parent::test(); // 调用父类的test方法,父类里的test方法调用父类自身的foo方法
        $this->foo(); // 掉用自己的foo方法
    }
}

整体来说的话,就是不要把继承当成属于.

另外就是官方说的是正确的,你这里并不是继承问题,而是应该弄清楚属于与不属于问题。

根据你的回复,就这个我继续做一下回答:


看一下你本身的这个例子

class A {
    private function foo() {
        echo 'a';
    }
    public function test() {
        $this->foo();
    }
}
class B extends A {
    public function foo() {
        echo 'b';
    }
}

在这种情况下,如果你把A中的foo方法改成了public,那么此时foo就被继承了,而在B类里的foo方法相当于是重写了A类的foo方法,但是此时A类中的foo并没有消失,但是需要在B类中以parent::foo()这种形式来访问父类方法。这种情况方便理解,可说A中的foo方法被隐藏了。

既然被重写了,那么自然就会调用foo就是调用重写后的foo了,可以理解为既然有新的东西,那么就肯定用新东西了,新的自然优先嘛。

然后回头看为什么A中原本为private,虽然B继承了一个公共方法还是访问A类中的foo呢?首先我们看一下前提概要,也就是A类中的foo是private的,B类不可继承,那么此时就相当于有两个foo,一个是private的,一个是public的,嗯,这里要区分private与"隐藏"是不同的概念。

那么此时其实就相当于是,虽然B继承了A,但是A会首先到自己里面找,那么此时foo没有被重写,也就是没有被覆盖,那么肯定是调用自己的了。对于A来说,B你继承了我,但是我自己有,我就不要你的东西了,还是自己老婆好啊。

嗯。。。我试着用几句更简单的话来描述看看是否可行:

A: 我是A?
B: 我是B,我继承你了啊,我继承了你的test,我有foo
A:嗯,我也有foo,我不给你用
B:我被调用了啊,用的是继承了你的test
A:嗯,我看看,哦,调用了foo啊,我有
B:能用我的foo吗?
A:不能
B:好吧。。。?

再来看把A中的private变为public后:

A:我是A
B:我是B,我继承了你的test,也继承了你的foo,然而我把你的foo重写了
A:我艹你大爷?,那你用parent::foo()调用我给你的foo了么?
B:。。。没有,我现在是大爷?
A:好吧。。。
B:我被调用了啊,用的是继承了你的test
A:好吧...卧槽,foo被你重写了啊,那用你的foo
B:那是当然啊,要是还要用你的,我会用parent::foo()调用的
A:行吧,艹你大爷?

因为class A的私有方法不能被覆盖,所以,在class B中再次定义时,它的作用域是不同的,而 A.test只能看到A.foo.当你把 A.foo的属性改为 protectedpublic,你就能看到0

  1. A类中的foo()是private,不能被继承,所以不存在重写;
  2. test()继承于A类,由于foo()是不能继承的,所以B中的foo()可以认为是一个全新的方法;
  3. 当A中的foo()从private变为可继承的时候,B中的foo()就属于foo()的重写了;
  4. 这样想调用A中的foo()的话只能用parent::foo();
结论: test()是A的,$this也是A的,调用自己私有的foo()很正常嘛。
延伸:为什么A中的foo()改为public结果不一样了呢?
因为B是继承A的,B把foo()继承又重写了,所以A中的foo()不能再用$this访问了,只能用parent::foo()

不能继承是关键。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏