【问题标题】:What are the uses of pure virtual functions in C++?C++中纯虚函数的用途是什么?
【发布时间】:2010-11-07 10:49:37
【问题描述】:

我现在正在课堂上学习 C++,但我不太了解纯虚函数。我知道它们稍后会在派生类中进行概述,但是如果您只是要在派生类中定义它,为什么要将其声明为等于 0?

【问题讨论】:

  • 对于你和少数投票给你的人:教科书没有解释为什么;因为我确信其他人会谷歌它,所以我在这里问了。这样别人就可以受益。
  • 那是什么教科书?坦率地说,这里的答案(对于此类一般性问题很典型)既不是特别好也不准确。所以不要觉得你问这样的问题是在做服务。
  • 您可能想知道为什么您希望基类是抽象的。它是——除其他外——控制你的类层次结构的使用,记住当你创建类时,你不仅要对问题进行建模,还需要考虑它们将来和其他人将如何使用。
  • 尼尔:有些人不会整天坐在这里。不要以为你在做这个社区服务时很刻薄。你的权利,其中很多都不是好的答案,但最上面的答案将其分解为最基本的水平。其他人将从中受益。

标签: c++ virtual-functions pure-virtual


【解决方案1】:

简单来说就是把类抽象化,使其不能被实例化,但是子类可以重写纯虚方法,形成一个具体的类。这是在 C++ 中定义接口的好方法。

【讨论】:

  • 我建议将:“...a child class can...”更改为“...a leaf class must...”。
  • 我试图故意保持模糊,以允许一个子类覆盖一些但不是所有纯虚方法的可能性,这导致一个抽象基类需要更少的覆盖才能变得具体。然而,我不会称它为叶子,因为即使是具体的孩子也可以有自己的孩子,其中一些可以是抽象的。事实上,一个类可以在声明新方法的同时覆盖所有以前的纯虚方法。考虑到这一切,我认为最好将原始的简短解释留给这个冗长的评论。
  • 这个问题可能只是术语之一。恕我直言,一个函数只有在没有在派生类之一中实现时才是纯虚拟的。换句话说,如果任何级别的类仍然具有纯虚函数,那么该类仍然是抽象的。因此我的要求。但我想说,这些 cmets 在这一点上可能已经足够解释事情了! :)
【解决方案2】:

这会强制派生类定义函数。

【讨论】:

    【解决方案3】:

    任何包含纯虚方法的类都是抽象的,也就是说,它不能被实例化。抽象类对于定义子类应该共享的一些核心行为很有用,但允许(实际上,要求)子类单独实现抽象。

    一个抽象类的例子:

    class Foo {
    
        // pure virtual, must be implemented by subclasses
        virtual public void myMethod() = 0;
    
        // normal method, will be available to all subclasses,
        // but *can* be overridden
        virtual public void myOtherMethod();
    };
    

    每个方法都是抽象的类可以用作接口,要求所有子类通过实现其中包含的所有方法来符合接口。

    接口示例:

    class Bar {
    
        // all method are pure virtual; subclasses must implement
        // all of them
        virtual public void myMethod() = 0;
    
        virtual public void myOtherMethod() = 0;
    };
    

    【讨论】:

    • 我认为您的意思是第一句话是“任何包含 pure 虚拟方法的类”....
    • 我可能会补充一点,抽象基类可能不仅包含方法的实现,还包含数据成员。接口不应该有。在 C++ 中,这只是一个约定。在 C# 或 Java 中,这是一个规则。
    【解决方案4】:

    C++ 中的纯虚方法基本上是一种无需实现即可定义接口的方法。

    【讨论】:

    • 正确,更准确地说:“一种强制所有继承者遵循定义签名的方法”
    【解决方案5】:

    想象一下,我想为几种形状建模,它们都有一个明确定义的区域。我决定每个形状都必须继承IShape(“I”表示接口),IShape 将包含一个GetArea() 方法:

    class IShape {
        virtual int GetArea();
    };
    

    现在的问题是:如果该形状不覆盖GetArea(),我应该如何计算该形状的面积?也就是说,最好的默认实现是什么?圆形使用 pi*radius^2,正方形使用 length^2,平行四边形和矩形使用 base*height,三角形使用 1/2 base*height,菱形、五边形、八边形等使用其他公式。

    所以我说“如果你是一个形状,你必须定义一种计算面积的方法,但如果我知道那会是什么,那该死的”,通过定义纯虚拟方法:

    class IShape {
        virtual int GetArea() = 0;
    };
    

    【讨论】:

    • 在两种情况下都不需要将 GetArea 声明为虚拟的吗?
    • 需要在基类中声明为virtual。谢谢指正。
    【解决方案6】:

    补充 Steven Sudit 的回答:

    “简单来说就是把类抽象化,这样就不能实例化了,但是子类可以重写纯虚方法,形成一个具体的类。这是在C++中定义接口的好方法。 "

    如果您有一个基类(可能是 Shape),用于定义其派生类可以使用的许多成员函数,但希望阻止声明 Shape 的实例并强制用户仅使用派生类(可能是 Rectangle、Triangle、Pentagon 等)

    RE:上面杰夫的回答

    非抽象类可以包含虚成员函数并被实例化。事实上,对于重载成员函数,这是必需的,因为默认情况下 C++ 不会确定变量的运行时类型,但是当使用虚拟关键字定义时,它会。

    考虑一下这段代码(注意,为了清楚起见,不包括访问器、修改器、构造器等):

    class Person{
      int age;
    
      public:
        virtual void print(){
          cout << age <<endl;
        }
    }
    
    class Student: public Person{
      int studentID
    
      public:
        void print(){
          cout << age << studentID <<endl;
        }
    }
    

    现在运行这段代码时:

     Person p = new Student();
     p.print();
    

    没有 virtual 关键字,只会打印年龄,而不是学生类应该发生的年龄和学生 ID

    (这个例子是基于一个非常相似的 c++ for java程序员http://www.amazon.com/Java-Programmers-Mark-Allen-Weiss/dp/013919424X

    @Steven Sudit:你完全正确,我忽略了包含实际继承,doh!不包括访问器等以使事情更清晰,我现在已经使这一点更加明显。 09 年 3 月 7 日:全部修复

    【讨论】:

    • 我相信您的示例已损坏,因为它不包括继承,并且无法设置私有数据成员的值。当然,它背后的想法是正确的。
    • 这样更好,但仍然不太正确:子类不应该有自己的年龄数据成员。
    • 另外,底部的实例在构造函数周围缺少括号。
    • @chrris 一个问题。这条线可以在 c++ 中使用吗? :“人 p = 新学生();”。 (new 运算符返回一个指针)
    【解决方案7】:

    本质上,纯虚拟用于创建接口(类似于 java)。这可以用作两个模块(或类,或其他)之间关于期望什么样的功能的协议,而无需了解其他部分的实现。这使您可以使用相同的界面轻松即插即用,而无需更改使用您的界面的其他模块中的任何内容。

    例如:

    class IStudent
    {
        public:
        virtual ~IStudent(){};
        virtual std::string getName() = 0;
    };
    
    
    class Student : public IStudent
    {
        public:
        std::string name;
        std::string getName() { return name; };
        void setName(std::string in) { name = in; };
    };
    
    class School
    {
        public:
        void sendStudentToDetention(IStudent *in) {
            cout << "The student sent to detention is: ";
            cout << in->getName() << endl;
        };
    };
    
    int main()
    {
        Student student;
        student.setName("Dave");
    
        School school;
        school.sendStudentToDetention(&student);
    return 0;
    }
    

    学校不需要知道如何设置学生的姓名,只需要知道如何获取学生的姓名即可。通过为学生提供一个接口来实现和学校使用,两部分之间就学校需要什么功能来执行其工作达成了协议。现在,我们可以在不影响学校的情况下随意切换 Student 类的不同实现(只要我们每次都实现相同的接口)。

    【讨论】:

    • 我建议学生中的“姓名”数据成员应该是私有的,或者至少是受保护的。我还建议通过引用而不是指针传递学生。最后,现实世界的 Student 类可能会在其构造函数中使用名称。
    • 我同意所有这些。当我快速输入这个示例时,这些只是疏忽。
    【解决方案8】:

    抽象类的想法是您仍然可以使用该类型声明一个变量(即,它是静态类型),但该变量实际上引用或指向一个实际的具体类型(动态类型)。

    在 C++ 中调用方法时,编译器需要确保该对象支持该方法。

    通过声明纯虚函数,您放置了一个“占位符”,编译器可以使用它来表示“哦……我知道这个变量最终引用的任何内容都将接受该调用”,因为实际的具体类型将实现它。但是,您不必提供抽象类型的实现。

    如果你没有声明任何东西,那么编译器将没有有效的方法来保证它会被所有子类型实现。

    当然,如果你问为什么要让一个类抽象,这方面有很多信息。

    【讨论】:

      猜你喜欢
      • 2011-05-21
      • 2012-06-03
      • 2011-06-04
      • 2015-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多