成员函数一般化三个结论
#include <iostream>
using std::cout;
using std::endl;
class Employee
{
public:
void Work() { cout << "Employee work" << endl; }
};
class Dev : public Employee
{
public:
void Work() { cout << "Development work" << endl; }
};
int main()
{
Employee *pEmployee = NULL;
Dev *pDev = NULL;
Dev dev;
pEmployee = &dev;
pEmployee->Work();
pDev = &dev;
pDev->Work();
return 0;
}
1. 如果以“基类指针”指向“派生类对象”,那么经由该指针只能调用基类所定义的函数
2. 如果以“派生类指针”指向“基类对象”,必须先做明显的转换操作。这种做法很危险,
不符合真实生活经验,在程序设计上也会带给程序员困惑
3. 如果基类和派生类都定义了“相同名称的成员函数”,那么经由对象指针调用成员函数时,
到底调用哪一个函数,必须视该指针的原始类型而定,而不是视指针所指的对象的类型而定
基类的指针指向派生类的对象
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "A" << endl; }
virtual ~A() { cout << "~A" << endl; }
};
class B: public A
{
public:
B() { cout << "B" << endl; }
~B() {cout << "~B" << endl; }
};
int main()
{
B *pDerive = new B;
A *pBase = pDerive;
cout << pDerive << " " << pBase << endl;
delete pBase;
pBase = NULL;
return 0;
}
output:
A
B
0xc1e010 0xc1e010
~B
~A
由于类A的析构函数是虚函数,所以其对象的存储空间中包含了虚表;
类B继承了类A,所以类B的析构函数也变成了虚析构函数,类B对象的
存储空间中也包含了虚表;根据多态的原则,用基类的指针删除派生类对象,
实际上是通过虚指针(vptr)找到虚表(vtable),然后在找到虚函数的地址。
成员函数的虚拟化
虚函数是为了对“如果以基类指针指向派生类对象,那么通过该指针就只能调用基类所定义的成员函数”
这条规则反其道而行的设计。多态就是以单一指令调用不同函数。
为了达到动态绑定的目的,c++编译器通过某个表格,在执行期“间接”调用实际上欲绑定的函数。
这样的表格称为虚函数表(vtable)。每一个“内含虚函数的类”,编译器都会为它做出一个虚函数表,
表中的每一个元素都指向一个虚函数的地址。此外,编译器当然也会为类加上一项成员变量,
是一个指向该虚函数表的指针(vptr)。
Object slicing 与虚函数
#include <iostream>
using std::cout;
using std::endl;
class Junior
{
public:
virtual void level() { cout << "level B" << endl; }
};
class Senior : public Junior
{
public:
void show()
{
cout << "show senior level" << endl;
level();
}
virtual void level() { cout << "level C" << endl; }
};
class Princeple : public Senior
{
public:
void show()
{
cout << "show princeple level" << endl;
level();
}
virtual void level() { cout << "level D" << endl; }
};
int main()
{
Princeple prin;
cout << "test 1" << endl;
prin.show();
cout << "test 2" << endl;
((Senior *)(&prin))->show();
cout << "test 3" << endl;
Princeple *pPrin = new Princeple;
pPrin->show();
cout << "test 4" << endl;
((Senior)prin).show();
delete pPrin;
pPrin = NULL;
return 0;
}
由于向上强制转型, (Senior)prin, 将会造成对象的内容被切割(object slicing),当调用
((Senior)prin).show(); prin已经是一个被切割得只剩下半条命的对象,
由于((Senior)prin).show()是传值而非传址操作,编译器以所谓的拷贝构造函数把Senior对象的内容复制了一份,
使得prin的vtable内容与Senior对象的vtable相同。