1,定义:
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加"=0" ,同 java中抽象方法类似
virtual void funtion1()=0
二、引入原因:
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念:
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。
2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载
3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
程序举例:
基类:
class A
{
public:
A();
void f1();
virtual void f2();
virtual void f3()=0;
virtual ~A();
};
子类:
class B : public A
{
public:
B();
void f1();
void f2();
void f3();
virtual ~B();
};
主函数:
int main(int argc, char* argv[])
{
A *m_j=new B();
m_j->f1();
m_j->f2();
m_j->f3();
delete m_j;
return 0;
}
f1()是一个普通的重载.
调用m_j->f1();会去调用A类中的f1(),它是在我们写好代码的时候就会定好的.
也就是根据它是由A类定义的,这样就调用这个类的函数.
f2()是虚函数.
调用m_j->f2();会调用m_j中到底保存的对象中,对应的这个函数.这是由于new的B
对象.
f3()与f2()一样,只是在基类中不需要写函数实现.
转:
多态上java和C++的不同之处
1.java中接口相当于C++中的“纯虚类”(所有的成员方法都是纯虚函数的类),即其中所有的方法都不包括函数体;而java中的抽象类和C++中的抽象类在意义上则大差不离。
2.java中的抽象类由关键字abstract clas 声明,若一个类被声明为抽象类那么则该类中必须要有一个抽象方法abstract function;C++中的一个类中若有一个方法声明成 纯虚函数virtual function=0,那么该类就是抽象类。若一个抽象类被其派生类继承,基类中的所有抽象(纯虚)方法若被子类实现那么该类就不在是抽象类,否则这个子类还是抽象类,那么它同样不能被实例化,这一点在java和C++中是相同的。
3.在面向对象的编程中,请牢记所有基类最好是抽象类(或者接口),这样才符合OO编程思想。因为基类(接口)存在的目的是因为,它们包含同一类对象中的某些共性的行为和属性,将这些共性的行为和属性抽象成抽象(纯虚)函数放在基类中,当不同子类继承基类时,可以按照自身特有禀赋对这些抽象的共性的行为进行具体地实现(动物都是进食的行为,但不同的动物会有不同的进食方式),这些特定的具体实现的函数就变为虚函数,可以用于多态了;但是若是该类别的共性行为可以确定,不随子类的差别而又区分,那么与这个共性行为相关的函数不需要声明为纯虚函数。按照这个意义,基类中的抽象(纯虚)方法就如同一个个“接口”(画布),不同的子类对这些抽象(纯虚)方法进行实现,就如同在这些“画布”上画出不同的风格的作品。但有一点要注意:由于java封装了C++中的对象的指针、句柄和引用等相关概念,让人不能那么明显的体会到C++中的这些概念,这就产生了这么一个现象:java类中的普通方法相当于C++中的虚方法,java中的抽象方法相当于C++中的纯虚方法。例子:JAVA中 B extends A, A和B 中都有函数fun,即B重载了fun,那么当A a=new B;a.fun调用的是B类中的fun,也就是调用对象引用a所指的对象的fun;C++中 B :public A,A和B 中都有虚函数fun,即B重载了fun,那么当A* a=new()B,a->fun调用的也是B类中的fun,就是指针a所指对象的fun。在C++中如若不把fun声明为虚函数,那么a->fun调用的将是A类中的fun,,因为C++OO中的函数调用是根据原始指针类型所拥有的函数来进行的,这样就无法实现在运行时的多态功能,只有将它们声明为虚函数才能实现动态绑定。
4.由于C++中存在析构函数,为了在多态的过程中,不造成内存“泄漏”,通常将虚基类的析构函数定义为虚析构函数()。考虑下面代码:
class Animal
{
public:
Animal()
{
}
virtual ~Animal()
{
cout<<"Animal descontruct"<<endl;
}
};
class Fish:public Animal
{
public:
Fish()
{
}
~Fish()
{
cout<<"Fish descontruct"<<endl;
}
};
int main()
{
Animal* a=new Fish();
delete a;
}
结果运行为:Fish deconstruct
Animal deconstruct。若基类Animal的析构函数不为虚,那么程序将只打印Animal deconstruct,因为a在编译期间就被定义为指向Animal类对象的指针,在Animal类 的一个对象(由于父类Animal的构造函数没有参数,那么在new fish的时候父类构造函数会被自动调用,这一点和java相同;当父类的构造函数有参数时,C++中在子类的构造函数那里用:父类名(参数表)来实现的,而java中是在子类的构造函数函数中用super关键字来调用父类的构造函数)生命结束后,编译器认为a是指向父类anima类型的指针,此时会跳过子类的析构函数,直接根据a的指针类型(指向父类)来调用父类的析构函数。而当父类的析构函数是虚时,在delete a时,会有动态绑定,即先调用a所指的对象的析构函数,再调用父类的析构函数。
JAVA中则不存在C++中相应的问题:只要将父类的引用指向一个子类对象,那么只要子类覆盖了父类中的方法,当通过父类引用来调用方法时,会直接调用子类中的方法。
5.对比:C++中的虚方法相当于java中的一般方法,C++中的纯虚方法相当于java中的虚方法。C++中的一般方法无法实现多态,而java的可以。C++中,父类中最好有纯虚函数(父类不具有而子类具有的共性而又具体的行为),虚函数根据设计思路可有可无,当你需要在子类中重新定义(重载)父类中的一个函数时,那么为了实现多态,切记要在父类中把该函数声明为虚函数(父类和子类具有的个性或者共性行为),在子类最好实现父类中的纯虚方法,如若不然(虽然程序不会出错,但是这个子类还是抽象类,无法被实实例化),你继承这个父类还有什么意思?虚函数可以在子类中重载,也可以不重载。重载了的话,那么该虚函数对应的行为就是子类特有的;否则就是父类和子类共有的行为。最后无论子类重不重载子类中的虚函数,该函数始终都是一个虚函数。
当创建子类对象时,java和C++都会调用父类的构造函数,但调用父类的初始化方法(不是构造函数),并不会产生父类的对象,:java中的路由机制是:当调用父类构造函数时,会逐层递归调用每个父类的初始化方法直到OBJECT类为止(OBJECT无父类),在编译阶段将该子类所有父类的属性,方法和构造函数作为初始化方法写进方法区,每个父类有几个构造函数,就会有相应数目的初始化方法,编译器会根据子类构造函数的参数来调用相应的初始化方法,来进行父类的初始化。当子类对象生命完结后,JVM会先释放子类对象的内存,在释放父类的初始化方法所占的内存。而C++中由于存在析构函数,当父类有虚析构时,会先析构子类的对象的内存,然后在析构父类的初始化方法所占的内存。不过C++的析构函数所具有的作用不仅仅在于释放资源,也可以用来用作其他功能(通过static类成员NUM来计数细菌的数量,每新建一个细菌对象,在构造函数中进行++NUM;一个细菌死亡在析构函数中进行--NUM)。