为什么使用基类指针来派生类

时间:2012-02-24 05:25:14

标签: c++ oop polymorphism virtual-functions base-class

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工程师,我想知道这一点。如果我们可以避免基类指针,为什么要使用虚函数?

4 个答案:

答案 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( &notq ); 
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)

您似乎已经提出两个问题(标题中和最后一个):

  1. 为什么要为派生类使用基类指针? 这是多态性的用法。它允许您统一处理对象,同时允许您具有特定的实现。如果这困扰你,那么我认为你应该问:为什么是多态?

  2. 如果我们可以避免基类指针,为什么要使用虚拟析构函数? 这里的问题是你不能总是避免使用基类指针来利用多态性的强度。