我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
当前回答
我们需要支持“运行时多态性”的虚拟方法。当使用指针或对基类的引用引用派生类对象时,可以为该对象调用虚拟函数并执行派生类版本的函数。
其他回答
下面是一个完整的示例,说明了为什么使用虚拟方法。
#include <iostream>
using namespace std;
class Basic
{
public:
virtual void Test1()
{
cout << "Test1 from Basic." << endl;
}
virtual ~Basic(){};
};
class VariantA : public Basic
{
public:
void Test1()
{
cout << "Test1 from VariantA." << endl;
}
};
class VariantB : public Basic
{
public:
void Test1()
{
cout << "Test1 from VariantB." << endl;
}
};
int main()
{
Basic *object;
VariantA *vobjectA = new VariantA();
VariantB *vobjectB = new VariantB();
object=(Basic *) vobjectA;
object->Test1();
object=(Basic *) vobjectB;
object->Test1();
delete vobjectA;
delete vobjectB;
return 0;
}
您需要至少1个级别的继承和一个升级来演示它。下面是一个非常简单的示例:
class Animal
{
public:
// turn the following virtual modifier on/off to see what happens
//virtual
std::string Says() { return "?"; }
};
class Dog: public Animal
{
public: std::string Says() { return "Woof"; }
};
void test()
{
Dog* d = new Dog();
Animal* a = d; // refer to Dog instance with Animal pointer
std::cout << d->Says(); // always Woof
std::cout << a->Says(); // Woof or ?, depends on virtual
}
跟进@user6359267的回答,C++范围层次结构是
global -> namespace -> class -> local -> statement
因此,每个类都定义了一个范围。如果不是这样的话,子类中的重写函数实际上会在同一范围内重新定义函数,而链接器不允许这样做:
在每个翻译单元中使用之前必须声明函数,并且一个函数只能在整个程序(跨所有翻译单元)的给定范围内定义一次
由于每个类都定义了自己的作用域,因此被调用的函数是在调用该函数的对象的类中定义的函数。所以
#include <iostream>
#include <string>
class Parent
{
public:
std::string GetName() { return "Parent"; }
};
class Child : public Parent
{
public:
std:::string GetName() { return "Child"; }
};
int main()
{
Parent* parent = new Parent();
std::cout << parent->GetName() << std::endl;
Child* child = new Child();
std::cout << child->GetName() << std::endl;
*parent = child;
std::cout << child->GetName() << std::endl;
return 0;
}
输出
Parent
Child
Parent
因此,我们需要一种方法来告诉编译器应该在运行时而不是编译时确定要调用的函数。这就是虚拟关键字的作用。
这就是为什么函数重载被称为编译时多态(或早期绑定),而虚拟函数重写被称为运行时多态(或者后期绑定)。
细节:
在内部,当编译器看到一个虚拟函数时,它会创建一个类成员指针,该指针使用.*和->*运算符一般指向该类的成员(而不是对象中该成员的特定实例)。他们的工作是允许您访问一个类的成员,该成员具有指向该成员的指针。这些很少被程序员直接使用(也许除非你正在编写一个编译器来实现“虚拟”)。
我认为您所指的是这样一个事实:一旦方法被声明为virtual,您就不需要在重写中使用“virtual”关键字。
class Base { virtual void foo(); };
class Derived : Base
{
void foo(); // this is overriding Base::foo
};
如果在Base的foo声明中不使用“virtual”,那么Derived的foo将只是隐藏它。
您必须区分重写和重载。如果没有virtual关键字,则只能重载基类的方法。这只意味着隐藏。假设您有一个基类base和一个派生类Specialized,它们都实现了void foo()。现在有一个指向Base的指针指向Specialized的实例。当您对其调用foo()时,您可以观察到virtual的不同之处:如果该方法是虚拟的,则将使用Specialized的实现,如果缺少,则将选择Base的版本。最好不要重载基类中的方法。使方法非虚拟化是作者告诉你它在子类中的扩展不是有意的。