class base{
.....
virtual void function1();
virtual void function2();
};
class derived::public base{
int function1();
int function2();
};
int main()
{
derived d;
base *b = &d;
int k = b->function1() // Why use this instead of the following line?
int k = d.function1(); // With this, the need for virtual functions is gone, right?
}
我不是CompSci工程师,我想知道这一点。如果我们可以避免基类指针,为什么要使用虚函数?
答案 0 :(得分:53)
在你的简单例子中,多态性的力量并不明显,但如果你稍微扩展它可能会变得更加清晰。
class vehicle{
.....
virtual int getEmission();
}
class car : public vehicle{
int getEmission();
}
class bus : public vehicle{
int getEmission();
}
int main()
{
car a;
car b;
car c;
bus d;
bus e;
vehicle *traffic[]={&a,&b,&c,&d,&e};
int totalEmission=0;
for(int i=0;i<5;i++)
{
totalEmission+=traffic[i]->getEmission();
}
}
这使您可以遍历指针列表,并根据基础类型调用不同的方法。基本上,它允许您编写代码,在编译时不需要知道子类型是什么,但代码将执行正确的函数。
答案 1 :(得分:4)
你是对的,如果你有一个对象,你不需要通过指针来引用它。当对象将按其创建类型销毁时,您也不需要虚拟析构函数。
当您从另一段代码中获取指向对象的指针时,该实用程序就会出现,而您实际上并不知道派生类型最多的是什么。您可以在同一个基础上构建两个或多个派生类型,并具有一个返回指向基类型的指针的函数。虚函数将允许您使用指针而不必担心您正在使用哪种派生类型,直到销毁对象为止。虚拟析构函数将在不知道它对应的派生类的情况下销毁该对象。
以下是使用虚函数的最简单示例:
base *b = new derived;
b->function1();
delete b;
答案 2 :(得分:1)
它实现多态性。除非你有基类指针 指向派生对象,你不能在这里有多态。
派生类的一个关键特性是指向a的指针 派生类与指向其基类的指针类型兼容。 多态性是利用这种简单的艺术 强大而通用的功能,带来面向对象 充分发挥其潜力的方法论。
在C ++中,存在一个特殊的类型/子类型关系,其中有一个基础 类指针或引用可以解决它的任何派生类 没有程序员干预的子类型。这种操纵能力 带有指针或对基类的引用的多个类型是 被称为多态性。
子类型多态允许我们编写应用程序的内核 独立于我们希望操纵的各种类型。相反,我们 编程抽象基类的公共接口 通过基类指针和引用。在运行时,实际 被引用的类型被解析并且是适当的实例 调用公共接口。运行时的分辨率 适当的调用函数称为动态绑定(默认情况下, 函数在编译时静态解析。在C ++中,动态 通过称为类虚拟的机制来支持绑定 功能。子类型多态性通过继承和动态 绑定为面向对象编程提供了基础
继承层次结构的主要好处是我们可以编程 到抽象基类的公共接口而不是 以这种方式形成其继承层次结构的各个类型 屏蔽我们的代码以防止该层次结构的变化我们定义eval(), 例如,作为抽象Query基础的公共虚函数 类。通过编写代码等
_rop->eval();
用户代码不受查询语言的多样性和易变性的影响。这不仅允许添加,修改, 或者删除类型而不需要更改用户程序,但是 释放新查询类型的提供者不必重新编码行为 或层次结构本身中所有类型共有的操作。这是 由两个特殊的继承特性支持:多态性 和动态绑定。当我们谈到C ++中的多态时,我们 主要指指针或基类引用的能力 解决它的任何派生类。例如,如果我们定义一个 非成员函数eval()如下,// pquery可以解决任何问题 从Query派生的类void eval( const Query *pquery ) { pquery->eval(); }
我们可以合法地调用它,传入任何一个对象的地址 四种查询类型:
int main()
{
AndQuery aq;
NotQuery notq;
OrQuery *oq = new OrQuery;
NameQuery nq( "Botticelli" ); // ok: each is derived from Query
// compiler converts to base class automatically
eval( &aq );
eval( ¬q );
eval( oq );
eval( &nq );
}
而尝试使用不是从Query派生的对象的地址来调用eval() 导致编译时错误:
int main()
{ string name("Scooby-Doo" ); // error: string is not derived from Query
eval( &name);
}
在eval()中,执行pquery-&gt; eval();必须调用 适当的eval()基于实际类的虚拟成员函数 对象pquery地址。在前面的示例中,依次为pquery 解决AndQuery对象,NotQuery对象,OrQuery对象, 和一个NameQuery对象。在执行期间的每个调用点 我们的程序,pquery解决的实际类类型是 确定,并调用适当的eval()实例。动态 绑定是实现这一目标的机制。 在面向对象的范例中,程序员操纵绑定但无限类型的未知实例。 (一套 types由其继承层次结构绑定。然而,在理论上,那里 对该层次结构的深度和广度没有限制。)在C ++中 是通过基类操纵对象来实现的 仅指针和引用。在基于对象的范例中, 程序员 操作在编译点完全定义的固定单数类型的实例。虽然 对象的多态操作需要对象 通过指针或引用访问,操纵 C ++中的指针或引用本身并不一定会产生 在多态性。例如,考虑
// no polymorphism
int *pi;
// no language-supported polymorphism
void *pvi;
// ok: pquery may address any Query derivation
Query *pquery;
在C ++中,多态性 仅存在于各个类层次结构中。指针类型 void *可以描述为多态,但它们没有明确的 语言支持 - 也就是说,它们必须由程序员管理 通过明确的演员和某种形式的判断力来追踪 正在处理的实际类型。
答案 3 :(得分:0)
您似乎已经提出两个问题(标题中和最后一个):
为什么要为派生类使用基类指针? 这是多态性的用法。它允许您统一处理对象,同时允许您具有特定的实现。如果这困扰你,那么我认为你应该问:为什么是多态?
如果我们可以避免基类指针,为什么要使用虚拟析构函数? 这里的问题是你不能总是避免使用基类指针来利用多态性的强度。