本文共 3501 字,大约阅读时间需要 11 分钟。
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加"=0" ,同 java中抽象方法类似
virtual void funtion1()=01、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 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()一样,只是在基类中不需要写函数实现.
由于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所指的对象的析构函数,再调用父类的析构函数。
继承是面向对象中引入的重要特性之一,它的一个重要的特点就是子类是父类,父类不是子类。也就是说:
1. 如果基类指针指向子类对象(pbase=&pchild),则该指针只能调用基类定义了的函数;
2. 如果子类指针指向基类对象(pchild=(child *)pbase),则会出问题;
3. 如果基类和子类定义了同名的函数,则到底调用什么函数,必须视该指针的原始类型而定,而不是视指针实际指向的对象类型而定。
4. 就算子类继承了父类的某个函数(而未改写它),该函数依然被视为父类的,该函数依然属于父类的域中,该函数中使用的普通函数(非虚函数)依然被视为父类的函数。
5. 私有变量的继承性:私有变量对子类是不可见的,即使是子类从父类继承下来了,也仍然是不可见的,它仅仅能被父类的函数(没有在子类中改写或重写过的函数)操作。
6. this指针:类的成员函数的参数中有一个隐藏的参数this指针,这保证了被继承的成员函数归属对象的正确性和无混淆性。
正是因为继承的这个特点,虚函数的加入似乎就是顺理成章的事情了。虚函数简化并明确了软件和各种类库的设计以及维护。
一般的函数在编译链接时就进行了绑定,这称之为早绑定。由于信息量不够,所以只能依赖于调用它的对象或指针的声明类型实现绑定。也就是侯俊杰说的,如果基类和子类定义了同名的函数,那么到底调用哪个类的函数,必须视该指针的原始类型而定,而不是视指针实际指向的对象类型而定。因为赋值的动作还没有产生。
而虚函数则不是这样。虚函数实现的机制是晚绑定,它在编译链接时并没有与某个对象绑定----这也正是虚函数能实现多态(以相同代码调用不同函数)的原因所在。当编译器对程序进行编译碰到虚函数时,将不会赋予一个地址,而是插入一段汇编代码。每个包含虚函数的类都会由编译器产生一个虚函数表和一个虚函数表指针,其中虚函数表指针放在每个类的首地址处(也许不是,不过反正地址偏移量在每个类所占内存中是固定的)。当程序执行时,碰到对虚函数的调用,则通过插入的汇编代码到当前类的地址中找到虚函数表指针,通过虚函数的序号找到需要调用的虚函数。注意,一个系列的类的虚函数表中某一个函数的序号是一样的。而且,编译器会保证在使用父类指针操作子类对象时只能在父类已有的虚函数上实现虚函数的机制。这里还有一个虚函数的默认参数的问题。虚函数是动态绑定的,而默认参数则是静态绑定的,所以在虚函数中使用默认参数可以说是不符合逻辑的。如果子类改写了父类虚函数中的默认参数,当使用多态特性时,会出现调用子类的虚函数,使用的却是父类中的对应虚函数的默认参数的情况。
1. 某个子类中调用继承下来的非虚函数中有对已改写的虚函数的调用。值得注意的是,在某个子类中调用继承下来的未改写的非虚函数中有对已改写的虚函数的调用时,调用的是当前子类中改写过的虚函数;若该非虚函数中有对已改写的非虚函数的调用时,调用的是父类的非虚函数(也是因为晚绑定)。这是MFC的惯用手法。
2. 使用向上映射(父类指针=子类指针),实现代码的重用
3. 父类中的析构函数。当一个类确信不会成为任何类的父类时,它的析构函数是不需要设置成虚函数的;当一个类肯定会成为某个类的父类时,虚析构函数是必要的。因为若是父类中的析构函数是非虚的,则当用一个父类的指向子类的指针delete子类时,这种行为在C++标准中并没有被定义,是十分危险的。
经过以上分析可知,虚函数实际上就是继承中的一种接口。继承中一共有纯虚函数、非纯 虚函数和非虚函数三种接口,它们在子类中的处理如下:
1. 纯虚函数:所有子类必须强制性地改写,否则会报错。这是一种仅仅继承接口的方法。
2. 非纯虚函数:又被称为简单虚函数,可以在基类中有自己的实现(默认的动作),子类不一定要改写,这是一种继承接口及其默认实现的方法。
3. 非虚函数:子类最好不要改写,这是一种强制性地继承接口及其实现的方法,表示的是一种共性。
当在同一个类中存在同名但是参数不同的函数,叫作overloading;子类改写父类的虚函数,叫做overriding;子类改写父类的非虚函数,叫做redefining,这是不推荐的。
转自:
转载地址:http://apgpi.baihongyu.com/