按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
为'Shape' 的物体呢)。如果硬要强渡关山,会换来这样的编译消息:
error : illegal attempt to instantiate abstract class。
关于抽象类别,我还有一点补充。CCircle 继承了CShape 之后,如果没有改写CShape 中
的纯虚拟函数,那么CCircle 本身也就成为一个拥有纯虚拟函数的类别,于是它也是一
个抽象类别。
是对虚拟函数做结论的时候了:
■ 如果你期望衍生类别重新定义一个成员函数,那么你应该在基础类别中把此函
数设为virtual 。
Polymorphism 〃the ability to
■ 以单一指令唤起不同函数,这种性质称为 ,意思是
assume many forms〃,也就是多态。
C++ Polymorphism
■ 虚拟函数是 语言的 性质以及动态绑定的关键。
76
…………………………………………………………Page 139……………………………………………………………
■ 既然抽象类别中的虚拟函数不打算被调用,我们就不应该定义它,应该把它设
为纯虚拟函数(在函数声明之后加上〃=0〃 即可)。
■ 我们可以说,拥有纯虚拟函数者为抽象类别(abstract Class ),以别于所谓的
具象类别(concrete class) 。
■ 抽象类别不能产生出对象实体,但是我们可以拥有指向抽象类别之指针,以便
于操作抽象类别的各个衍生类别。
■ 虚拟函数衍生下去仍为虚拟函数,而且可以省略virtual 关键词。
类别与对象大解剖
你一定很想知道虚拟函数是怎么做出来的,对不对?
如果能够了解C++ 编译器对于虚拟函数的实现方式,我们就能够知道为什么虚拟函数
可以做到动态绑定。
为了达到动态绑定(后期绑定)的目的,C++ 编译器透过某个表格,在执行时期「间接」
调用实际上欲绑定的函数(注意「间接」这个字眼)。这样的表格称为虚拟函数表(常
被称为vtable )。每一个「内含虚拟函数的类别」,编译器都会为它做出一个虚拟函数表,
表中的每一笔元素都指向一个虚拟函数的地址。此外,编译器当然也会为类别加上一项
成员变量,是一个指向该虚拟函数表的指针(常被称为vptr )。举个例:
class Class1 {
public :
data1;
data2;
memfunc ();
virtual vfunc1();
virtual vfunc2 ();
virtual vfunc3();
};
Class1 对象实体在内存中占据这样的空间:
77
…………………………………………………………Page 140……………………………………………………………
Class1 对象实体 vtable
vptr (*vfunc1)() Class1::vfunc1()
class Class1 m_data1 (*vfunc2)() Class1::vfunc2()
{
public : m_data2 (*vfunc3)() Class1::vfunc3()
m_data1;
m_data2; Class1::memfunc()
memfunc();
virtual vfunc1();
virtual vfunc2();
virtual vfunc3();
}
C++ C
类别的成员函数,你可以想象就是 语言中的函数。它只是被编译器改过名称,
并增加一个参数(this 指针),因而可以处理调用者(C++ 对象)中的成员变量。所以,
你并没有在Class1 对象的内存区块中看到任何与成员函数有关的任何东西。
每一个由此类别衍生出来的对象,都有这么一个vptr 。当我们透过这个对象调用虚拟函
数,事实上是透过vptr 找到虚拟函数表,再找出虚拟函数的真正地址。
奥妙在于这个虚拟函数表以及这种间接调用方式。虚拟函数表的内容是依据类别中的虚
拟函数声明次序,一一填入函数指针。衍生类别会继承基础类别的虚拟函数表(以及所
有其它可以继承的成员),当我们在衍生类别中改写虚拟函数时,虚拟函数表就受了影
响:表中元素所指的函数地址将不再是基础类别的函数地址,而是衍生类别的函数地址。
看看这个例子:
class Class2 : public Class1 {
public :
data3;
memfunc();
virtual vfunc2();
};
78
…………………………………………………………Page 141……………………………………………………………
class Class2 : public Class1
{ vtable
public : vptr (*vfunc1)() Class1::vfunc1()
m_data3;
memfunc(); m_data1 (*vfunc2)() Class2::vfunc2()
virtual vfunc2();
} m_data2 (*vfunc3)() Class1::vfunc3()
m_data3 Class2::memfunc()
Class2 对象实体
于是,一个「指向Class1 所生对象」的指针,所调用的vf unc2 就是Class1::vf unc2 ,而
一个「指向Class2 所生对象」的指针,所调用的vf unc2 就是Class2::vf unc2 。
动态绑定机制,在执行时期,根据虚拟函数表,做出了正确的选择。
我们解开了第二道神秘。
口说无凭,何不看点实际。观其地址,物焉C哉,下面是一个测试程序:
#0001 #include
#0002 #include
#0003
#0004 class ClassA
#0005 {
#0006 public:
#0007 int m_data1;
#0008 int m_data2;
#0009 void func1() { }
#0010 void func2() { }
#0011 virtual void vfunc1() { }
#0012 virtual void vfunc2() { }
#0013 };
#0014
#0015 class ClassB : public ClassA
#0016 {
#0017 public:
#0018 int m_data3;
#0019 void func2() { }
#0020 virtual void vfunc1() { }
#0021 };
#0022
#0023 class ClassC : public ClassB
#0024 {
79
…………………………………………………………Page 142……………………………………………………………
#0025 public:
#0026 int m_data1;
#0027 int m_data4;
#0028 void func2() { }
#0029 virtual void vfunc1() { }
#0030 };
#0031
#0032 void main()
#0033 {
#0034 cout