C++ 虚函数&纯虚函数&抽象类&接口&虚基类(转)

http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.htmlhtml

1. 多态算法

在面向对象语言中,接口的多种不一样实现方式即为多态。多态是指,用父类的指针指向子类的实例(对象),而后经过父类的指针调用实际子类的成员函数app

多态性就是容许将子类类型的指针赋值给父类类型的指针,多态是经过虚函数实现的函数

多态可让父类的指针有“多种形态”,这是一种泛型技术。(所谓泛型技术,就是试图使用不变的代码来实现可变的算法)。性能

2. 虚函数指针

2.1虚函数定义htm

在基类的类定义中,定义虚函数的通常形式:对象

Virtual 函数返回值类型 虚函数名(形参表)
{函数体}blog

虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public。继承

2.2 虚函数的做用

虚函数的做用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,能够在基类的派生类中对虚函数进行从新定义(形式同上)。在派生类中定义的函数应与虚函数具备相同的形参个数和形参类型(覆盖),以实现统一的接口,不一样定义过程。若是在派生类中没有对虚函数从新定义,则它继承其基类的虚函数。

虚函数可让成员函数操做通常化,用基类的指针指向不一样的派生类的对象时,基类虚成员函数调用基类指针,则会调用其真正指向的对象的成员函数,而不是基类中定义的成员函数(只要派生类改写了该成员函数)。若不是虚函数,则无论基类指针指向哪一个派生类对象,调用时都会调用基类中定义的那个函数。

2.3 实现动态联编须要三个条件:

1)必须把须要动态联编的行为定义为类的公共属性的虚函数
2)类之间存在子类型关系,通常表现为一个类从另外一个类公有派生而来;
3)必须先使用基类指针指向子类型的对象,而后直接或者间接使用基类指针调用虚函数。

2.4 定义虚函数的限制

1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但能够将析构函数定义为虚函数

2)只须要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不须要使用关键字“virtual”。

3)若是声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名而且返回值、参数个数、参数类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种非虚的同名同返回值同参数个数同参数类型函数。

2.5 

1)为何类的静态成员函数不能为虚函数: 

若是定义为虚函数,那么它就是动态绑定的,也就是在派生类中能够被覆盖的,这与静态成员函数的定义(在内存中只有一份拷贝,经过类名或对象引用访问静态成员)自己就是相矛盾的。

2)为何构造函数不能为虚函数:

由于若是构造函数为虚函数的话,它将在执行期间被构造,而执行期则须要对象已经创建,构造函数所完成的工做就是为了创建合适的对象,所以在没有构建好的对象上不可能执行多态(虚函数的目的就在于实现多态性)的工做。在继承体系中,构造的顺序就是从基类到派生类,其目的就在于确保对象可以成功地构建。构造函数同时承担着虚函数表的创建,若是它自己都是虚函数的话,如何确保vtbl的构建成功呢?

3)虚析构函数

C++开发的时候,用来作基类的类的析构函数通常都是虚函数。当基类中有虚函数的时候,析构函数也要定义为虚析构函数。若是不定义虚析构函数,当删除一个指向派生类对象的指针时,会调用基类的析构函数,派生类的析构函数未被调用,形成内存泄露。
虚析构函数工做的方式是:最底层的派生类的析构函数最早被调用,而后各个基类的析构函数被调用。这样,当删除指向派生类的指针时,就会首先调用派生类的析构函数,不会有内存泄露的问题了。
通常状况下,若是类中没有虚函数,就不用去声明虚析构函数。当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。
只有当一个类被用来做为基类的时候,才把析构函数写成虚函数。

2.6虚函数的实现——虚函数表

虚函数是经过一张虚函数表来实现的,简称V-Table。类的虚函数表是一块连续的内存,每一个内存单元中记录一个JMP指令的地址。编译器会为每一个有虚函数的类建立一个虚函数表,该虚函数表将被该类的全部对象共享,类的每一个虚函数成员占据虚函数表中的一行。
在这个表中,主要是一个类的虚函数的地址表。这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。在有虚函数的类的实例中,分配了指向这个表的指针的内存,因此,当用父类的指针来操做一个子类的时候,这张虚函数表就指明了实际所应该调用的函数。

3. 纯虚函数

许多状况下,在基类中不能对虚函数给出有意义的实现,则把它声明为纯虚函数,它的实现留给该基类的派生类去作

纯虚函数的声明格式:virtual <函数返回类型说明符> <函数名> ( <参数表> )=0;

纯虚函数的做用是为派生类提供一个一致的接口

4.抽象类(abstract class)

抽象类是指含有纯虚函数的类(至少有一个纯虚函数),该类不能建立对象(抽象类不能实例化),可是能够声明指针和引用,用于基础类的接口声明和运行时的多态。

抽象类中,既能够有抽象方法,也能够有具体方法或者叫非抽象方法。抽象类中,既能够全是抽象方法,也能够全是非抽象方法。一个继承于抽象类的子类,只有实现了父类全部的抽象方法才可以是非抽象类。

5.接口

接口是一个概念。它在C++中用抽象类来实现,在C#和Java中用interface来实现。

接口是专门被继承的。接口存在的意义也是被继承。和C++里的抽象类里的纯虚函数是相同的。不能被实例化。
定义接口的关键字是interface,例如:   
public interface MyInterface{   
public void add(int x,int y);   
public void volume(int x,int y,int z);   
}  

继承接口的关键字是implements,至关于继承类的extends。须要注意的是,当继承一个接口时,接口里的全部函数必须所有被覆盖。
当想继承多个类时,开发程序不容许,报错。这样就要用到接口。由于接口容许多重继承,而类不容许(C++中能够多重继承)。因此就要用到接口。

6.虚基类

在派生类继承基类时,加上一个virtual关键词则为虚拟基类继承,如:
class derive : virtual public base
{
};

虚基类是相对于它的派生类而言的,它自己能够是一个普通的类。只有它的派生类虚继承它的时候,它才称做虚基类,若是没有虚继承的话,就称为基类。好比类B虚继承于类A,那类A就称做类B的虚基类,若是没有虚继承,那类B就只是类A的基类。
虚继承主要用于一个类继承多个类的状况,避免重复继承同一个类两次或屡次。
例如 由类A派生类B和类C,类D又同时继承类B和类C,这时候类D就要用虚继承的方式避免重复继承类A两次。

7. 抽象类VS接口

一个类能够有多个接口,只能继承一个父类??

抽象类能够有构造方法,接口中不能有构造方法;

抽象类中能够有普通成员变量,接口中没有普通成员变量;

接口里边所有方法都必须是abstract的,抽象类的能够有实现了的方法;

抽象类中的抽象方法的访问类型能够是public,protected,但接口中的抽象方法只能是public类型的,而且默认即为public abstract类型;

抽象类中能够包含静态方法,接口中不能包含静态方法;

抽象类和接口中均可以包含静态成员变量,抽象类中的静态成员变量的访问类型能够任意,但接口中定义的变量只能是public static final类型,而且默认即为public static final类型。

8. 虚函数VS纯虚函数

虚函数
引入缘由:为了方便使用多态特性,咱们经常须要在基类中定义虚函数。
纯虚函数
引入缘由:
1)同“虚函数”;
2)在不少状况下,基类自己生成对象是不合情理的。例如,动物做为一个基类能够派生出老虎、孔雀等子类,但动物自己生成对象明显不合常理。
纯虚函数就是基类只定义了函数体,没有实现过程。
纯虚函数至关于接口,不能直接实例话,须要派生类来实现函数定义;
有的人可能在想,定义这些有什么用?
好比你想描述一些事物的属性给别人,而本身不想去实现,就能够定义为纯虚函数。说的再透彻一些,好比盖楼房,你是老板,你给建筑公司描述清楚你的楼房的特性,多少层,楼顶要有个花园什么的,建筑公司就能够按照你的方法去实现了,若是你不说清楚这些,可能建筑公司不太了解你须要楼房的特性。用纯需函数就能够很好的分工合做了。

两者的区别:

1> 类里声明为虚函数的话,这个函数是实现的,哪怕是空实现,它的做用就是为了能让这个函数在它的子类里面能够被重载,这样的话,编译器就可使用后期绑定来达到多态了;
纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。

2>虚函数在子类里面也能够不重载的;但纯虚必须在子类去实现,这就像Java的接口同样。一般咱们把不少函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,可是增长了面向对象的多态性,由于你很难预料到父类里面的这个函数不在子类里面不去修改它的实现;

3>虚函数的类用于“实做继承”,继承接口的同时也继承了父类的实现。固然咱们也能够完成本身的实现。纯虚函数的类用于“介面继承”,主要用于通讯协议方面。关注的是接口的统一性,实现由子类完成。通常来讲,介面类中只有纯虚函数的;

4>带纯虚函数的类叫抽象类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。

相关文章
相关标签/搜索