C++ 为什么要使用虚拟函数?

C++ 为什么要使用虚拟函数?,c++,virtual-functions,C++,Virtual Functions,可能重复: 我有一个关于C++虚函数的问题。 为什么以及何时使用虚拟函数?有人能给我一个虚拟函数的实时实现或使用吗?你会使用虚拟函数来实现“多态性”,特别是当你有一个对象,不知道实际的底层类型是什么,但知道你想对它执行什么操作,以及这个操作的实现(它是如何实现的)根据您实际拥有的类型而有所不同 基本上就是通常所说的“利斯科夫替代原则”,以1983年左右谈到这一点的芭芭拉·利斯科夫的名字命名 如果您需要使用动态运行时决策,在调用函数的代码被调用时,您不知道什么类型可以通过它,无论是现在还是将来,

可能重复:

我有一个关于C++虚函数的问题。
为什么以及何时使用虚拟函数?有人能给我一个虚拟函数的实时实现或使用吗?

你会使用虚拟函数来实现“多态性”,特别是当你有一个对象,不知道实际的底层类型是什么,但知道你想对它执行什么操作,以及这个操作的实现(它是如何实现的)根据您实际拥有的类型而有所不同

基本上就是通常所说的“利斯科夫替代原则”,以1983年左右谈到这一点的芭芭拉·利斯科夫的名字命名

如果您需要使用动态运行时决策,在调用函数的代码被调用时,您不知道什么类型可以通过它,无论是现在还是将来,这是一个很好的使用模型


但这不是唯一的办法。有各种各样的“回调”可以接收“blob”数据,您可能有依赖于传入数据中的头块的回调表,例如消息处理器。因此,不需要使用虚拟函数,事实上,您可能会使用的是一种仅使用一个条目实现v-table的方式(例如,一个类只有一个虚拟函数)。

当您想要覆盖某个行为(读取方法)时,您使用虚拟函数对于派生类,而不是为基类实现的类,并且您希望在运行时通过指向基类的指针执行此操作

经典的例子是当您有一个名为
Shape
的基类以及从中派生的具体形状(类)时。每个具体类重写(实现一个名为
Draw()
的虚拟方法)

类层次结构如下所示:

下面的代码片段显示了该示例的用法;它创建一个
Shape
类指针数组,其中每个指针指向一个不同的派生类对象。在运行时,调用
Draw()
方法将导致调用该派生类覆盖的方法,并绘制(或呈现)特定的
形状

Shape*basep[]={&line\u obj和tri\u obj,
&rect_obj和cir_obj};
对于(i=0;iDraw();
上面的程序只使用指向基类的指针来存储派生类对象的地址。这提供了松散耦合,因为如果随时添加新的
shape
具体派生类,则程序不必急剧更改。原因是实际使用(取决于)混凝土
形状
类型的代码段最少


以上是著名设计原则的一个很好的例子。

想想动物类,从中衍生出猫、狗和牛。动物类有一个

virtual void SaySomething()
{
    cout << "Something";
}
狗应该说“吠”,猫应该说“喵”,而不是打印“某物”。在本例中,您看到a是一只狗,但有时您有一个动物指针,不知道它是哪种动物。你不想知道它是哪种动物,你只想让它说些什么。所以你只需要调用虚拟函数,猫会说“喵”,狗会说“吠”


当然,SaySomething函数应该是纯虚拟的,以避免可能的错误。

当您需要以相同的方式处理不同的对象时,可以使用虚拟函数。这叫做多态性。让我们假设您有一些基类-类似于经典形状:

    class Shape
    {
        public:
           virtual void draw() = 0;
           virtual ~Shape() {}
    };

    class Rectange: public Shape
    {
        public:
            void draw() { // draw rectangle here } 
    };


    class Circle: public Shape
    {
        public:
           void draw() { // draw circle here }
    };
现在,您可以拥有不同形状的向量:

    vector<Shape*> shapes;
    shapes.push_back(new Rectangle());
    shapes.push_back(new Circle());
矢量形状;
形状。向后推(新矩形());
形状。向后推(新圆());
您可以像这样绘制所有形状:

    for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
    {
          (*i)->draw();
    }
for(向量::迭代器i=shapes.begin();i!=shapes.end();i++)
{
(*i)->draw();
}
这样,您就可以使用一个虚拟方法draw()绘制不同的形状。根据运行时关于指针后面对象类型的信息选择方法的正确版本

注意 当您使用虚函数时,您可以将它们声明为纯虚函数(就像在类形状中一样,只需将“=0”放在方法proto之后)。在这种情况下,您将无法使用纯虚函数创建对象的实例,它将被称为抽象类


还要注意析构函数前面的“virtual”。当您计划通过指向对象基类的指针处理对象时,您应该声明析构函数为虚拟的,所以当您为基类指针调用“delete”时,将调用所有析构函数链,并且不会出现内存泄漏

谢谢,你的回答令人满意……我不太明白你最后一句话“SaySomething函数应该是纯虚拟的,以避免可能的错误。”你能举个例子吗?很抱歉,回复太晚了。考虑到这一点,开发人员创建一个新的类继承我们的动物类(例如,代码>狐狸< /代码>,并且忘记重写SayOy方法。如果使用我提供的虚拟方法,
Fox
实例会说“Something”,这是不对的。如果我们将saysome声明为纯虚拟,我们将无法实例化
Fox
,包含
newfox(…)
的代码将引发错误。这样,创建
Fox
类的开发人员将在编译时收到错误通知。编译时错误很好,因为它们不会浪费时间:),但在不支持纯虚拟方法的语言中,“必须实现”方法应该引发异常。Java开发人员通常会这样做。然而这里的主要问题是,狐狸说了什么:如果我们不使用
virtual
,只在
Line
Triangle
等子类中重新定义它们,会怎么样。有什么区别?如果函数在基类中不是虚拟的,您就不能访问它
    vector<Shape*> shapes;
    shapes.push_back(new Rectangle());
    shapes.push_back(new Circle());
    for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
    {
          (*i)->draw();
    }