C++中纯虚函数有什么用途?

24

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


2
对于你和那些支持你的人:教科书没有解释为什么;而且我相信其他人会去谷歌搜索,所以我在这里问了。这样其他人也可以受益。 - patricksweeney
那是什么教科书?坦白地说,这里的答案(通常针对此类一般性问题)既不特别好也不准确。所以不要觉得你通过问这样的问题在做服务。 - anon
你可能会想为什么要将一个基类定义为抽象类。其中一个原因是控制类层次结构的使用。记住,当你创建类时,不仅要对问题进行建模,还需要考虑它们将来如何被使用以及被其他人使用的情况。 - AndersK
8
尼尔:我们中有些人并不整天坐在这里。别以为你能通过讽刺来进行社区服务。你是对的,很多回答都不够好,但排名第一的回答把问题简化到了最基本的层面。某人会从中受益。 - patricksweeney
8个回答

37

简而言之,这是为了使类成为抽象类,从而不能实例化,但可以通过子类重写纯虚方法来形成具体的类。这是在C++中定义接口的一种好方法。


2
我建议更改:“...一个子类可以...”为“...一个叶子类必须...”。 - Richard Corden
我试图保持故意模糊,以便允许一个子类覆盖一些但不是所有的纯虚方法,这将导致一个抽象基类需要更少的覆盖才能变得具体。然而,我不会称它为叶子,因为即使具体的子类也可以有自己的子类,并且其中一些可以是抽象的。实际上,一个类可以同时覆盖所有先前的纯虚方法并声明新的方法。考虑到所有这些,我认为最好保留原始的简要说明,并附上这个冗长的评论。 - Steven Sudit
1
问题可能只是术语上的一个问题。在我看来,如果一个函数在任何派生类中都没有被实现,那么它才是纯虚函数。换句话说,如果一个类在任何层次上仍然有纯虚函数,那么这个类仍然是抽象的。因此,这就是我的请求。但我想说,在这一点上,这些评论可能已经足够解释清楚了! :) - Richard Corden

18

这迫使派生类定义该函数。


8
任何包含纯虚方法的类都是抽象类,即不能实例化。抽象类可用于定义一些核心行为,这些子类应该共享,但允许(事实上,要求)子类单独实现抽象。
以下是一个抽象类示例:
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;
};

我认为你的意思是第一句话应该是“任何包含纯虚拟方法的类”... - Dan
我可能会补充一下,抽象基类不仅可以包含方法的实现,还可以包含数据成员。而接口则不应该包含任何数据成员。在C++中,这只是一种约定。但在C#或Java中,这是一条规则。 - Steven Sudit

4

C++中的纯虚方法基本上是一种定义接口而不需要实现它们的方法。


1
更准确地说:“一种强制所有继承者遵循定义的签名的方法” - Vishal Sahu

3

补充Steven Sudit的回答:

“简单来说,这是为了使类成为抽象类,从而防止其被实例化,但子类可以重写纯虚方法以形成具体类。这是在C++中定义接口的好方法。”

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

关于Jeff上面的回答:

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

考虑以下代码(请注意,出于清晰起见,未包括访问器、修改器、构造函数等):

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();

如果没有虚关键字,只会打印年龄,而不是像Student类应该打印出年龄和学生ID。

(此示例基于《C++ for Java程序员》中的一个非常相似的示例 http://www.amazon.com/Java-Programmers-Mark-Allen-Weiss/dp/013919424X

@Steven Sudit:您完全正确,我忽略了实际继承,呃!为了保持清晰,访问器等未包含在内,我现在已经更明显地表达了这一点。 3-7-09:所有问题都已解决。


我认为你的例子有问题,因为它没有包括继承,并且永远无法设置私有数据成员的值。当然,其背后的思想是正确的。 - Steven Sudit
这样就好多了,但还不太对:子类不应该有自己的年龄数据成员。 - Steven Sudit
此外,底部的实例化缺少构造函数周围的括号。 - Steven Sudit
@chrris 一个问题。这行代码在C++中能用吗?:“Person p = new Student();”。(new运算符返回一个指针) - Sss

3

假设我想要建模几种形状,而且每种形状都有明确定义的面积。我决定每个形状都必须继承IShape(“I”代表接口),并且IShape将包括一个GetArea()方法:

class IShape {
    virtual int GetArea();
};

现在的问题是,如果一个形状没有覆盖“GetArea()”,我该如何计算它的面积?也就是说,什么是最好的默认实现?圆形使用pi * 半径平方,正方形使用边长平方,平行四边形和矩形使用底边乘以高度,三角形使用1/2底边乘以高度,菱形、五边形、八边形等则使用其他公式。
因此,我通过定义纯虚方法来表明:“如果你是一个形状,你必须定义一种计算面积的方法,但我不确定那将是什么” 。
class IShape {
    virtual int GetArea() = 0;
};

在这两种情况下,GetArea都必须被声明为虚函数吗? - Steven Sudit
它需要在基类中声明为虚函数。感谢您的更正。 - Max Lybbert

1

本质上,纯虚函数用于创建一个接口(类似于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类中,“name”数据成员应该是私有的,或者至少是受保护的。我还建议通过引用而不是指针传递学生对象。最后,一个真实世界的Student类可能会在构造函数中接收名字作为参数。 - Steven Sudit
我完全同意。那些只是我匆忙打出这个例子时的疏忽。 - davidivins

0
使用抽象类的想法是,您仍然可以声明具有该类型的变量(即它是静态类型),但实际上变量引用或指向实际的具体类型(动态类型)。
当您在C++中调用方法时,编译器需要确保该方法将在该对象上受到支持。
通过声明纯虚函数,您正在放置一个“占位符”,编译器可以使用它来说“哦...我知道最终被此变量引用的任何内容都将接受该调用”,因为实际的具体类型将实现它。但是,在抽象类型中不必提供实现。
如果您没有声明任何内容,则编译器将无法有效地保证所有子类型都将实现它。
当然,如果您想知道为什么要使类成为抽象类,那么周围有很多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接