在功能面前使用虚拟词保留字的目的是什么?如果我希望子类覆盖父函数,我只需声明相同的函数,例如void draw(){}
。
class Parent {
public:
void say() {
std::cout << "1";
}
};
class Child : public Parent {
public:
void say()
{
std::cout << "2";
}
};
int main()
{
Child* a = new Child();
a->say();
return 0;
}
输出为2.
再说一遍,为什么在virtual
的标题中需要保留字say()
?
非常感谢。
答案 0 :(得分:37)
如果该功能是虚拟的,那么你可以这样做并仍然得到输出“2”:
Parent* a = new Child();
a->say();
这是有效的,因为virtual
函数使用实际类型,而非虚函数使用声明的类型。阅读polymorphism,以便更好地讨论您为什么要这样做。
答案 1 :(得分:25)
尝试使用:
Parent *a = new Child();
Parent *b = new Parent();
a->say();
b->say();
没有virtual
,两者都打印'1'。添加虚拟,即使它通过指向Parent
的指针引用,孩子也会像孩子一样行事。
答案 2 :(得分:18)
这是我认为多态性如何工作的经典问题。主要思想是您要抽象每个对象的特定类型。换句话说:你希望能够在不知道它是孩子的情况下调用Child实例!
这是一个例子: 假设您有类“Child”和类“Child2”和“Child3”,您希望能够通过它们的基类(Parent)引用它们。
Parent* parents[3];
parents[0] = new Child();
parents[1] = new Child2();
parents[2] = new Child3();
for (int i=0; i<3; ++i)
parents[i]->say();
你可以想象,这是非常强大的。它允许您根据需要多次扩展Parent,并且使用Parent指针的函数仍然可以工作。为了使其像其他人一样提及,您需要将该方法声明为虚拟。
答案 3 :(得分:15)
如果不使用virtual
关键字,则不要覆盖,但是rahter在派生类中定义一个隐藏基类方法的无关方法。也就是说,如果没有virtual
,则Base::say
和Derived::say
无关 - 除了名称巧合。
当您使用virtual关键字(在基类中需要,在派生类中是可选的)时,您告诉编译器从此基础派生的类将能够覆盖该方法。在这种情况下,Base::say
和Derived::say
被视为同一方法的覆盖。
当您使用引用或指向基类的指针来调用虚方法时,编译器将添加适当的代码,以便调用最终覆盖(在最派生类中的覆盖在使用中的具体实例的层次结构中定义方法。请注意,如果您不使用引用/指针而是使用局部变量,则编译器可以解析该调用,并且不需要使用虚拟调度机制。
答案 4 :(得分:12)
我自己测试过,因为我们可以考虑很多事情:
#include <iostream>
using namespace std;
class A
{
public:
virtual void v() { cout << "A virtual" << endl; }
void f() { cout << "A plain" << endl; }
};
class B : public A
{
public:
virtual void v() { cout << "B virtual" << endl; }
void f() { cout << "B plain" << endl; }
};
class C : public B
{
public:
virtual void v() { cout << "C virtual" << endl; }
void f() { cout << "C plain" << endl; }
};
int main()
{
A * a = new C;
a->f();
a->v();
((B*)a)->f();
((B*)a)->v();
}
输出:
A plain
C virtual
B plain
C virtual
我认为一个好的,简单的和简短的答案可能看起来像这样(因为我认为能够理解更多的人可以记住更少,因此需要简短而简单的解释):
虚方法检查指针指向的实例的DATA,而经典方法因此不调用与指定类型相对应的方法。
该功能的要点如下:假设你有一个A的数组。该数组可以包含B,C,(甚至派生类型)。如果你想顺序调用所有这些实例的相同方法,你可以调用你重载的每个实例。
我发现这很难理解,显然任何C ++课程都应该解释如何实现这一点,因为大多数时候你刚刚学习了虚函数,你使用它们,但直到你理解编译器如何理解它们和可执行文件如何处理调用,你处于黑暗中。
关于VFtables的事情是,我从未解释过它添加了什么样的代码,而且显然这里C ++需要比C更多的经验,这可能是C ++被标记为“慢”的主要原因。早期:事实上,它很强大,但就像一切一样,如果你知道如何使用它会很强大,否则你只是“把你的整条腿吹掉”。
答案 5 :(得分:2)
使用关键字virtual时,会创建一个虚函数表来定位实例中的正确方法。然后,即使派生实例由基类指针指向,它仍然会找到该方法的正确实现。
答案 6 :(得分:0)
假设我们有两个类,如下所示:-
class Fruit {
protected:
int sweetness;
char* colour;
//...
public:
void printSweetness() const {
cout<<"Sweetness : "<<sweetness<<"\n";
return;
}
void printColour() const {
cout<<"Colour : "<<colour<<"\n";
return;
}
virtual void printInfo() const {
printSweetness();
printColour();
return;
}
};
class Apple : public Fruit {
private:
char* genus;
//...
public:
Apple() {
genus = "Malus";
}
void printInfo() const {
Fruit::printInfo();
cout<<"Genus : "<<genus<<"\n";
return;
}
};
现在假设我们具有以下功能……
void f() {
Fruit* fruitList[100];
for(int i = 0; i<100 ; i++) {
fruitList[i]->printInfo();
}
return;
}
在上述情况下,我们可以调用相同的函数,并依赖动态分配机制及其提供的抽象,而无需知道该数组中存储了哪种水果。这极大地简化了代码并提高了可读性。而且比使用类型字段要好得多,这会使代码变得丑陋!
在重写方法中,我们必须知道我们正在处理哪种对象,否则将面临可能导致意外结果的对象切片问题。
注意 -我写这个答案只是为了明确显示其好处。
答案 7 :(得分:-1)
这是c ++编程的一个非常重要的方面 - 几乎我去过的每次采访都会被问到这个问题。
如果您将主要内容更改为:
会发生什么int main() { Parent* a = new Child(); a->say(); return 0; }
此外,值得了解vtable是什么。