精华内容
下载资源
问答
  • java的编译时多态和运行时多态,保证一看就会
  • 根据何时确定执行多态方法中的哪一个,多态分为两种情况:编译时多态和运行时多态。如果在编译时能够确定执行多态方法 中的哪一个,称为编译时多态,否则称为运行时多态。 一、编译时多态 方法重载都是编译时多态。...

    根据何时确定执行多态方法中的哪一个,多态分为两种情况:编译时多态运行时多态。如果在编译时能够确定执行多态方法

    中的哪一个,称为编译时多态,否则称为运行时多态。

    一、编译时多态
    方法重载都是编译时多态。根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。

        **方法覆盖**表现出两种多态性,**当对象引用本类实例时,为编译时多态**,否则为运行时多态。例如,以下声明p、m引用本类实例,调用toString()方法是编译时多态。
    
    public class Test {  
      
        public static void main(String[] args) {  
            Person p = new Person();         //对象引用本类实例  
            Man m = new Man();              
            System.out.println(p.toString());   //编译时多态,执行Person类的toString()  
            System.out.println(m.toString()); //编译时多态,执行Man类的toString()  
        }  
    }  
      
    class Person{  
        public String toString() {  
            String name = "Person";  
            return name;  
        }  
    }  
      
    class Man extends Person{  
        public String toString(){  
            String name = "Man";  
            return name;  
        }  
    }  
    
    
    

    二、运行时多态
    1.当以下父类对象p引用子类实例时,p.toString)执行谁的setName()方法?

    
    Person p = new Man();     
    p.toString();  
    
    
       Java支持运行时多态,意为p.toString()实际执行p所引用实例的toString(),究竟执行Person类还是Man类的方法,运行时再确定。如果Man类声明了toString()方法,则执行之;否则执行Person类的toString()方法。
        程序运行时,Java从实例所属的类(new 类)开始寻找匹配的方法执行,如果当前类中没有匹配的方法,则沿着继承关系逐层向上,依次在父类或各祖先类中寻找匹配方法,直到Object类。寻找p.toString()匹配执行方法的过程如下图所示。
    

    在这里插入图片描述

        因此,父类对象只能执行那些在父类中声明、被子类覆盖了的子类方法,如toString(),不能执行子类增加的成员方法。
    

    2.将上述例子中toString方法改为getName,因为在Object类中有toString类,无法测试Person与Man中所匹配的执行方法。

    public class Test {   //例子2  
        public static void main(String[] args) {  
            Person p = new Man();  
            System.out.println(((Man) p).getName());   //返回结果为Man  
        }  
    }  
      
    class Person{}  
      
    class Man extends Person{  
        public String getName(){  
            String name = "Man";  
            return name;  
        }  
    }  
    

    此例中Person类型要引用Man类的实例,因Person中未定义setName()方法,故需要把Person类显式地转换为Man类,然后调用Man中的getName方法。
    3.将例子1中Person和Man的方法名改为静态的getName()方法,会返回什么结果呢?

    public class Test {   //例子3  
      
        public static void main(String[] args) {  
            Person p = new Man();  
            System.out.println(p.type);        //返回结果为P  
            System.out.println(p.getName());   //返回结果为Person  
      
        }  
      
    }  
      
    class Person{  
      
        String type = "P";  
        public static String getName() {  
            String name = "Person";  
            return name;  
        }  
    }  
      
    class Man extends Person{  
          
        String type = "M";  
        public static String getName(){  
            String name = "Man";  
            return name;  
        }  
    }  
    
        栗子中子类Man隐藏父类Person的属性,而 Person p = new Man() 表示“**先声明一个Person类的对象p,然后用Man类对p进行实例化**”,即**引用类型为Person类,实际代表的是Man类**。因此,访问的是Person的属性及静态方法,详细解释如下。
    
        所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。
       子类对于父类静态方法的隐藏(hide),子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。 
    
        父类中属性只能被隐藏,而不能被覆盖;而对于方法来说,方法隐藏只有一种形式,就是父类和子类存在相同的静态方法。
    

    运行时多态demo:

      public class Test {
     
        public static void main(String[] args) {
            //运行时多态
            Father c = new SonClass();
            c.method();   //父类的构造方法 子类的构造方法 子类的method()
            c.method2();  //父类的构造方法 子类的构造方法 父类的method2()
        }
    }
     
    class Father {
     
     
        public Father() {
            System.out.print("父类的构造方法\t");
        }
     
        public void method() {
            System.out.print("父类的method()\t");
        }
        public void method2() {
            System.out.print("父类的method2()\t");
        }
     
    }
     
    class SonClass extends Father {
     
        public SonClass() {
            System.out.print("子类的构造方法\t");
        }
     
        @Override
        public void method() {
            System.out.print("子类的method()\t");
        }
     
    }
    

    运行时多态性是面向对象程序设计代码重用的一个最强大机制,动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态方法调度,它是一种在运行时而不是在编译期调用重载方法的机制,下面就继承和接口实现两方面谈谈java运行时多态性的实现。
    一、通过继承中超类对象引用变量引用子类对象来实现

    举例说明:

    //定义超类superA 
    class superA 
    
    { 
    int i = 100; 
    void fun() 
    { 
    System.out.println(“This is superA”); 
    } 
    
    } 
    
    //定义superA的子类subB 
    class subB extends superA 
    { 
    int m = 1; 
    void fun() 
    { 
    System.out.println(“This is subB”); 
    } 
    
    } 
    
    //定义superA的子类subC 
    class subC extends superA 
    { 
    int n = 1; 
    void fun() 
    { 
    System.out.println(“This is subC”); 
    } 
    
    } 
    
    
    class Test 
    
    { 
    public static void main(String[] args) 
    { 
    superA a; 
    subB b = new subB(); 
    subC c = new subC(); 
    a=b; 
    a.fun(); (1) 
    a=c; 
    a.fun(); (2) 
    } 
    } 
    
    

    运行结果为:

    This is subB 
    This is subC 
    
    

    上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b, c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:“为什么(1)和(2)不输出:This is superA”。java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

    所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于(1)中的a被b赋值,指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),它覆盖了超类superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。
    另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。
    不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,否则
    子类必须被abstract修饰符修饰
    ,当然也就不能被实例化了。

    二、通过接口类型变量引用实现接口的类的对象来实现
    接口的灵活性就在于“规定一个类必须做什么,而不管你如何做”。我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似。

    举例说明:

    
    //定义接口InterA 
    
    interface InterA 
    { 
    void fun(); 
    } 
    
    //实现接口InterA的类B 
    class B implements InterA 
    { 
    public void fun() 
    { 
    System.out.println(“This is B”); 
    } 
    
    } 
    
     
    
    //实现接口InterA的类C 
    class C implements InterA 
    
    { 
    public void fun() 
    { 
    System.out.println(“This is C”); 
    } 
    
    } 
    
    
    class Test 
    { 
    public static void main(String[] args) 
    { 
    InterA a; 
    a= new B(); 
    a.fun(); 
    a = new C(); 
    a.fun(); 
    } 
    } 
    
    

    输出结果为:

    This is B 
    This is C 
    

    上例中类B和类C是实现接口InterA的两个类,分别实现了接口的方法fun(),通过将类B和类C的实例赋给接口引用a而实现了方法在运行时的动态绑定,充分利用了“**一个接口,多个方法”**展示了Java的动态多态性。
    需要注意的一点是:Java在利用接口变量调用其实现类的对象的方法时,该方法必须已经在接口中被声明,而且在接口的实现类中该实现方法的类型和参数必须与接口中所定义的精确匹配。
    结束语:以上就是java运行时多态性的实现方法,大家在编程过程中可以灵活运用,但是在性能要求较高的代码中不提倡运用运行时多态,毕竟Java的运行时动态方法调用较之普通的方法调用的系统开销是比较大的。

    展开全文
  • 有,那就是编译时多态 正文 动态多态 运行多态存在于继承类中,通过虚函数实现动态选择调用。简单的说就是通过一张虚函数表——vptable,vptable中记录了基类的虚函数地址,在派生类中继承了基类的这张vptable...

    前言
        多态多为C++的三大特性之一,我们对此的了解和应用大多停留在类继承层次和运行时阶段。那既然有运行(动态)时阶段,那是否也有静态的多态?有,那就是编译时多态

    正文
      动态多态

        运行时的多态存在于继承类中,通过虚函数实现动态选择调用。简单的说就是通过一张虚函数表——vptable,vptable中记录了基类的虚函数地址,在派生类中继承了基类的这张vptable,而且在派生类的构造函数中对继承来的vptable相关地址进行替换,因此在调用时通过vptable的实际地址能够知道调用哪个函数。下面是个简单的范例:

    #pragma once
     
    #include <iostream>
    class CBase
    {
    public:
    	CBase(){std::cout<<"CBase::CBase"<<std::endl;}
    	~CBase(){std::cout<<"CBase::~CBase"<<std::endl;}
     
    	virtual int getMax(int a, int b)
    	{
    		std::cout<<"CBase::getMax"<<std::endl;
    		return a>b?a:b;
    	}
    };
     
    class CChild : public CBase
    {
    public:
    	CChild(){std::cout<<"CChild::CChild"<<std::endl;}
    	~CChild(){std::cout<<"CCHild::~Child"<<std::endl;}
     
    	virtual int getMax(int a, int b)
    	{
    		std::cout<<"CChild::getMax"<<std::endl;
    		return a>b?a:b;
    	}
    };
     
    void main()
    {
    	CBase *pBase = new CBase;	
    	CChild *pChld = new CChild;
    	pBase->getMax(5, 2);
    	pBase = new CChild;
    	pBase->getMax(5,2);
    	getchar();
    	return;
    }

    输出如下:

     

    总之动态多态发生在继承关系的类中,通过虚函数表实现。那静态多态呢?

      静态多态

        静态多态是发生在编译时期的,通过模板和函数重载实现,相比动态多态不需派生关系。下面看一个范例

    class Animal
    {
    public :
        void shout() { cout << "发出动物的叫声" << endl; };
    };
    class Dog
    {
    public:
         void shout(){ cout << "汪汪!"<<endl; }
    };
    class Cat
    {
    public:
         void shout(){ cout << "喵喵~"<<endl; }
    };
    class Bird
    {
    public:
         void shout(){ cout << "叽喳!"<<endl; }
    };
    template <typename T>
    void  animalShout(T & t)
    {
        t.shout();
    }
    int main()
    {
        Animal anim;
        Dog dog;
        Cat cat;
        Bird bird;
     
        animalShout(anim);
        animalShout(dog);
        animalShout(cat);
        animalShout(bird);
     
        getchar();
    }

    上例就是通过模板实现静态多态,重载范例就不展示了。那么这两者区别在于什么?

     

    编译时多态和运行时多态区别:

    1、运行时体现了面向对象的思想,但是虚函数表增加了内存开销

    2、运行时多态发生在运行时期,所以编译器无法进行优化处理

    3、编译时多态运用泛型编程,在编译器完成提升运行期效率,但是无法实现模板的分离编译所以对于大工程而言编译耗时

    4、编译时多态无法处理异质对象集合(什么是异质对象?)

     

    延申拓展:显示接口和隐士接口

        显示接口就是指能够明确来源的接口,比如在动态多态的范例中,当调用getMax时,我们能够知道这个getMax到底是CBase的还是CChild的

        隐式接口就是指那些无法确定的来源的接口,如对于函数重载和模板,我们也不知道调用的是哪个实现。

     

    解答:

        异质类:就是(存储、指向)类型不一致的数据对象,如pBase,或Template<type T>,所以异质类的实现核心就是多态+模板编程
     

    展开全文
  • 编译时多态和运行时多态 多态分为两种情况:编译时多态和运行时多态。如果在编译时能够确定执行多态方法称为编译时多态,否则称为运行时多态。 一、编译时多态 方法重载都是编译时多态。根据实际参数的数据类型、个...

    多态分为两种情况:编译时多态和运行时多态。
    如果在编译时能够确定执行多态方法称为编译时多态,否则称为运行时多态。

    一、编译时多态

    方法重载都是编译时多态。根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法。
    方法覆盖表现出两种多态性,当对象引用本类实例时,为编译时多态,否则为运行时多态。
    例如,以下声明p、m引用本类实例,调用toString()方法是编译时多态。

            
    public class Test {
     
    		public static void main(String[] args) {
    		Person p = new Person();         //对象引用本类实例
    		Man m = new Man();               //编译时多态,执行Person类的toString()
    		System.out.println(p.toString());
    		System.out.println(m.toString()); //编译时多态,执行Man类的toString()
    	}
    }
     
    
    
    class Person{
    	public String toString() {
    		String name = "Person";
    		return name;
    	}
    }
     
    class Man extends Person{
    	public String toString(){
    		String name = "Man";
    		return name;
    	}
    }
    
    

    二、运行时多态

    1.当以下父类对象p引用子类实例时,p.toString执行谁的setName()方法

    Person p = new Man();   
    p.toString();
    

    Java支持运行时多态,意为p.toString()实际执行p所引用实例的toString(),究竟执行Person类还是Man类的方法,运行时再确定。如果Man类声明了toString()方法,则执行之;否则执行Person类的toString()方法。
    程序运行时,Java从实例所属的类开始寻找匹配的方法执行,如果当前类中没有匹配的方法,则沿着继承关系逐层向上,依次在父类或各祖先类中寻找匹配方法,直到Object类。寻找p.toString()匹配执行方法的过程如下图所示。
    在这里插入图片描述
    2.将上述例子中toString方法改为getName,因为在Object类中有toString类,无法测试Person与Man中所匹配的执行方法。

    public class Test {   //例子2
    	public static void main(String[] args) {
    		Person p = new Man();
    		System.out.println(((Man) p).getName());   //返回结果为Man
    	}
    }
     
    class Person{}
     
    class Man extends Person{
    	public String getName(){
    		String name = "Man";
    		return name;
    	}
    }
    

    此例中Person类型要引用Man类的实例,因Person中未定义setName()方法,故需要把Person类显式地转换为Man类,然后调用Man中的getName方法。
    3.将例子1中Person和Man的方法名改为静态的getName()方法,会返回什么结果呢?

    public class Test {   //例子2
    	public static void main(String[] args) {
    		Person p = new Man();
    		System.out.println(((Man) p).getName());   //返回结果为Man
    	}
    }
     
    class Person{}
     
    class Man extends Person{
    	public String getName(){
    		String name = "Man";
    		return name;
    	}
    }
    

    例子中子类Man隐藏父类Person的属性,而 Person p = new Man() 表示“先声明一个Person类的对象p,然后用Man类对 p进行实例化”,即引用类型为Person类,实际代表的是Man类。因此,访问的是Person的属性及静态方法,详细解释如下。

    所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。

    子类对于父类静态方法的隐藏(hide),子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。

    父类中属性只能被隐藏,而不能被覆盖;而对于方法来说,方法隐藏只有一种形式,就是父类和子类存在相同的静态方法。

    展开全文
  • (1)编译时多态/静态联编 指联编工作在编译阶段完成,即在编译阶段确定了程序中的操作调用与执行该操作的代码间的关系,基于指向对象的指针类型或引用类型。 (2)运行时多态/动态联编 指联编在程序运行动态进行,对...

    多态的定义:

    同一操作作用与不同的对象,可以有不同的解释,产生不同的执行结果。

    (1)编译时多态/静态联编
    指联编工作在编译阶段完成,即在编译阶段确定了程序中的操作调用与执行该操作的代码间的关系,基于指向对象的指针类型或引用类型。
    (今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分。在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态。)

    (2)运行时多态/动态联编
    指联编在程序运行时动态进行,对函数的调用基于对象的类型。

    区别总结

    在这里插入图片描述

    代码

    (1)编译时多态
    1)函数重载

    //编译时多态,函数调用与指针类型有关。
    #include
    using namespace std;
    class A{
    public:
    void print(){cout<<“A”<<endl;}//函数重载
    };
    class B:public A{
    public:
    void print(){cout<<“B”<<endl;}//函数重载
    };

    int main(){
    A *p;//指针p为A类型
    A a;
    B b;
    p=&a;
    p->print() ;
    p=&b;
    p->print();
    return 0;
    }
    //输出结果:
    A
    A

    2)模板具现化实例

    #include
    #include
    using namespace std;
    class animal{
    public:
    void voice(){
    cout<<“动物叫声”<<endl;
    }
    };
    class Cat:public animal{
    public:
    void voice(){
    cout<<“喵喵~”<<endl;
    }
    };
    class Dog:public animal{
    public:
    void voice(){
    cout<<“汪汪!”<<endl;
    }
    };
    class Bunny:public animal{
    public:
    void voice(){
    cout<<“唧唧”<<endl;
    }
    };
    template //类模板
    void pVoice(T&tmp){
    tmp.voice();
    }

    int main(){
    animal anim;
    Cat cat;
    Dog dog;
    Bunny bunny;
    pVoice(anim);//编译器推测模板参数,以调用不同函数
    pVoice(cat);
    pVoice(dog);
    pVoice(bunny);
    return 0;
    }
    //运行结果:
    动物叫声
    喵喵~
    汪汪!
    唧唧

    (2)运行时多态
    1)使用基类指针指向派生类

    //运行时多态,函数调用与指针所指对象类型有关
    #include
    using namespace std;
    class A{
    public:
    virtual void print(){cout<<“A”<<endl;}//虚函数
    };
    class B:public A{
    public:
    virtual void print(){cout<<“B”<<endl;}//虚函数
    };

    int main(){
    A *p;
    A a;
    B b;
    p=&a;//指针p指向a , 基类 指针指向基类
    p->print() ;
    p=&b;//指针p指向b ,基类指针指向子类 ,调用子类虚函数
    p->print();
    return 0;
    }
    //运行结果:
    A
    B

    使用基类引用

    #include
    using namespace std;
    class animal{
    public:
    virtual void voice(){
    cout<<“动物叫声”<<endl;
    }
    };
    class Cat:public animal{
    public:
    virtual void voice(){
    cout<<“喵喵~”<<endl;
    }
    };
    class Dog:public animal{
    public:
    virtual void voice(){
    cout<<“汪汪!”<<endl;
    }
    };
    class Bunny:public animal{
    public:
    virtual void voice(){
    cout<<“唧唧”<<endl;
    }
    };

    void pVoice(animal&tmp){//对基类的引用
    tmp.voice();
    }

    int main(){
    Cat cat;
    Dog dog;
    Bunny bunny;
    pVoice(cat);
    pVoice(dog);
    pVoice(bunny);

    return 0;
    

    }
    //运行结果如下:
    喵喵~
    汪汪!
    唧唧

    异质集合处理

    #include
    #include
    using namespace std;
    class animal{
    public:
    virtual void voice(){
    cout<<“动物叫声”<<endl;
    }
    };
    class Cat:public animal{
    public:
    virtual void voice(){
    cout<<“喵喵~”<<endl;
    }
    };
    class Dog:public animal{
    public:
    virtual void voice(){
    cout<<“汪汪!”<<endl;
    }
    };
    class Bunny:public animal{
    public:
    virtual void voice(){
    cout<<“唧唧”<<endl;
    }
    };

    int main(){
    vector<animal*>anims;//使用容器
    Dog d;Cat c;Bunny b;
    animaldog=&d;
    animal
    cat=&c;
    animal*bunny=&b;
    //处理异质类集合

    anims.push_back(cat);
    anims.push_back(dog);
    anims.push_back(bunny);
    for(int i;i<anims.size();++i)
        anims[i]->voice();
    return 0;
    

    }
    //运行结果如下:
    喵喵~
    汪汪!
    唧唧

    附加:
    运行时多态要求派生类与基类对应的虚函数同时具有相同的:
    1函数名称
    2函数参数类型、个数和顺序
    3返回值
    如果不满足以上条件,派生类的虚函数将丢失其虚特性,在调用时采用编译时多态。

    范例代码如下:

    #include
    using namespace std;

    class base{
    public:
    virtual void fun1(){cout<<“baseFun1”<<endl;}
    virtual void fun2(){cout<<“baseFun2”<<endl;}
    void fun3(){cout<<“basefun3”<<endl;}
    void fun4(){cout<<“baseFun4”<<endl;}
    virtual void fun5(){cout<<“baseFun5”<<endl;}
    };

    class derived:public base{
    public:
    virtual void fun1(){cout<<“derivedFun1”<<endl;}//运行时多态。基类派生类同定义为虚函数
    virtual void fun2(int x){cout<<“derivedFun2”<<endl;}//编译时多态。虽然基类、派生类均定义为虚函数,但是两者具有不同的参数个数 ,
    //因而派生类虚函数丢失其虚特性,采用编译时多态
    virtual fun3(){cout<<“derivedFun3”<<endl;}//编译时多态。基类为一般成员函数,派生类定义为虚函数,但以基类说明的特性为标准。
    void fun4(){cout<<“derivedFun4”<<endl;}//编译时多态。基类、派生类均为一般成员函数
    void fun5(){cout<<“derivedFun5”<<endl;}//运行时多态。基类为虚函数,派生类为一般成员函数,仍然以基类说明为准
    };

    int main(){
    base *p;
    base a;derived b;
    p=&b;
    p->fun1();
    p->fun2();
    p->fun3();
    p->fun4();
    p->fun5();
    return 0;
    }
    //输出结果如下:
    derivedFun1
    baseFun2
    baseFun3
    baseFun4
    derivedFun5
    ————————————————
    版权声明:上文为CSDN博主「簇僵僵」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/pilipilipan/article/details/79675653

    下述为另一篇,主要介绍编译期多态及运行期多态的优缺点

    C++编译期多态与运行期多态
    阅读目录

    前言
    运行期多态
    编译期多态
    运行期多态与编译期多态优缺点分析
    运行期多态优点
    运行期多态缺点
    编译期多态缺点
    关于显式接口与隐式接口

    前言
    今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分。在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态。在本文中,我们将了解:

    什么是运行期多态
    什么是编译期多态
    它们的优缺点在哪

    运行期多态
    运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。典型地我们会举下面这个例子:

    class Animal
    {
    public :
    virtual void shout() = 0;
    };
    class Dog :public Animal
    {
    public:
    virtual void shout(){ cout << “汪汪!”<<endl; }
    };
    class Cat :public Animal
    {
    public:
    virtual void shout(){ cout << “喵喵~”<<endl; }
    };
    class Bird : public Animal
    {
    public:
    virtual void shout(){ cout << “叽喳!”<<endl; }
    };

    int main()
    {
    Animal * anim1 = new Dog;
    Animal * anim2 = new Cat;
    Animal * anim3 = new Bird;

    //藉由指针(或引用)调用的接口,在运行期确定指针(或引用)所指对象的真正类型,调用该类型对应的接口
    anim1->shout();
    anim2->shout();
    anim3->shout();

    //delete 对象
    ...
    

    return 0;
    }
    运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。

    运行期多态的优势还在于它使处理异质对象集合称为可能:

    //我们有个动物园,里面有一堆动物
    int main()
    {
    vector<Animal*>anims;

    Animal * anim1 = new Dog;
    Animal * anim2 = new Cat;
    Animal * anim3 = new Bird;
    Animal * anim4 = new Dog;
    Animal * anim5 = new Cat;
    Animal * anim6 = new Bird;
    
    //处理异质类集合
    anims.push_back(anim1);
    anims.push_back(anim2);
    anims.push_back(anim3);
    anims.push_back(anim4);
    anims.push_back(anim5);
    anims.push_back(anim6);
    
    for (auto & i : anims)
    {
        i->shout();
    }
    //delete对象
    //...
    return 0;
    

    }
    总结:运行期多态通过虚函数发生于运行期

    编译期多态

    对模板参数而言,多态是通过模板具现化和函数重载解析实现的。以不同的模板参数具现化导致调用不同的函数,这就是所谓的编译期多态。
    相比较于运行期多态,实现编译期多态的类之间并不需要成为一个继承体系,它们之间可以没有什么关系,但约束是它们都有相同的隐式接口。我们将上面的例子改写为:

    class Animal
    {
    public :
    void shout() { cout << “发出动物的叫声” << endl; };
    };
    class Dog
    {
    public:
    void shout(){ cout << “汪汪!”<<endl; }
    };
    class Cat
    {
    public:
    void shout(){ cout << “喵喵~”<<endl; }
    };
    class Bird
    {
    public:
    void shout(){ cout << “叽喳!”<<endl; }
    };
    template
    void animalShout(T & t)
    {
    t.shout();
    }
    int main()
    {
    Animal anim;
    Dog dog;
    Cat cat;
    Bird bird;

    animalShout(anim);
    animalShout(dog);
    animalShout(cat);
    animalShout(bird);
    
    getchar();
    

    }
    在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。

    运行期多态与编译期多态优缺点分析
    运行期多态优点
    OO设计中重要的特性,对客观世界直觉认识。
    能够处理同一个继承体系下的异质类集合。
    运行期多态缺点
    运行期间进行虚函数绑定,提高了程序运行开销。
    庞大的类继承层次,对接口的修改易影响类继承层次。
    由于虚函数在运行期在确定,所以编译器无法对虚函数进行优化。
    虚表指针增大了对象体积,类也多了一张虚函数表,当然,这是理所应当值得付出的资源消耗,列为缺点有点勉强。

    >>编译期多态优点

    它带来了泛型编程的概念,使得C++拥有泛型编程与STL这样的强大武器。
    在编译器完成多态,提高运行期效率。
    具有很强的适配性与松耦合性,对于特殊类型可由模板偏特化、全特化来处理。

    编译期多态缺点
    程序可读性降低,代码调试带来困难。
    无法实现模板的分离编译,当工程很大时,编译时间不可小觑。
    无法处理异质对象集合。

    附:关于显式接口与隐式接口

    所谓的显式接口是指类继承层次中定义的接口或是某个具体类提供的接口,总而言之,我们能够在源代码中找到这个接口.显式接口以函数签名为中心,例如

    void AnimalShot(Animal & anim)
    {
    anim.shout();
    }
    我们称shout为一个显式接口。在运行期多态中的接口皆为显式接口。

    而对模板参数而言,接口是隐式的,奠基于有效表达式。例如:

    template
    void AnimalShot(T & anim)
    {
    anim.shout();
    }
    对于anim来说,必须支持哪一种接口,要由模板参数执行于anim身上的操作来决定,在上面这个例子中,T必须支持shout()操作,那么shout就是T的一个隐式接口。

    展开全文
  • 运行时多态编译时多态

    千次阅读 2017-11-28 16:21:47
    前言 今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一...在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态编译期进行,因此称为编译多态或静态多态。在本文中,我们将了解:
  • 编译时多态与运行时多态的区别

    千次阅读 2018-03-24 10:48:32
    (1)编译时多态/静态联编 指联编工作在编译阶段完成,即在编译阶段确定了程序中的操作调用与执行该操作的代码间的关系,基于指向对象的指针类型或引用类型。 (2)运行时多态/动态联编 指联编在程序运行动态进行...
  • 杂谈——编译时多态与运行时多态

    千次阅读 2018-12-20 10:32:35
    其实,这也没有啥好蒙圈的,因为: 重载都是编译时多态,而重写表现出两种多态性,当对象引用本类实例,为编译时多态,否则为运行时多态。  怎么判定是编译时多态还是运行时多态呢? 如果在编译时能够确定执行...
  • java多态 运行时多态编译时多态

    千次阅读 2018-11-28 18:58:07
    java多态 运行时多态编译时多态 我们知道java的多态是一个重要的特性,其中体现java的多态有两种形式。运行多态编译时多态编译时多态会发生在方法重载的时候,方法的重载指方法名相同,方法的参数...
  • java面向对象三大特性之多态---编译时多态和运行时多态详解 原创颺灏发布于2019-04-01 22:17:55阅读数 494收藏 展开 一.引言 1.什么是多态? 说到重载和重写,大家可能都知道。它们都是多态性的体现,那么说...
  • 可分为编译时多态和运行时多态。 运行时多态 运行时多态由函数重写产生,仅发生在函数重写,以以下程序为例: public class Test { public static void main(String[] args) { A a = new A(); B b = a; b.p...
  • 多态弊端: 前期定义的内容不能使用(调用)后期子类的特有方法(就是多态调用的只能是父类)。但如果是继承子类覆盖了父类方法,多态调用的仍是子类的方法! 多态前提: 1、必须有关系(继承、实现) 2、要有...
  • 一.引言 1.什么是多态? 说到重载和重写,大家可能都知道。它们都是多态性的体现,那么说什么是多态呢?多态是指允许不同子类型的对象对同一行为作出不同的响应。例如在生活中,比如跑的...多态性分为编译时的多...
  • Java 编译时多态和运行时多态

    万次阅读 多人点赞 2016-05-02 11:36:25
    根据何时确定执行多态方法中的哪一个,多态分为两种情况:编译时多态和运行时多态。如果在编译时能够确定执行多态方法 中的哪一个,称为编译时多态,否则称为运行时多态。 一、编译时多态  方法重载都是编译...
  • 编译时多态和运行时多态的区别

    千次阅读 2019-03-31 19:38:50
    编译时多态 主要是方法的重载,通过参数列表的不同来区分不同的方法。 运行时多态 也叫作动态绑定,一般是指在执行期间(非编译期间)判断引用对象的实际类型,根据实际类型判断并调用相应的属性和方法。主要用于...
  • 1)静态多态也称为编译时多态 2)动态多态也称为运行时多态 编译时间多态性(或静态多态性) 在编译器时间内解析的多态性称为静态多态性。 方法重载是编译时多态的一个例子。 方法重载:如果方法的参数在参数的...
  • C++之编译时多态

    千次阅读 2018-03-26 11:38:40
    虚函数是动态多态(函数调用的地址运行才可确定即为动态多态),C++编译时的多态性由函数重载和运算符重载来实现,运行的多态性由虚函数来实现。1、函数重载参数的个数,参数的类型,参数的顺序参数的...
  • 编译时期多态(又叫静态多态): 编译时期的多态是靠重载实现的,根据参数个数,类型和顺序决定的(必须在同一个类中) 运行多态(又叫动态多态): 在运行根据实际情况决定调用函数 ~~~~~~~~~~~~~~~~~~~~...
  • (1)静态方法调用是指对于类的静态方法的调用方式,是在编译时刻就已经确定好具体调用方法的情况,是静态绑定的。(2)动态方法调用需要有方法调用所作用的对象,是在调用的时候才确定具体的调用方...
  • Java多态编译时多态和运行时多态

    千次阅读 2016-09-04 10:53:39
    多态编译时多态和运行时多态。  第一个是通过方法重载实现;  第二个是通过方法覆盖实现(子类覆盖父类方法)。  第一种就是我们调用方法是不用区分参数类型,程序会自动执行相应方法,如:加法运算,可以使int...
  • 2. 静态多态(早绑定、编译时多态、重载) 程序在编译之前就知道用哪个函数,即在一个类中有相同的函数名,也就是函数重载。 3. 动态多态(晚绑定、运行期多态、覆盖 、重写)   使用虚函数,virtual +函数名,...
  • 1.多态基础知识: 什么是多态 面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。 多态的定义:指允许不同类的对象对同一消息...
  • 多态编译时多态运行时多态两种多态的优缺点 编译时多态 编译时多态,又叫静态多态、早绑定。编译时多态基于template(模板)的具现化与函数的重载解析,这种多态编译期进行,因此称为编译多态。 1.模板具现化 以...
  • 多态性(polymorphism)一词意味着具有多种形式。简而言之,我们可以将多态定义为消息以多种形式显示的能力。...在C++中,多态性主要分为两种类型:编译时多态运行时多态1. 编译时多态:这种类型的多态是通过函数重...
  • 多态性有两种: 1)编译时多态性 对于多个同名方法,如果在编译...1.编译时多态性:方法覆盖表现出两种多态性,当对象获得本类实例,为编译时多态性,否则为运行多态性,例如: XXXX x1 = new XXXX(参数列表); //对象获...
  • 多态顾名思义,是多种状态,是指方法有多种不同的状态,在编译时或运行才可以确定具体的状态,这种使程序具有多种状态的技术就叫做多态,在面向对象语言中,接口的多种不同的实现方式即为多态
  • 909422229__编译时多态与运行时多态

    千次阅读 2016-11-26 14:59:33
    最近听伙伴面试问多态的问题,于是本人加上百度与个人理解,现在上传一些个人的...多态分为编译时多态与运行时多态:   编译时多态:实现是重载,重载是方法的参数表不同,方法名相同,在不同对象调用方法的时候需要
  • 以前在大学学习OOP的时候,知道了重载和重写的区别,但如果要把他们和多态联系起来,我想很多新手朋友和我当初一样是死记的,...重载和重写我就不解释了,大家都知道,我现在主要是让大家记住 重载是编译时多态,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 137,021
精华内容 54,808
关键字:

编译时多态