我创建了一个简单函数:基类:动物叫声,派生类:羊叫,均命名为speak函数。然后观察调用:
//基类
class Animal
{
public:
void speak()
{
cout << "动物再说话" << endl;
}
};
//派生类:
class sheep :public Animal {
public:
void speak()
{
cout << "咩咩" << endl;
}
};
//地址早绑定了 在编译阶段确定了函数地址
// 如果想让猫说话,函数地址不能提前绑定,需要在运行阶段再绑定。
void dospeak(Animal &Animal)
{
Animal.speak();
}
void test01()
{
sheep s1;
dospeak(s1);
}
int main()
{
test01();
system ("pause");
return 0;
}
首先阅读这段代码:基类和派生类都有一个speak函数,我想调用这个Speak函数,那么它到底会生成哪个函数呢?
这段代码输出结果一定是:“动物再说话”,但是我们的本意写的代码dospeak(s1),其实是想让羊再叫。 那么我们如果给代码 基类中的speak函数 前面加一个virtural,构成虚函数呢,那么你运行就发现是:“咩咩了”!
这就是虚函数和普通函数的区别:
你可以理解为:
你的比喻可以调整为:
-
基类虚函数表是“壳子”:基类提供了一个“框架”(虚函数表),其中填充了默认的函数地址(如
Animal::speak)。 -
派生类重写是“替换壳子内容”:派生类继承这个“壳子”,但用自己的函数地址替换其中的条目(如
sheep::speak)。 -
调用时“按壳子找内容”:通过基类指针/引用调用时,实际是根据对象实际类型的“壳子”(虚函数表)找到正确的函数
-
静态函数:
-
编译阶段确定函数地址:编译器在编译时根据指针/引用的类型(
Animal&)直接绑定到Animal::speak()。 -
派生类函数会被隐藏:即使
sheep类定义了同名函数speak(),它只是隐藏了基类的函数,而不是覆盖。 -
调用结果:通过
Animal&调用时,始终执行基类的speak()。 -
Virtual后:
-
虚函数表(vtable):编译器会为每个类生成虚函数表,记录虚函数的实际地址。
-
运行时动态绑定:通过基类指针/引用调用虚函数时,会根据对象的实际类型(
sheep)查找虚函数表,调用正确的派生类函数。 -
调用结果:通过
Animal&调用时,实际执行的是sheep::speak()。
2232

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



