【问题标题】:Trouble understanding C++ `virtual`无法理解 C++ `virtual`
【发布时间】:2009-12-01 21:21:42
【问题描述】:

我无法理解 C++ 中 virtual 关键字的用途。我非常了解 C 和 Java,但我是 C++ 新手

来自维基百科

在面向对象编程中,一个 虚函数或虚方法是 其行为的函数或方法 可以在继承中被覆盖 由具有相同功能的函数分类 签名。

但是我可以在不使用 virtual 关键字的情况下覆盖如下所示的方法

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : A { 
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    B b;
    cout << b.a() << endl;
    return 0;
}

//output: 2

如下所示,函数 A::a 被 B::a 成功覆盖,不需要 virtual

同样来自维基百科的关于虚拟析构函数的声明让我更加困惑

如下例所示, 这对于 C++ 基类很重要 有一个虚拟析构函数来确保 从最 派生类将始终被调用。

所以virtual 也告诉编译器调用父级的析构函数?这似乎与我最初对virtual 的理解大不相同,即“使函数可覆盖”

【问题讨论】:

    标签: c++ virtual-functions


    【解决方案1】:

    进行以下更改,您就会明白原因:

    #include <iostream>
    
    using namespace std;
    
    class A {
        public:
            int a();
    };
    
    int A::a() {
        return 1;   
    }
    
    class B : public A { // Notice public added here
        public:
            int a();
    };
    
    int B::a() {
        return 2;
    }
    
    int main() {
        A* b = new B(); // Notice we are using a base class pointer here
        cout << b->a() << endl; // This will print 1 instead of 2
        delete b; // Added delete to free b
        return 0;
    }
    

    现在,让它按您的预期工作:

    #include <iostream>
    
    using namespace std;
    
    class A {
        public:
            virtual int a(); // Notice virtual added here
    };
    
    int A::a() {
        return 1;   
    }
    
    class B : public A { // Notice public added here
        public:
            virtual int a(); // Notice virtual added here, but not necessary in C++
    };
    
    int B::a() {
        return 2;
    }
    
    int main() {
        A* b = new B(); // Notice we are using a base class pointer here
        cout << b->a() << endl; // This will print 2 as intended
        delete b; // Added delete to free b
        return 0;
    }
    

    关于虚拟析构函数的注释是完全正确的。在您的示例中,没有什么需要清理的,但是说 A 和 B 都有析构函数。如果它们没有被标记为虚拟,哪一个会被基类指针调用?提示:它的工作方式与未标记为虚拟时的 a() 方法完全相同。

    【讨论】:

    • 无需使用 new/delete 使示例复杂化:B obj; A* p = &amp;obj;A&amp; ref = obj;
    • True - 实际上我在第一次编辑时忽略了删除;但是 C++ 中的解除引用语义也很复杂...... ;-)
    【解决方案2】:

    你可以这样想。

    Java 中的所有函数都是虚函数。如果您有一个带有函数的类,并且您在派生类中重写了该函数,则无论您用来调用它的变量的声明类型如何,都会调用它。

    另一方面,在 C++ 中,它不一定会被调用。

    如果你有一个基类 Base 和一个派生类 Derived,并且它们都有一个名为 'foo' 的非虚函数,那么

    Base * base;
    Derived *derived;
    
    base->foo(); // calls Base::foo
    derived->foo(); // calls Derived::foo
    

    如果 foo 是虚拟的,那么两者都调用 Derived::foo。

    【讨论】:

      【解决方案3】:

      virtual 表示实际方法是根据实例化的类而不是您用来声明变量的类型来确定运行时的。 在您的情况下,这是一个静态覆盖,它将适用于为 B 类定义的方法,无论创建的对象的实际类型是什么

      【讨论】:

        【解决方案4】:

        所以 virtual 也告诉编译器调用父级的析构函数?这似乎与我最初对 virtual 的理解非常不同,即“使函数可覆盖”

        你原来的理解和新的理解都是错误的。

        • 方法(您称它们为函数)总是可重写。不管是虚拟的、纯粹的、非虚拟的还是什么。
        • 总是调用父析构函数。构造函数也是如此。

        “虚拟”只有在您通过指向基类的指针类型的指针调用方法时才会产生影响。由于在您的示例中您根本不使用指针,因此 virtual 根本没有任何区别。

        如果你使用了一个指向-A类型的变量a,即A* a;,你不仅可以给它赋值pointer-to-A类型的其他变量,还可以给它赋值pointer-to类型的变量-B,因为 B 派生自 A。

        A* a; 
        B* b;
        
        b = new B(); // create a object of type B. 
        a = b;       // this is valid code. a has still the type pointer-to-A, 
                     // but the value it holds is b, a pointer to a B object.
        
        a.a();       // now here is the difference. If a() is non-virtual, A::a()
                     // will be called, because a is of type pointer-to-A. 
                     // Whether the object it points to is of type A, B or
                     // something entirely different doesn't matter, what gets called
                     // is determined during compile time from the type of a.
        
        a.a();       // now if a() is virtual, B::a() will be called, the compiler
                     // looks during runtime at the value of a, sees that it points
                     // to a B object and uses B::a(). What gets called is determined
                     // from the type of the __value__ of a.
        

        【讨论】:

        • +1,您可能希望添加您的声明,即函数总是可覆盖。在 c++0x 中,关键字“final”应防止覆盖。
        【解决方案5】:

        如下所示,函数 A::a 被 B::a 成功覆盖,不需要 virtual

        它可能有效,也可能无效。在您的示例中它有效,但这是因为您直接创建和使用B 对象,而不是通过指向A 的指针。见C++ FAQ Lite, 20.3

        所以 virtual 也告诉编译器调用父级的析构函数?

        如果您删除指向派生类对象的基类指针,并希望基类和派生析构函数都运行,则需要虚拟析构函数。见C++ FAQ Lite, 20.7

        【讨论】:

          【解决方案6】:

          如果您使用基类指针作为consultutah(以及我输入时的其他人;)),您需要虚拟。

          缺少虚拟允许保存检查以了解它需要调用的方法(基类之一或某些派生类)。但是,此时不要担心表现,只关注正确的行为。

          虚拟析构函数特别重要,因为派生类可能会在堆上声明其他变量(即使用关键字“new”)并且您需要能够将其删除。

          然而,您可能会注意到,在 C++ 中,您往往比在 java 中使用更少的派生(您经常使用模板进行类似用途),甚至可能不需要打扰了。此外,如果您从未在堆上声明您的对象(“A a;”而不是“A * a = new A();”),那么您也不必担心它。当然,这在很大程度上取决于您开发什么/如何开发,以及您是否计划其他人将派生您的类。

          【讨论】:

          • 这不是关于分配对象的位置,而是关于通过指针和对基类的引用来使用它们,包括方法中隐含的“this”。
          【解决方案7】:

          试试 ((A*)&b).a() 看看会调用什么。

          virtual 关键字可以让您以抽象的方式(即通过基类指针)处理对象,但仍然可以调用后代代码...

          换句话说,虚拟关键字“让旧代码调用新代码”。您可能已经编写了对 A 进行操作的代码,但是通过虚函数,该代码可以调用 B 的较新的 a()。

          【讨论】:

            【解决方案8】:

            假设您实例化了 B,但将其作为 A 的实例保存:

            A *a = new B();
            

            并调用了函数 a(),其 a() 的实现将被调用?

            如果 a() 不是虚拟 A,将被调用。如果 a() 是虚拟的,则无论您如何持有它,都会调用 a() 的实例化子类版本。

            如果 B 的构造函数为数组或打开的文件分配了大量内存,则调用

            delete a;
            

            将确保调用 B 的析构函数,无论它是如何被持有的,无论是通过基类或接口还是其他方式。

            顺便问下好问题。

            【讨论】:

            • 糟糕。谢谢你。猜猜谁三年来只使用 VB.net 和 C#?
            【解决方案9】:

            我总是把它想象成棋子(我第一次尝试 OO)。

            棋盘上有指向所有棋子的指针。空方格是 NULL 指针。但它只知道每个指针指向一个棋子。董事会不需要知道更多信息。但是当棋子移动时,棋盘并不知道这是有效的棋步,因为每个棋子的移动方式都不同。所以棋盘需要检查棋子是否有效。

            Piece*    board[8][8];
            
            CheckMove(Point const& from,Point const& too)
            {
                Piece*  piece = board[from.x][from.y];
                if (piece != NULL)
                {
                    if (!piece->checkValidMove(from,too))
                    {    throw std::exception("Bad Move");
                    }
                    // Other checks.
                }
            }
            
            class Piece
            {
                virtual bool checkValidMove(Point const& from,Point const& too)  = 0;
            };
            
            class Queen: public Piece
            {
                virtual bool checkValidMove(Point const& from,Point const& too) 
                {
                     if (CheckHorizontalMove(from,too) || CheckVerticalMoce(from,too) || CheckDiagonalMove(from,too))
                     {
                         .....
                     }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2019-01-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-05-03
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多