如果在编译时间中创建虚拟表,那么为什么我们将其称为运行时间多态性
If virtual table is created in compile time, then why do we call this as a run time polymorphism?
作为编译时间创建虚拟表,为什么我们将其称为C 中的运行时间多态性?
在典型的实现中,每个类都有一个虚拟表,该表在编译时已知。
在运行时,BaseClass *
类型的指针可能指向一个类型为BaseClass
的对象,或者指向其类型为DerivedClass
的对象的基类子对象,其中BaseClass
是DerivedClass
的基础。引用也是如此。
在前一种情况下,在BaseClass
的VTable中抬高了一个虚拟呼叫。在后一种情况下,在DerivedClass
的VTable中抬高了一个虚拟呼叫。由于呼叫站点直到在运行时实际执行呼叫之前,呼叫站点才"知道"哪个函数,这称为动态或运行时多态性。
再次在典型的实现中,它发现使用哪种VTable的方式是,具有一个或多个虚拟函数类型的对象包含一个"隐藏"的附加字段,该字段指向其完整类型的可vtable。那是简单的继承。多重和虚拟继承增加并发症,但原理是相同的,该对象为应使用的任何VTable提供了指针。
将此与非虚拟呼叫进行比较,在此中,合并不需要使用任何VTable或知道完整对象的类型。它根据指针或参考的类型选择功能。
虚拟表是无关紧要的。C 中的运行时多态性表示:
struct B {
virtual void f() { std::cout << "In Bn"; }
};
struct D1 : B {
virtual void f() { std::cout << "In D1n"; }
};
struct D2 : b {
virtual void f() { std::cout << "In D2n"; }
};
B *bp = new B;
bp->f(); // calls B::f
B *bp1 = new D1;
bp1->f(); // calls D1::f
B *bp2 = new D2;
bp2->f(); // calls D2::f
即使所有三个指针都具有B*
类型,对f()
的呼叫的行为取决于指针指向的对象的运行时类型。
每个对象都包含一个指向其动态类型的正确表的指针。该指针在创建对象时在运行时初始化,并在运行时使用以选择要调用的正确虚拟函数。这就是为什么它称为运行时多态性。
虚拟表是在编译期间创建但在运行时使用的。
虚拟表是C 类型和其他一些OO语言的类型的运行时表示的元素。它用于动态调度虚拟方法调用。换句话说,这是C 动态多态性特征的实现细节。
构造该表的时间与调度方案无关,该计划定义了多态性是静态的还是动态的。
您必须必须简要了解什么是运行时多态性以及其工作原理。
如果您有这样的类层次结构:
class Animal
{
...
virtual void sayHello() {
cout << "hello" << endl;
}
};
class Dog : public Animal
{
...
/*virtual*/ void sayHello() {
cout << "woof!" << endl;
}
};
并在Dog
实例上调用方法,您希望能够调用Dog
的(虚拟)覆盖方法,而不是Animal
。我想你知道我们为什么想要这个,所以我不想进一步解释。
但是,在运行时我们怎么知道Animal*
实际上是Dog*
?问题是,我们不必知道是什么,我们只需要知道要调用哪些功能,或者更好地说,哪个功能 pointer pointer /em>致电。虚拟函数的所有功能指针都存储在每个类的虚拟表中。您可以将其想象为"指南",以拨打哪个代码,以实现哪种虚拟函数,这取决于。
此虚拟表是在编译时间内创建的(编译器将"指南"写入可执行文件中),每个实例指向一个可用的虚拟表之一。因此,如果您说Dog *dog = new Dog
,则指向狗的虚拟表。诸如dog->sayHello()
之类的调用将尚未指定的类的虚拟呼叫汇编为虚拟函数sayHello
...
然后,在运行时 dog->sayHello()
之类的调用将首先查找存储在对象中的混凝土虚拟表(代码不知道它是狗,只有它是动物)并找到方法Dog::sayHello()
的功能指针。
要回答您的问题,此机制称为运行时多态性,因为我们可以调用过载该指针的方法on)虽然在运行时做出决定。您可以调用对话编译时间多态性编译器可以知道混凝土类型的对象,例如Dog dog; dog.sayHello()
中的对象类型。
虽然在编译时创建V-table时,当编译以下代码时,编译器不知道将调用哪个函数:
struct A {
virtual void f() { cout << "A::f" << endl;}
};
struct B : public A {
void f() { cout << "B::f" << endl;}
};
int main() {
A* b = new B();
b->f(); // prints "B::f", chosen at runtime
}
so,尽管对象在编译时间和运行时间之间不变,但方法B :: F仅在运行时选择,因为编译器不知道对象的动态类型(哪个确定要调用的方法)。
- 为什么我们将单个或多维数组的大小声明为常量值?
- 为什么我们将 [ ] 与指向数组的指针一起使用?
- 如何输入数组的元素,每次获得新元素时,我们将其放在数组的中间?
- 为什么我们不能将使用异或运算找到的整数转换为字符?
- 链表指针赋值为什么我们不能直接将尾巴分配给 temp 而不是尾巴>尾巴下一个
- 为什么我们不允许将纯引用参数传递给 std::thread,但允许传递原始指针?
- 为什么我们在这里将 0 添加到双空指针上?
- 为什么即使我们将指针分配给 NULL,指针的指向对象的大小也不为零
- 为什么我们将数组称为顺序容器?
- 在哪个文件中,我们将非成员函数放在C 中
- Windows编程:为什么我们要将lParam转换为CREATESTRUCT来获取应用程序状态
- 为什么在对自定义对象的向量进行分类时,我们将在struct的定义中包括比较函数
- 在哪些条件下,我们将指针作为函数参数
- 如果在编译时间中创建虚拟表,那么为什么我们将其称为运行时间多态性
- 当我们将数据从 ax 移动到端口地址时会发生什么
- 如何创建一个模板类,该类具有两个变量,我们将从用户那里获取输入,并且输入值可以是任何类型的
- 当我们将char强制转换为大于256的数字时会发生什么
- 如果我们将 std::cout 应用于指向成员的指针,我们将获得什么值
- 为什么我们将传递的引用值视为地址
- 当我们将一个结构指针分配给另一个指针时,为什么我们不能直接使用分配的指针来访问数据?