C++中的继承与虚函数各种概念
虚继承与一般继承
虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段。而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个间接的指针去存取基类对象中的字段。
继承的特性
继承是面向对象中引入的重要特性之一,它的一个重要的特点就是子类是父类,父类不是子类。也就是说:
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(重定义),这是不推荐的。
虚函数、纯虚函数、虚基类、抽象类、虚函数继承、虚继承-------各种概念解释
虚函数:
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。是C++中多态性的一个重要体现,利用基类指针访问派生类中的成员函数,这种情况下使用虚函数,这种情况下采用的是动态绑定技术。
虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式:
virtual 函数返回值类型 虚函数名(形参表)
{ 函数体 }
动态绑定:
基类指针是调用派生类的中的成员函数还是调用基类中的成员函数要到程序运行时确定。主要要看指针所指向的对象。
纯虚函数:
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion1()=0
虚基类、抽象类:
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
虚函数继承:
虚函数继承就是覆盖。即基类中的虚函数被派生类中的同名函数所覆盖。 是实现多态的方法。
{
public:
vitual void foo(){cout < <"foo from parent";};
void foo1(){cout < <"foo1 from parent";};
};
class son:public parent
{
void foo(){cout < <"foo from son";};
void foo1(){cout < <"foo1 from son";};
};
int main()
{
parent *p=new son();
p->foo();
p->foo1();
return 0;
}
其输出结果是:
foo from son,foo1 from parent
虚继承:
解决多重继承中派生类成员函数调用模糊问题。比如类A中有一个函数print(),类B继承A,类C继承A,类D继承类B和类C,这个时候,类D中就有两个print函数,一个是从B继承得到的,一个是从C继承得到的,则类D的对象调用print函数就会出现print模糊的编译错误。解决办法:类B虚拟继承A。类C虚拟继承A,类D继承B,C时,只拷贝A中的数据成员和函数成员一次,再遇到拷贝时候就忽略了!
虚继承就是为了节约内存的,他是多重继承中的特有的概念。适用与菱形继承形式。
如:类B、C都继承类A,D继承类B和C。为了节省内存空间,可以将B、C对A的继承定义为虚拟继承,此时A就成了虚拟基类。
class A;
class B:public vitual A;
class C:public vitual A;
class D:public B,public C;
参考:http://www.haogongju.net/art/1069038
http://www.diybl.com/course/3_program/c++/cppsl/2008520/117246.html