RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 596666
Accepted
Michael
Michael
Asked:2020-11-28 07:57:06 +0000 UTC2020-11-28 07:57:06 +0000 UTC 2020-11-28 07:57:06 +0000 UTC

使用虚函数

  • 772

谁能解释一下它们的实际用途是什么?事实是,我了解它们的工作机制,但我不明白它们的用途和用途。

让我们看一个例子:

class Animal
{
public:
    Animal():itsAge(1) { cout << "Animal constructor...\n"; }
    virtual ~Animal() { cout << "Animal destructor...\n"; }
    virtual void Speak() const { cout << "Animal speak!\n"; }
protected:
    int itsAge;
};

class Dog : public Animal
{
public:
    Dog() { cout << "Dog constructor...\n"; }
    virtual ~Dog() { cout << "Dog destructor...\n"; }
    void Speak() const { cout << "Woof!\n"; }
    void WagTail() { cout << "Wagging Tail...\n"; }
}

int main()
{
    Animal *pDog = new Dog;
    pDog->Speak();
    return 0;
}

结果:

  • 动物构造器...
  • 狗构造器...
  • 纬!

使用虚函数的本质是,当通过指针访问一个方法时,恰好会调用在基类中声明为虚函数而在派生类中被重写的选项。

但首先,使用 Animal 类指针 *pDog,您仍然无法访问WagTail()方法(摇尾巴),因为它未在 Animal 类中定义。其次,使用这种机制,您将不得不支付与创建 v 表相关的某些成本(其中每个元素都占用 RAM 资源)。

另外,我不明白为什么在期望指向基类对象的指针时传递指向派生类对象的指针?

上述两个问题都可以通过在基类和派生类中声明非虚拟方法然后编写以下内容来解决:

Dog *pDog = new Dog;

代替:

Animal *pDog = new Dog;

“利润”在哪里?

c++
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Best Answer
    Vlad from Moscow
    2020-11-28T09:58:31Z2020-11-28T09:58:31Z

    考虑图形形状的经典示例。

    您可以定义一个类Shape,其中包含您打算在应用程序中使用的所有几何形状的通用方法。

    此类为所有几何形状定义了一个通用接口。

    假设您有一个要在其上放置几何形状的窗体。表单事先并不知道它必须包含哪些几何形状。它将几何形状称为某种抽象对象,这些对象被赋予了一些方法,表单可以使用这些方法在控制台上显示这些形状。

    由于可以在表格中添加任意数量的各种类型的几何形状,那么问题来了,如何将它们存储在表格中呢?如果这些形状没有共同的抽象表示,那么它们就不能以一种形式存储,因为需要一种特定的对象类型,这样它们就可以全部存储在一个容器中,而不关心形状实际上是不同的。

    如果所有形状都从同一个类继承,这很容易做到,在本例中是从 class 继承的Shape,并且在这个类中定义了表单可以使用的虚拟方法,而不管表单正在处理哪个特定对象。

    下面是一个简单的演示程序,它实现了所描述的想法。

    有一个类Form将所有几何形状(在本例中为类的对象LeftTriangle和RightTriangle)存储Rectangle在一个标准容器中std::vector,它有一个方法display允许您在控制台上显示所有形状,将显示自身的过程委托给每个图形.

    // Shape.cpp: определяет точку входа для консольного приложения.
    //
    
    //  #include "stdafx.h"
        #include <iostream>
        #include <iomanip>
        #include <vector>
        #include <memory>
    
        struct Point
        {
            int x;
            int y;
        };
    
        class Shape
        {
        protected:
            Point upper_left;
            char pixel = '*';
    
        public:
            explicit Shape(Point p = { 0, 0 }) : upper_left(p)
            {
            }
    
            virtual ~Shape() = default;
    
            char set_pixel(char pixel)
            {
                char old_pixel = this->pixel;
    
                this->pixel = pixel;
    
                return old_pixel;
            }
    
            virtual std::ostream & draw(std::ostream &os = std::cout) const = 0;
    
            Point move(int dx = 0, int dy = 0)
            {
                Point old_upper_left = this->upper_left;
    
                this->upper_left.x += dx;
                this->upper_left.y += dy;
    
                if (this->upper_left.x < 0) this->upper_left.x = 0;
                if (this->upper_left.y < 0) this->upper_left.y = 0;
    
                return old_upper_left;
            }
        };
    
        class Triangle : public Shape
        {
        protected:
            unsigned int height;
    
        public:
            explicit Triangle(unsigned int height = 1) : height(height)
            {
            }
        };
    
    
        class LeftTriangle : public Triangle
        {
        public:
            explicit LeftTriangle(unsigned int height = 1)
                : Triangle(height)
            {
            }
    
            std::ostream & draw(std::ostream &os = std::cout) const override
            {
                for (int i = 0; i < upper_left.y; i++) os << '\n';
    
                for (unsigned int i = 0; i < height; i++)
                {
                    os << std::setw( upper_left.x ) 
                       << std::setfill( ' ' )
                       << ""
                       << std::setw(i + 2) 
                       << std::setfill(pixel) << '\n';
                }
    
                return os;
            }
        };
    
        class RightTriangle : public Triangle
        {
        public:
            explicit RightTriangle( unsigned int height = 1)
                : Triangle( height)
            {
            }
    
            std::ostream & draw(std::ostream &os = std::cout) const override
            {
                for (int i = 0; i < upper_left.y; i++) os << '\n';
    
                for (unsigned int i = height; i != 0; i-- )
                {
                    os << std::setw(upper_left.x + i - 1 )
                        << std::setfill( ' ' ) << ""
                        << std::setw( height - i + 2 ) 
                        << std::setfill( pixel )
                        << '\n';
                }
    
                return os;
            }
        };
    
        class Rectangle : public Shape
        {
        protected:
            unsigned int height;
            unsigned int width;
    
        public:
            explicit Rectangle( unsigned int height = 1, unsigned int width = 1 ) 
                : height(height), width( width )
            {
            }
    
            std::ostream & draw(std::ostream &os = std::cout) const override
            {
                for (int i = 0; i < upper_left.y; i++) os << '\n';
    
                for (unsigned int i = 0; i < height; i++)
                {
                    os << std::setw(upper_left.x ) << std::setfill( ' ' ) << ""
                        << std::setw( width + 1 ) << std::setfill(pixel)
                        << '\n';
                }
    
                return os;
            }
        };
    
        class Form
        {
        public:
            Form() = default;
    
            void add( Shape * &&shape )
            {
                shapes.push_back(std::unique_ptr<Shape>( shape ));
            }
    
            std::ostream & display(std::ostream &os = std::cout) const
            {
                const int Step = 10;
                int dx = 0;
                for (auto &p : shapes)
                {
                    p->move(dx);
                    p->draw(os) << std::endl;
    
                    dx += Step;
                }
    
                return os;
            }
        private:
            std::vector<std::unique_ptr<Shape>> shapes;
        };
    
        int main() 
        {
            Form form;
    
            form.add(new RightTriangle(5));
            form.add(new LeftTriangle(5));
            form.add(new Rectangle(5, 5));
    
            form.display();
    
            return 0;
        }
    

    将程序输出到控制台

        *
       **
      ***
     ****
    *****
    
              *
              **
              ***
              ****
              *****
    
                        *****
                        *****
                        *****
                        *****
                        *****
    

    虚方法为所有派生类定义了一个公共接口,允许它们定义自己的接口实现。为了能够将派生类的对象引用为具有共同属性的相同类型的对象,必须将它们强制转换为某种共同类型。此公共类型可以是这些对象的公共基类之一。这样就实现了多态性,即看起来像同一类型对象的对象具有多种形式的行为和表示。

    当然,每个派生类都可以另外定义自己的数据成员和方法。但在这种情况下,这就是它们与其他派生类的对象的区别。

    例如,您可以说每个女人和每个男人都是一个人。但是你不能说,例如,每个人都是女人,或者每个人都是男人。如果您将女性和男性视为人,那么您可以不分性别地称呼他们,向他们发送各种信息,正如他们在巴解组织中所说的那样。例如,如果您是公共汽车的售票员,您可能需要出示车票。对你来说,公交车上的男女都是乘客,他们必须有共同的属性,比如有车票。为此,您必须将男性和女性视为某种一般类型的对象,在本例中为乘客。然而,男人和女人作为他们各自阶级的客体是不同的。比如女人可以生孩子,男人不能(除非男人是女人,

    • 5
  2. Harry
    2020-11-28T11:18:14Z2020-11-28T11:18:14Z

    我不会描述任何狗或几何形状的优点,我会表达一种听起来平庸的考虑,但这对我的理解来说意义重大。

    当他们说继承使重用代码更容易时,我们在谈论什么样的代码?关于派生类使用基类代码的事实?一点也不。这些事情可以通过简单的函数调用来完成。

    继承使得以新的方式使用已经编写的代码(甚至编译为动态库)成为可能。

    有些f(Base*);在没有任何更改的情况下被重用,使用的代码甚至还没有接近编写,甚至可能在编写和编译这个代码时就没有设计f()过——它只是从Base类派生的虚函数的代码。

    是的,在某种程度上,这类似于将指向其他函数的指针传递给函数,但只是在非常一定程度上。而且,除此之外,“为什么我们需要将其他功能传递给功能?”这个问题,我希望不会让您感到困惑“利润在哪里?”

    • 5
  3. ampawd
    2020-11-29T05:09:03Z2020-11-29T05:09:03Z

    大家知道,C++是一种编译型静态类型语言,也就是在编译时进行类型解析,所以虚方法的机制将这种可能性扩展到了程序员想要在运行时判断对象类型的情况,使用,例如,dynamic_cast在 NOT 多态类的情况下,这将不起作用,但会产生编译错误。

    “利润”在哪里?

    假设有这样的代码

    struct SomeThing
    {
       virtual void someGenericOperation() { cout << "base generic operation" }
       ~SomeThing();
    };
    
    struct SomeThingConcrete : SomeThing
    {
       void someConcreteOperation() {}
    
       void someGenericOperation() { cout << "override generic op" }    // override
    };
    
    SomeThing* p_smth = ...;
    

    假设我们有这样一种情况,我们想要检查什么p_smth指向类型SomeThingConcrete,以便可以调用特定于SomeThingConcrete-的方法someConcreteOperation()。

    验证如下

    SomeThingConcrete* p_concrete = dynamic_cast<SomeThingInterface*>(p_smth);
    if (p_concrete != NULL)
    {
       p_concrete->someConcreteOperation();   
    }
    else
    {
       // p_concrete does not point to SomeThingInterface 
    }
    

    也就是说,如果它downcast适用于该类型SomeThing(满足条件p_concrete != NULL),那么我们的假设得到证实并p_smth真正指向SomeThingConcrete,否则它p_smth指向其他某个派生SomeThing类。

    此外,如果基类不是多态的,它会变成一堆额外的代码,例如,如果您将指向基类的指针传递给函数void f(SomeThing*),那么由于静态绑定属性,您将不得不f为SomeThingConcrete每个新继承人重载函数SomeThing- 以便它是在派生中重新定义的那个被称为继承方法的版本。

    动态绑定,使用调用虚拟方法的内置机制,将完全摆脱这种重载——即

    void f(SomeThing* p_smth) //   f is a single polymorphic function
    {
       p_smth->someGenericOperation();
    {
    
    int main()
    {
       SomeThing* p_smth = new SomeThing();
       SomeThing* p_smthConcrete = new SomeThingConcrete();
    
       f(p_smth);         // base generic operation
       f(p_smthConcrete); // override generic operation
    }
    
    • 2

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5