如果一个函数被定义为虚函数,它究竟意味着什么?它与纯虚函数是相同的吗?


当前回答

I'd like to comment on Wikipedia's definition of virtual, as repeated by several here. [At the time this answer was written,] Wikipedia defined a virtual method as one that can be overridden in subclasses. [Fortunately, Wikipedia has been edited since, and it now explains this correctly.] That is incorrect: any method, not just virtual ones, can be overridden in subclasses. What virtual does is to give you polymorphism, that is, the ability to select at run-time the most-derived override of a method.

考虑下面的代码:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

这个程序的输出是什么?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Derived重写Base的每个方法:不仅是虚方法,而且是非虚方法。

我们看到,当你有一个基类指向派生类的指针(bDerived)时,调用NonVirtual调用基类实现。这是在编译时解析的:编译器看到bDerived是一个Base*,而NonVirtual不是virtual,所以它在类Base上进行解析。

然而,调用Virtual调用派生类实现。由于关键字virtual,方法的选择发生在运行时,而不是编译时。这里在编译时发生的事情是,编译器看到这是一个Base*,并且它正在调用一个虚方法,所以它插入一个对虚表的调用,而不是类Base。此虚表在运行时实例化,因此运行时解析为最派生的覆盖。

我希望这不会让你太困惑。简而言之,任何方法都可以被覆盖,但只有虚方法可以提供多态性,即运行时选择派生最多的覆盖。然而,在实践中,覆盖非虚拟方法被认为是不好的实践,很少被使用,所以很多人(包括撰写维基百科文章的人)认为只有虚拟方法可以被覆盖。

其他回答

“Virtual”意味着该方法可以在子类中重写,但在基类中有一个可直接调用的实现。“纯虚拟”意味着它是一个没有直接可调用实现的虚拟方法。这样的方法必须在继承层次结构中至少重写一次——如果一个类有任何未实现的虚方法,该类的对象就不能被构造,编译就会失败。

@夸克指出,纯虚拟方法可以有一个实现,但由于纯虚拟方法必须被覆盖,所以不能直接调用默认的实现。下面是一个带默认值的纯虚方法的例子:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

根据注释,编译是否失败是特定于编译器的。至少在GCC 4.3.3中,它不会编译:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

输出:

$ g++ -c virt.cpp 
virt.cpp: In function ‘int main()’:
virt.cpp:8: error: cannot declare variable ‘a’ to be of abstract type ‘A’
virt.cpp:1: note:   because the following virtual functions are pure within ‘A’:
virt.cpp:3: note:   virtual void A::Hello()

来自维基百科的虚拟功能 ...

在面向对象编程中,在c++和Object Pascal等语言中,虚函数或虚方法是一种可继承和可覆盖的函数或方法,可以方便地进行动态分派。这个概念是面向对象编程(OOP)的(运行时)多态性部分的重要组成部分。简而言之,虚函数定义了要执行的目标函数,但在编译时可能不知道目标。

与非虚函数不同,当虚函数被重写时,派生最多的版本将用于类层次结构的所有级别,而不仅仅是创建它的级别。因此,如果基类的一个方法调用虚方法,将使用派生类中定义的版本,而不是基类中定义的版本。

这与非虚函数相反,非虚函数仍然可以在派生类中被重写,但“新”版本只会被派生类及其以下类使用,但根本不会改变基类的功能。

而. .

纯虚函数或纯虚方法是一种虚函数,如果派生类不是抽象的,则需要由派生类实现。

当纯虚方法存在时,类是“抽象的”,不能单独实例化。相反,必须使用实现纯虚方法的派生类。纯虚类根本没有在基类中定义,因此派生类必须定义它,否则派生类也是抽象的,不能实例化。只有没有抽象方法的类才能被实例化。

虚类提供了一种重写基类功能的方法,而纯虚类则需要这种方法。

Simula、c++和c#在默认情况下使用静态方法绑定,程序员可以通过将特定方法标记为虚拟来指定它们应该使用动态绑定。 动态方法绑定是面向对象编程的核心。

面向对象编程需要三个基本概念:封装、继承和动态方法绑定。

的实现细节 抽象要隐藏在背后 简单的接口。 继承允许将新的抽象定义为 某些的扩展或改进 现有的抽象,获得一些 或者说它的所有特征 自动。 动态方法绑定允许新抽象显示其新抽象 行为,即使在上下文中使用 这需要旧的抽象。

I'd like to comment on Wikipedia's definition of virtual, as repeated by several here. [At the time this answer was written,] Wikipedia defined a virtual method as one that can be overridden in subclasses. [Fortunately, Wikipedia has been edited since, and it now explains this correctly.] That is incorrect: any method, not just virtual ones, can be overridden in subclasses. What virtual does is to give you polymorphism, that is, the ability to select at run-time the most-derived override of a method.

考虑下面的代码:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

这个程序的输出是什么?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Derived重写Base的每个方法:不仅是虚方法,而且是非虚方法。

我们看到,当你有一个基类指向派生类的指针(bDerived)时,调用NonVirtual调用基类实现。这是在编译时解析的:编译器看到bDerived是一个Base*,而NonVirtual不是virtual,所以它在类Base上进行解析。

然而,调用Virtual调用派生类实现。由于关键字virtual,方法的选择发生在运行时,而不是编译时。这里在编译时发生的事情是,编译器看到这是一个Base*,并且它正在调用一个虚方法,所以它插入一个对虚表的调用,而不是类Base。此虚表在运行时实例化,因此运行时解析为最派生的覆盖。

我希望这不会让你太困惑。简而言之,任何方法都可以被覆盖,但只有虚方法可以提供多态性,即运行时选择派生最多的覆盖。然而,在实践中,覆盖非虚拟方法被认为是不好的实践,很少被使用,所以很多人(包括撰写维基百科文章的人)认为只有虚拟方法可以被覆盖。

虚函数是在基类中声明并由派生类重新定义的成员函数。虚函数是按继承顺序分层的。 当派生类不重写虚函数时,将使用在其基类中定义的函数。

纯虚函数是指相对于基类不包含任何定义的虚函数。 它在基类中没有实现。任何派生类都必须重写此函数。