1.首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。定义他是为了实现一个接口,起到一个规范的作用,规范继承这个。类的程序员必须实现这个函数。
2.关于实例化一个类:
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如:
class CA
{
public:
virtual void fun() = 0; // 说明fun函数为纯虚函数
virtual void fun1();
};
class CB
{
public:
virtual void fun();
virtual void fun1();
};
// CA,CB类的实现
...
void main()
{
CA a; // 不允许,因为类CA中有纯虚函数
CB b; // 可以,因为类CB中没有纯虚函数
...
}
3.虚函数在多态中间的使用:
多态一般就是通过指向基类的指针来实现的。
4.有一点你必须明白,就是用父类的指针在运行时刻来调用子类:
例如,有个函数是这样的:
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。定义他是为了实现一个接口,起到一个规范的作用,规范继承这个。类的程序员必须实现这个函数。
2.关于实例化一个类:
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如:
class CA
{
public:
virtual void fun() = 0; // 说明fun函数为纯虚函数
virtual void fun1();
};
class CB
{
public:
virtual void fun();
virtual void fun1();
};
// CA,CB类的实现
...
void main()
{
CA a; // 不允许,因为类CA中有纯虚函数
CB b; // 可以,因为类CB中没有纯虚函数
...
}
3.虚函数在多态中间的使用:
多态一般就是通过指向基类的指针来实现的。
4.有一点你必须明白,就是用父类的指针在运行时刻来调用子类:
例如,有个函数是这样的:
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}
参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。
参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。
5.用虚函数
#include <iostream.h>
class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
virtual void born();
};
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}
animal::animal() { }
animal::~animal() { }
void animal::born()
{
cout<< "animal";
}
///////////////////////horse
class horse:public animal
{
public:
horse();
~horse();
virtual void born();
};
horse::horse() { }
horse::~horse() { }
void horse::born()
{
cout<<"horse";
}
///////////////////////main
void main()
{
animal a;
horse b;
a.fun1(&b);
}
//output: horse
cout<<"horse";
}
///////////////////////main
void main()
{
animal a;
horse b;
a.fun1(&b);
}
//output: horse
6.不用虚函数
#include <iostream.h>
class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
void born();
};
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}
animal::animal() { }
animal::~animal() { }
void animal::born()
{
cout<< "animal";
}
////////////////////////horse
class horse:public animal
{
public:
horse();
~horse();
void born();
};
horse::horse() { }
horse::~horse() { }
void horse::born()
void animal::born()
{
cout<< "animal";
}
////////////////////////horse
class horse:public animal
{
public:
horse();
~horse();
void born();
};
horse::horse() { }
horse::~horse() { }
void horse::born()
{
cout<<"horse";
}
////////////////////main
void main()
{
animal a;
horse b;
a.fun1(&b);
}
//output: animal
cout<<"horse";
}
////////////////////main
void main()
{
animal a;
horse b;
a.fun1(&b);
}
//output: animal
多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的对象),当调用函数时可以自动判断调用的是哪个对象的函数。
一个函数说明为虚函数,表明在继承的类中重载这个函数时,当调用这个函数时应当查看以确定调用哪个对象的这个函数。
普通函数的处理:一个特定的函数都会映射到特定的代码,无论时编译阶段还是连接阶段,编译器都能计算出这个函数的地址,调用即可。
虚函数的处理:被调用的函数不仅依据调用的特定函数,还依据调用的
对象的种类。通常是由虚函数表(vtable)来实现的。
虚函数表的结构:它是一个函数指针表,每一个表项都指向一个函数。任何一个包含至少一个虚函数的类都会有这样一张表。需要注意的是vtable只包含虚函数的指针,没有函数体。
实现上是一个函数指针的数组。虚函数表既有继承性又有多态性。每个派生类的
vtable
继承了它各个基类的
vtable
,如果基类
vtable
中包含某一项,则其派生类的
vtable
中也将包含同样的一项,但是两项的值可能不同。如果派生类重载
(override)
了该项对应的虚函数,则派生类
vtable
的该项指向重载后的虚函数,没有重载的话,则沿用基类的值。
每一个类只有唯一的一个vtable,不是每个对象都有一个vtable,恰恰是每个同一个类的对象都有一个指针,这个指针指向该类的vtable(当然,前提是这个类包含虚函数)。那么,每个对象只额外增加了一个指针的大小,一般说来是4字节。
在类对象的内存布局中,首先是该类的vtable指针,然后才是对象数据。
在通过对象指针调用一个虚函数时,编译器生成的代码将先获取对象类的vtable指针,然后调用vtable中对应的项。对于通过对象指针调用的情况,在编译期间无法确定指针指向的是基类对象还是派生类对象,或者是哪个派生类的对象(见代码中的函数f在编译期间是无法判断的)。但是在运行期间执行到调用语句时,这一点已经确定,编译后的调用代码能够根据具体对象获取正确的vtable,调用正确的虚函数,从而实现多态性。
给出实例代码:
class A {
public :
virtual void run (){......}
}
class B :public A{
public:
void run(){......}
}
int f (A *pA){
pA->run();
}
分析一下这里的思想所在,问题的实质是这样,对于发出虚函数调用的这个对象指针,在编译期间缺乏更多的信息,而在运行期间具备足够的信息,但那时已不再进行绑定了而是直接执行好了,怎么在二者之间作一个过渡呢?把绑定所需的信息用一种通用的数据结构记录下来,该数据结构可以同对象指针相联系,在编译时只需要使用这个数据结构进行抽象的绑定,而在运行期间将会得到真正的绑定。这个数据结构就是
vtable
,也就是编译期间建立
vtable表,执行期间查表执行
。可以看到,实现用户所需的抽象和多态需要进行后绑定,而编译器又是通过抽象和多态而实现后绑定的。
下面是通过基类的指针来调用虚函数时,所发生的一切:
step 1:
开始执行调用
pA->run();
(这里能判断到底是哪个对象)
step 2:
取得对象的
vtable
的指针
step 3:
从
vtable
那里获得函数入口的偏移量,即得到要调用的函数的指针
step 4:
根据
vtable
的地址找到函数,并调用函数。
step 1
和
step 4
对于一般函数是一样的,虚函数只是多了
step 2
和
step 3
。
解惑:
1基类和派生类是共用一表,还是各有各的表(物理上)
答:基类和派生类是各有各的表,也就是说他们的物理地址是分开的,基类和派生类的虚表的唯一关联是:当派生类没有实现基类虚函数的重载时,派生类会直接把自己表的该函数地址值写为基类的该函数地址值.
答:基类和派生类是各有各的表,也就是说他们的物理地址是分开的,基类和派生类的虚表的唯一关联是:当派生类没有实现基类虚函数的重载时,派生类会直接把自己表的该函数地址值写为基类的该函数地址值.
转载:
http://www.cppblog.com/ElliottZC/archive/2007/07/20/28417.aspx
http://www.cppblog.com/ElliottZC/archive/2007/07/20/28416.aspx
3852

被折叠的 条评论
为什么被折叠?



