在面向对象的程序设计语言中,继承是一个非常重要的概念,使用继承和多态可以提高代码的可复用性和可维护性。本文主要总结了 C++ 中继承的实现细节,多态的概念和用途,以及虚函数继承时的虚表分析。
继承的概念 通过继承(inheritance)联系在一起的类构成一种层次关系,通常在层次关系的根部有一个基类(父类),其他类直接或间接地从基类继承而来,这些继承得到的类称为派生类(子类)。子类会继承父类的成员属性和成员方法。
下面是一个简单的实现继承的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Base // 基类{ public : void fun1 () { cout << "Fun1()" << endl; } int m_a; }; class Derived : public Base { public : void fun2 () { cout << "Fun2()" << endl; } int m_b; }; int main () { Derived derived; derived.m_a = 10 ; derived.m_b = 20 ; derived.fun1 (); derived.fun2 (); return 0 ; }
继承的权限分析 类成员的权限有三种:private、public、protected
private 私有成员,只能自身访问(当然C++中还有友元,这里先不考虑了)。
protected 保护成员,只能自身和非私有继承子类访问。
public 公有成员,可以在外部访问,可以由非私有继承的子类访问
这样描述起来有点乱,下面这张表对继承时的成员权限变化做出了总结
public 继承
protected 继承
private 继承
public 成员
public
protected
private
protected 成员
private
private
private
private 成员
不可访问
不可访问
不可访问
值得注意的还有一点,继承之后成员权限发生了变化,有些成员变得无法访问,但这并不意味着没有从父类那里继承对应的成员。
看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Base { private : char m_c; int m_a; }; class Derived : public Base{ private : short m_b; }; int main () { cout << sizeof (Derived) << endl; return 0 ; }
程序输出的结果为:
12
可见子类包含了父类的私有成员,但不能访问,并且通过字节对齐的规则可以知道,父类的成员在子类成员之前。(有关字节对齐的详细内容请移步这里 )
同名隐藏 在子类中声明一个与父类成员同名的成员(函数指函数名和参数列表完全相同,否则就是重载)时,父类中的成员会被隐藏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Base { public : void fun () { cout << "Base::fun()" << endl; } }; class Derived : public Base{ public : void fun () { cout << "Derived::fun()" << endl; } }; int main () { Derived d; d.fun (); d.Base::fun (); return 0 ; }
程序运行结果为:
Derived::fun() Base::fun()
虚函数与函数重写 C++中可以用 virtual 关键字来声明虚函数,子类在继承父类后,可以重写此函数,这与上文提到的同名隐藏不同,重写就是完全覆盖父类中定义的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Base { public : virtual void fun () { cout << "Base::fun()" << endl; } }; class Derived : public Base{ public : void fun () { cout << "Derived::fun()" << endl; } }; int main () { Derived d; d.fun (); d.Base::fun (); return 0 ; }
程序运行结果为:
Derived::fun() Base::fun()
emmmm,这样看来和同名隐藏也没什么区别嘛,那么虚函数的意义在哪里呢?
貌似是这样,再来看下面的代码:
1 2 3 4 5 6 7 int main () { Derived d; Base *pb = &d; pb->fun (); return 0 ; }
在使用没有 virtual 的代码时,程序运行的结果为:
Base::fun()
在使用没有 virtual 的代码时,程序运行的结果为:
Derived::fun()
在使用了 virtual 时,使用父类的指针指向子类对象,可以访问到子类的成员方法。这就是多态的基本实现方式。
纯虚函数 在 C++ 中可以声明纯虚函数,与普通虚函数不同的是,纯虚函数在父类中并没有给出明确的定义(即没有函数体),这就要求在子类中给出纯虚函数的定义。含有纯虚函数的类不能被实例化。
1 2 3 4 5 6 7 8 9 10 11 12 class Base { public : virtual void fun () = 0 ; }; class Derived { public : void fun () { cout << "fun()" << endl; } };
虚析构的意义 顾名思义,虚析构就是虚的析构函数,如果一个类的析构函数中有必须执行的操作(比如动态空间的释放),并且这个类有可能被其他类继承,则必须将父类的析构函数声明为 virtual.
在下面的例子中,演示了析构函数是否为虚时的不同效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Base { public : ~Base () { cout << "~Base()" << endl; } }; class Derived : public Base{ public : ~Derived () { cout << "~Derived()" << endl; } }; int main () { Base *pb = new Derived; delete pb; return 0 ; }
这是未声明为虚析构函数时的情况,程序运行的结果为
~Base()
只调用了父类的析构函数,而子类中有可能的内存释放等必须要进行的操作就被没有执行。
当把父类的析构函数声明为 virtual 时(ps:代码就不贴了哈),程序运行结果为
~Derived() ~Base()
这样就正确地执行了子类的析构函数。实际编程中,建议把有可能被继承的类的析构函数都声明为 virtual,因为你永远不知道以后会不会有人在子类中写独有的析构方法。
多继承 在 C++ 中,一个类可以继承于多个父类。
父类构造的顺序与继承时的顺序有关,与构造函数参数初始化刘表无关。
父类析构的顺序是构造的反顺序。
虚继承的顺序会提前。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Base1 { public : Base1 () { cout << "Base1()" << endl; } virtual ~Base1 () { cout << "~Base1()" << endl; } }; class Base2 { public : Base2 () { cout << "Base2()" << endl; } virtual ~Base2 () { cout << "~Base2()" << endl; } }; class Derived : public Base1, public Base2{ public : Derived () { cout << "Derived" << endl; } ~Derived () { cout << "~Derived()" << endl; } }; int main () { Derived d; return 0 ; }
程序运行的结果是:
Base1() Base2() Derived() ~Derived() ~Base2() ~Base1()
虚继承 虚继承主要解决的是多重继承时的二义性问题。如图所示:
在类 D 中,会出现两份类 A 的成员,这显然是不合理的,不仅会浪费空间,还会导致二义性的问题,虚继承很好地解决了这一问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class A { public : int a; }; class B : virtual public A{ public : int b; }; class C : virtual public A{ public : int c; }; class D : public B, public C{ public : int d; }; int main () { D d; d.a = 0 ; d.b = 1 ; d.c = 2 ; d.d = 3 ; return 0 ; }
多态的作用 下面通过一个多态解决实际问题的案例来说明多态的作用
假设要开发一个画图程序,可以绘制不同的图形。
定义一个 Shap 类,作为所有图形类的基类。
每当需要增加一种图形时,不需要修改原先的代码,只需继承于 Shap 实现其中的 draw 方法即可。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Shap { public : virtual void draw () = 0 ; }; class Rect : public Shap{ public : void draw () { cout << "Draw a rectangle" << endl; } }; class Circle : public Shap{ public : void draw () { cout << "Draw a circle" << endl; } };
虚函数表结构分析 虚函数表是 C++ 虚函数,虚继承这一系列机制的底层实现方式,我们来一步步揭开它神秘的面纱。
当声明一个虚函数时,对应的类会多出一个虚表指针,指向虚函数表所在的内存空间,虚函数表保存了类中所有虚函数的函数指针,当一个类继承父类并重写其中的虚函数时,实际上就是修改了虚表中对应的函数指针的指向。
下面的程序对虚函数表的结构进行了验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Base { public : virtual void fun1 () { cout << "Base::fun1()" << endl; } virtual void fun2 () { cout << "Base::fun2()" << endl; } }; typedef void (*Fun) (void ) ;typedef void * Ptr;int main () { cout << "size of Base : " << sizeof (Base) << endl; cout << "size of Derived : " << sizeof (Derived) << endl; Base b; cout << "address of b'vir-table : " << (Ptr*)(&b) << endl; cout << "address of b'first vir-function : " << (Ptr*)*(Ptr*)(&b) << endl; cout << "address of b'second vir-function : " << (Ptr*)*(Ptr*)(&b) + 1 << endl; Fun pFun = NULL ; pFun = (Fun)*(Ptr*)*(Ptr*)(&b); pFun (); pFun = (Fun)*((Ptr*)*(Ptr*)(&b) + 1 ); pFun (); return 0 ; }
程序运行的结果是:(ps:当然,地址不会大家都一样)
size of Base : 8 size of Derived : 8 address of b’vir-table : 0x7ffdca2c0c50 address of b’first vir-function : 0x55c426c25d90 address of b’second vir-function : 0x55c426c25d98 Base::fun1() Base::fun2()
如果上面的指针变换过程看的一头雾水,可以对照下面的图进行分析。
当子类有函数重写时,类图及虚表结构图如下:
当多继承时,类图及虚表结构图如下:
最后附上一段多继承时验证虚表结构的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class Base1 { public : virtual void fun1 () { cout << "Base1::fun1()" << endl; } virtual void fun2 () { cout << "Base2::fun2()" << endl; } }; class Base2 { public : virtual void fun3 () { cout << "Base3::fun3()" << endl; } virtual void fun4 () { cout << "Base4::fun4()" << endl; } }; class Derived : public Base1, public Base2{ public : void fun1 () { cout << "Derived::fun1()" << endl; } void fun3 () { cout << "Derived::fun3()" << endl; } }; typedef void (*Fun) (void ) ;typedef void * Ptr;int main () { cout << "size of Derived : " << sizeof (Derived) << endl; Derived d; cout << "address of d'vir-table1 : " << (Ptr*)(&d) << endl; cout << "address of d'first vir-function : " << (Ptr*)*(Ptr*)(&d) << endl; cout << "address of d'second vir-function : " << (Ptr*)*(Ptr*)(&d) + 1 << endl; cout << "address of b'vir-table2 : " << (Ptr*)(&d) + 1 << endl; cout << "address of d'first vir-function : " << (Ptr*)*((Ptr*)(&d) + 1 ) << endl; cout << "address of d'second vir-function : " << (Ptr*)*((Ptr*)(&d) + 1 ) + 1 << endl; Fun pFun = NULL ; pFun = (Fun)*(Ptr*)*(Ptr*)(&d); pFun (); pFun = (Fun)*((Ptr*)*(Ptr*)(&d) + 1 ); pFun (); pFun = (Fun)*((Ptr*)*((Ptr*)(&d) + 1 )); pFun (); pFun = (Fun)*((Ptr*)*((Ptr*)(&d) + 1 ) + 1 ); pFun (); return 0 ; }
End,如有不当之处,望指正。