首先,关于虚函数和非虚函数之间的区别:
您可以在编译或链接期间解析代码中的每个非虚函数调用.
通过解析,我们的意思是函数的地址可以由编译器或链接器计算.
因此,在创建的目标代码中,函数调用可以替换为操作码,以便跳转到内存中该函数的地址.
使用虚函数,您可以调用只能在运行时解析的函数.
我们不是解释它,而是通过一个简单的场景:
class Animal
{
virtual void Eat(int amount) = 0;
};
class Lion : public Animal
{
virtual void Eat(int amount) { ... }
};
class Tiger : public Animal
{
virtual void Eat(int amount) { ... }
};
class Tigon : public Animal
{
virtual void Eat(int amount) { ... }
};
class Liger : public Animal
{
virtual void Eat(int amount) { ... }
};
void Safari(Animal* animals[], int numOfAnimals, int amount)
{
for (int i=0; i<numOfAnimals; i++)
animals[i]->Eat(amount);
// A different function may execute at each iteration
}
Run Code Online (Sandbox Code Playgroud)
您可能已经理解,该Safari
功能可以让您灵活地喂养不同的动物.
但由于每个动物的确切类型直到运行时才知道,因此Eat
要调用的确切函数也是如此.
类的构造函数不能是虚拟的,因为:
调用对象的虚函数是通过对象类的V-Table执行的.
每个对象都包含一个指向其类的V-Table的指针,但只有在创建对象时才会在运行时初始化此指针.
换句话说,只有在调用构造函数时才会初始化此指针,因此构造函数本身不能是虚拟的.
除此之外,构造函数首先不是虚拟的.
虚函数背后的想法是,您可以在不知道调用它们的对象的确切类型的情况下调用它们.
当您创建对象时(即,当您隐式调用构造函数时),您确切地知道要创建的对象类型,因此您不需要此机制.
基类的析构函数必须是虚拟的,因为:
当您静态分配其类继承自基类的对象时,则在函数的末尾(如果对象是本地的)或程序(如果对象是全局的),将自动调用类的析构函数,并且反过来,调用基类的析构函数.
在这种情况下,析构函数是虚拟的这一事实没有意义.
另一方面,当您动态分配(new
)一个类继承自基类的对象时,您需要delete
在程序执行的稍后时刻动态解除分配()它.
在delete
操作者需要一个指针的对象,其中指针的类型可以是基类本身.
在这种情况下,如果析构函数是虚拟的,那么delete
运算符将调用类的析构函数,而析构函数又调用基类的析构函数.
但是如果析构函数不是虚拟的,那么delete
运算符将调用基类的析构函数,并且永远不会调用实际类的析构函数.
请考虑以下示例:
class A
{
A() {...}
~A() {...}
};
class B: public A
{
B() {...}
~B() {...}
};
void func()
{
A* b = new B(); // must invoke the destructor of class 'B' at some later point
...
delete b; // the destructor of class 'B' is never invoked
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1409 次 |
最近记录: |