C++/Java中继承关系引发的调用关系详解

C++:

这里引用到了 http://blog.csdn.net/haoel/article/details/1948051/ 中的内容,还请提前阅读陈大神的这篇博客后在阅读本篇。

覆盖,实现多态的基础,通过虚函数表来实现,下面这个例子便是覆盖 Override 

 1 #include<iostream>
 2 
 3 using namespace std;
 4 
 5 class Base{
 6 public:
 7     Base(){
 8         cout << "Base::Base" << endl;
 9     }
10 
11     virtual void f(){
12         cout << "Base::f" << endl;
13     }
14 };
15 
16 class Derived : public Base{
17 public:
18     Derived(){
19         cout << "Devried::Derived" << endl;
20     }
21 
22     void f(){
23         cout << "Derived::f" << endl;
24     }
25 };
26 
27 int main(){
28     Base *p = new Derived(); // Base::Base CRCL Derived::Derived
29     p->f(); // Derived::f
30 }

如果 Base类f()没有被virtual修饰,如下

 1 #include<iostream>
 2 
 3 using namespace std;
 4 
 5 class Base{
 6 public:
 7     Base(){
 8         cout << "Base::Base" << endl;
 9     }
10 
11     void f(){
12         cout << "Base::f" << endl;
13     }
14 };
15 
16 class Derived : public Base{
17 public:
18     Derived(){
19         cout << "Devried::Derived" << endl;
20     }
21 
22     void f(){
23         cout << "Derived::f" << endl;
24     }
25 };
26 
27 int main(){
28     Base *p = new Derived();
29     p->f(); // Base::f
30 }

如果理解了陈大神博客上那些图,应该不会对这个结果产生意外,这是我画的一个草图,并不对应于真实的内存地址情况,只是为了方便说明概念,下面一排描述的是Derived:

解析:首先p是Base类型的指针,其对于p而言其能只能按照Base类型去运算偏移地址等来访问,所以其不可能访问到Derived中的函数。也就是说父类指针的作用域只有int a , Base::f Base::g这一部分。这样就能理解了。

隐藏,父类指针的访问域是父类部分,子类虽然拥有一份父类函数的拷贝,但如果子类中存在于父类同名的函数,则调用和变量类型一致的函数(即父类变量调用父类函数,子类变量调用子类函数,同时成对方的函数被隐藏)。例:

 1 #include<iostream>
 2 
 3 using namespace std;
 4 
 5 class Base{
 6 
 7 public:
 8     int a;
 9 
10     Base(){
11         cout << "Base::Base" << endl;
12     }
13 
14     void f(float a){
15         cout << "Base::f" << endl;
16     }
17 };
18 
19 class Derived : public Base{
20 public:
21     int d;
22     Derived(){
23         cout << "Devried::Derived" << endl;
24     }
25 
26     void f(int a){
27         cout << "Derived::f" << endl;
28     }
29 };
30 
31 int main(){
32     Derived *p = new Derived();
33     p->f(3.14f); // Derived::f
34 }

这里虽然传入f的参数是float型,更适合调用Base::f,但根据同名优先调用子类的原则,实际调用为Derived::f。这就是父类同名方法被隐藏,如果指针类型为父类,则称子类方法被隐藏。

如果子类f定义为 void f() 这里对f传值就会编译不通过。

Java

覆盖,java的情况很简单,只要是子类含有和父类的同名方法,就是覆盖(无论子类的函数是否为abstract)。

public class Test {
    public static void main(String[] args){
        Base base = new Derived();
        base.f(); // Derived::f
    }
}

class Base {

    public void f() {
        System.out.println("Base::f");
    }
}

class Derived extends Base {
    public void f() {
        System.out.println("Derived::f");
    }
}

隐藏,Java中也存在隐藏,不过这个隐藏和C++不太一样,子类对象引用可以调用父类的同名函数

 1 public class Test {
 2     public static void main(String[] args){
 3         Derived derived = new Derived();
 4         derived.f(3); //Derived::f
 5         derived.f(3.14f); // Base::f
 6     }
 7 }
 8 
 9 class Base {
10 
11     public void f(float a) {
12         System.out.println("Base::f");
13     }
14 }
15 
16 class Derived extends Base {
17     public void f(int a) {
18         System.out.println("Derived::f");
19     }
20 }

再讲覆盖,C++父类函数中如果调用了自己的虚成员函数,那么由于这个虚成员函数被覆盖了,所以其实相当于调用了子类的函数。例:

#include<iostream>

using namespace std;

class Base{

public:
    int a;

    Base(){
        cout << "Base::Base" << endl;
    }

    void f(){
        g();
    }

    virtual void g(){
        cout << "Base::g" << endl;
    }
};

class Derived : public Base{
public:
    int d;
    Derived(){
        cout << "Devried::Derived" << endl;
    }

    void g(){
        cout << "Derived::g" << endl;
    }
};

int main(){
    Derived *p = new Derived();
    p->f(); // Derived::g
}

Java中类似:

 1 public class Test {
 2     public static void main(String[] args){
 3         Base base = new Derived();
 4         base.f(); //Derived::f
 5     }
 6 }
 7 
 8 class Base {
 9 
10     public void f() {
11         g();
12     }
13     
14     public void g(){
15         System.out.println("Base::g");
16     }
17 }
18 
19 class Derived extends Base {
20     public void g(){
21         System.out.println("Derived::g");
22     }
23 }

由于想利用这种特性,有人会犯一个错误,在父类的构造函数中调用被覆盖的函数,由于子类在构建时先调用父类的构造函数,此时子类为构造所以不能调到子类的覆盖方法,由此

产生了奇怪的错误。

原文地址:https://www.cnblogs.com/chaiwentao/p/4792567.html