【问题标题】:Confused about Virtual Functions C++对虚函数 C++ 感到困惑
【发布时间】:2011-07-30 04:54:51
【问题描述】:

我是一个 c++ n00b,我不确定我是否找对了地方,但我对此感到困惑:

include <iostream>

using namespace std;

class Enemy
{
    public:
        void sayHere()
        {
            cout<<"Here"<<endl;
        }
        virtual void attack()
        {
        }
};

class Monster: public Enemy
{

    public:
        virtual void attack()
        {
            cout<<"RAWR"<<endl;
        }

};

class Ninja: public Enemy
{

    public:
        virtual void attack()
        {

            cout<<"Hiya!"<<endl;
        }
};

int main()
{
    Ninja n;
    Monster m;
    Enemy enemies[2];
    enemies[0] = m;
    enemies[1] = n;

    for(int i = 0; i<2; i++)
    {
        enemies[i].sayHere();
        enemies[i].attack();//Will not be overriden
    }
    return 0;
}

我的问题是为什么 Monster 或 Ninja 类中的 attack() 函数没有被覆盖?任何帮助,甚至是链接,都将不胜感激。

【问题讨论】:

  • public Enemy?我一定是产生了幻觉……
  • 记录一下,这种错误叫做“切片”:en.wikipedia.org/wiki/Object_slicing。基本上,当你写敌人[0]=m;你试图将一个太大的对象(m,它是派生的)卡到一个太小的内存位置(enemies[0],它是基础)。 C++ 对对象进行切片(“剪切”)并仅将其第一部分放在那里,即...基类。基本上,您只是将基类复制到数组中,并且数组中的所有对象都是 Enemy 类型。
  • 过失,已经被告知。

标签: c++ function virtual


【解决方案1】:

只要做:

Enemy* n = new Ninja();
Enemy* m = new Monster();

n->sayHere();
n->attack();
m->sayHere();
m->attack();

delete n;
delete m;

应该如你所愿。您需要使用指针才能使其工作。原因在于动态绑定的工作方式。

只要程序声明了 C++ 虚函数,就会为该类构造一个 v-table。 v-table 由指向类的虚函数的地址和指向派生类的每个对象的函数的指针组成。每当对 c++ 虚函数进行函数调用时,都会使用 v-table 解析到函数地址。这就是动态绑定在虚函数调用期间发生的方式。

这个想法是,编译器根据对象的内存地址存储指向每个方法的指针。它需要指针来访问表并调用适当的函数指针。想想如果你想写一个面向对象版本的C,你会如何提供继承和多态这样的机制?当你这样想时,它是有道理的。

我刚刚读到您正在从 JAVA 迁移过来。在 JAVA 中,您的大部分对象都存储在堆上。这一切都只是暗示。

JAVA 的

Enemy n = new Ninja();
n.attack();

大致相当于

Enemy* n = new Ninja();
n->attack();

在哪里。 JAVA 中的运算符更像是 c++ 中的 -> 运算符。在这两种情况下,n 都在堆上。 Java 只是对你隐藏了所有的指针和内存管理的东西。这就是为什么您可能对 JAVA 和 C# 中动态绑定的工作方式一无所知。

【讨论】:

    【解决方案2】:

    这与您无法通过指针访问敌人有关:

        Ninja n;
        Monster m;
    
        Enemy *enemies[2];
    
        enemies[0] = &m;
        enemies[1] = &n;
    
        for (int i = 0; i < 2; i++)
        {
            enemies[i]->sayHere();
            enemies[i]->attack();
        }
        return 0;
    

    【讨论】:

    • 我同时在看电视。 ;-)
    • 关闭但不太正确。这不是对象是否在堆或堆栈上,而是如果您使用指针或对该对象的引用。只有使用指针或引用才能获得多态行为(即虚函数)。
    • 问题是“为什么 Monster 或 Ninja 类中的 attack() 函数没有被覆盖?”
    • 谢谢,这行得通,但我对堆是什么感到困惑。我来自 java,这对我来说是全新的。
    • 我会将 n 和 m 存储为对象。然后将指向对象的指针存储在敌人数组中。您不需要动态分配它们,事实上我不想这样做,除非您使用拥有指针的容器(如 boost::ptr_vector)。
    【解决方案3】:

    除非您通过指针或引用访问对象,否则虚函数调用将不起作用。为了让它工作,你需要将你的敌人数组重写为

    Enemy *enemies[2];
    enemies[0] = &m;
    enemies[1] = &n;
    

    请注意,您必须将所有 enemies[i]. 更改为 enemies[i]-&gt;

    【讨论】:

      【解决方案4】:
      Enemy enemies[2];   
      enemies[0] = m;    
      enemies[1] = n;
      

      这只是对象切片 - 只会复制派生对象中的 Enemy 对象。虚函数无法发挥作用。

      【讨论】:

        【解决方案5】:

        这种现象被称为object slicing

        【讨论】:

        • 确实如此。但是,如果您阅读了所有有关对象切片的文章,似乎都没有提到为什么不允许您以多态方式调用虚函数。我知道为什么,但是链接到的解释会很好。
        • 如果一个对象被切片,那么你当然不能多态地调用虚函数,因为你没有子类的实例。这就是“切片”的意思。任何真正理解和思考它的人都应该立即明白这一点。
        • 你我都知道,但我不认为这很明显。
        猜你喜欢
        • 2021-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多