虚函数和虚基类、纯虚函数、抽象类抽象方法 ,动态绑定

抽象类与接口紧密相关,它们不能实例化,并且常常部分实现或根本不实现。抽象类和接口之间的一个主要差别是:类可以实现无限个接口,但仅能从一个抽象(或 任何其他类型)类继承。从抽象类派生的类仍可实现接口。可以在创建组件时使用抽象类,因为它们使您得以在某些方法中指定不变级功能,但直到需要该类的特定 实现之后才实现其他方法。抽象类也制定版本,因为如果在派生类中需要附加功能,则可以将其添加到基类而不中断代码。

在实现抽象类时,必须实现该类中的每一个抽象方法,而每个已实现的方法必须和抽象类中指定的方法一样,接收相同数目和类型的参数,具有同样的返回值。
抽象类不能被实例化,也就是不能用new关键字去产生对象
   抽象方法只需声明,而不需实现
   抽象类的子类必须覆盖所有的抽象方法后才能被实例化,否则这个子类还是个抽象类


虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。

下面是对C++的虚函数这玩意儿的理解。

一,  什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你就应该从这里开始)

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性 是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码

class A{

public:

void print(){ cout<<”This is A”<<endl;}

};

class B:public A{

public:

void print(){ cout<<”This is B”<<endl;}

};

int main(){   //为了在以后便于区分,我这段main()代码叫做main1

A a;

B b;

a.print();

b.print();

}

通过class A和class B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is A和This is B。但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。

int main(){   //main2

A a;

B b;

A* p1=&a;

A* p2=&b;

p1->print();

p2->print();

}

运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A。问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数

class A{

public:

virtual void print(){ cout<<”This is A”<<endl;}  //现在成了虚函数了

};

class B:public A{

public:

void print(){ cout<<”This is B”<<endl;}  //这里需要在前面加上关键字virtual吗?

};

毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以, class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了。

现在重新运行main2的代码,这样输出的结果就是This is A和This is B了。

现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

虚基类:是为了避免多重继承中产生的二义性问题。比如当在多条继承路径上有一个共同的基类,当在多条路径的交汇处可能产生基类的多个实例(副本),为避免这个问题可以使用虚基类,虚基类并不在声明时指出,而是在子类继承时指明是虚继承。
class a{}
class b{}:virtual a

纯虚函数是一种特殊的虚函数,它的一般格式如下:



    class <类名>

    {

        virtual <类型><函数名>(<参数表>)=0;

        …

    };



    在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。




绑定:

一.定义:

1.方法绑定:一个方法被调用时该方法关联其方法体的过程。
2.静态绑定:在面向过程的中又称为前期绑定在程序编译时进行了绑定,即在还没运行时,就已经加载到内存。
3.动态绑定:在面向过程中称为后期绑定(运行时绑定)在运行时就进行绑定,根据实际情况有选择的进行绑定。


二.优越性:

         动态绑定灵活性相对静态绑定来说要高,因为它在运行之前可以进行选择性的绑定,很多时候优点就是缺点,正是因为选择性的绑定,所以动态绑定的执行效率要低些(因为,绑定对象,还要进行编译)。

三.静态绑定实例:
Java代码 
//父类 
public class Person { 
     
    protected String attribute="人的特性"; 
 

 
//子类 
public class Male extends Person { 
 
    protected String attribute = "男人的特性"; 
 
     

//测试 
 
public class Tester { 
 
public static void main(String[] args) { 
 
        Person p = new Male(); 
 
        System.out.println("" + p.attribute); 
    } 
 
 




输出结果:人的属性

可以看出子类的对象调用到的是父类的成员变量。所以必须明确,动态绑定针对的范畴只是对象的方法。


static 块静态加载:

Java代码 
public class StaticTest { 
 
    static { 
 
        System.out.println("没有主方法我照样执行"); 
    } 



运行结果:
没有主方法我照样执行java.lang.NoSuchMethodError: main
Exception in thread "main" 

java中的变量都是静态绑定的;
构造方法 以及private,static,final类方法的调用都是静态绑定的。

四.动态绑定实例:

Java代码 
//父类 
public class Person { 
   
  public void  show(){ 
     System.out.println("人的特性"); 
  } 

//子类 
public class Male extends Person{ 
 
   public void show(){ 
 
    System.out.println("男人的特性"); 


//测试 
public  class Tester{ 
 
public static void main(String [] args){ 
             
        Person p = new Male(); 
        p.show(); 
    } 



运行结果:男人的特性

上面当创建一个Male类对象,通过继承的关系向上转型立即赋值给Person类的对象,貌似调用的是Person中的show();,然而从输出的结果看调用的却是Male类中的show();这就是动态绑定的结果,它能在运行时找到实际最适合类型的方法进行调用

你可能感兴趣的:(动态绑定)