一个具体类和抽象类之间有什么区别?

63

我正在学习 C++,但是抽象类和具体类让我感到困惑。能否提供一些现实世界的例子作为参考?


普通派生类和具体类有什么区别? - coming out of void
9个回答

114

抽象类是指声明了一个或多个方法但未定义其实现的类,这意味着编译器知道这些方法属于该类,但不知道如何执行代码。这些方法被称为抽象方法。下面是一个抽象类的例子。

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

这是一个声明抽象类的语句,它指定了该类的任何子类在成为具体类时都应该实现draw方法。由于它是抽象类,所以不能进行实例化,否则编译器将不知道在调用draw成员方法时应执行什么代码。因此,您不能执行以下操作:

shape my_shape();
my_shape.draw();
为了实际使用draw方法,您需要从这个抽象类派生出子类,并在子类中实现draw方法,使得这些子类变得具体:
class circle : public shape {
public:
  circle(int x, int y, int radius) {
    /* set up the circle */
  }
  virtual draw() {
    /* do stuff to draw the circle */
  }
};

class rectangle : public shape {
public:
  rectangle(int min_x, int min_y, int max_x, int max_y) {
    /* set up rectangle */
  }
  virtual draw() {
    /* do stuff to draw the rectangle */
  }
};

现在您可以实例化具体的对象circle和rectangle并使用它们的draw方法:

circle my_circle(40, 30, 10);
rectangle my_rectangle(20, 10, 50, 15);
my_circle.draw();
my_rectangle.draw();
当然,现在的问题是,为什么你要这样做呢?你不是可以定义圆和矩形类,然后摒弃整个形状类吗?你确实可以这样做,但那样你就不能利用它们的继承了。
std::vector<shape*> my_scene;
my_scene.push_back(new circle(40, 30, 10));
my_scene.push_back(new rectangle(20, 10, 50, 15));
std::for_each(my_scene.begin(), my_scene.end(), std::mem_fun_ref(&shape::draw)

这段代码让你可以将所有形状收集到一个容器中。如果你的场景中有很多形状和不同类型的形状,这将使得操作变得更加简单。例如,我们现在可以一次性绘制所有形状,而执行此操作的代码甚至不需要知道我们拥有哪些不同类型的形状。

现在最后我们需要知道为什么形状的绘制函数是抽象的,而不仅仅是一个空函数,也就是说,为什么我们没有定义:

class shape {
public:
  virtual void draw() {
    /* do nothing */
  }
};
这是因为我们不真正需要形状类型的对象,它们不会是实际存在的东西,而是抽象的。因此,定义draw方法的实现甚至是空的也没有任何意义。将shape类定义为抽象类可以防止我们错误地实例化shape类,或错误地调用基类的空draw函数而不是派生类的draw函数。实际上,我们为希望像形状那样行事的任何类定义了一个接口,我们说任何这样的类都应该有一个看起来像我们指定的draw方法。
回答你最后的问题,不存在什么“普通派生类”,每个类都是抽象类或具体类。具有任何抽象方法的类都是抽象类,否则就是具体类。这只是区分两种类型的类的一种方式。基类可以是抽象类或具体类,派生类也可以是抽象类或具体类。
class abstract_base {
public:
  virtual void abstract_method1() = 0;
  virtual void abstract_method2() = 0;
};

class concrete_base {
public:
  void concrete_method1() {
    /* do something */
  }
};

class abstract_derived1 : public abstract_base {
public:
  virtual void abstract_method3() = 0;
};

class abstract_derived2 : public concrete_base {
public:
  virtual void abstract_method3() = 0;
};

class abstract_derived3 : public abstract_base {
public:
  virtual abstract_method1() {
    /* do something */
  }
  /* note that we do not provide an implementation for
     abstract_method2 so the class is still abstract */
};

class concrete_derived1 : public concrete_base {
public:
  void concrete_method2() {
    /* do something */
  }
};

class concrete_derived2 : public abstract_base {
public:
  virtual void abstract_method1() {
    /* do something */
  }
  virtual void abstract_method2() {
    /* do something */
  }
  /* This class is now concrete because no abstract methods remain */
};

如果函数的定义缺失,那就只是一个链接错误。正如其他答案所述,抽象类涉及“= 0” 。更糟糕的是,在C ++中可以定义纯虚函数。事实上,已经定义但是是纯虚析构函数是很常见的。 - Martin Dorey

26

抽象类不能用来创建对象,而具体类可以用来创建对象。

具体意味着“存在于现实中或通过感官可感知;真实”。而抽象意味着“未应用或不切实际的;理论的”。

抽象类不能被实例化,而具体类可以。

抽象类是指具有一个或多个纯虚函数的类。而具体类没有纯虚函数。


20

具体类是可以用来创建对象的类。抽象类不能用来创建对象(您必须扩展抽象类并创建一个具体类,然后才能创建对象)。

假设有一台机器可以“压制”原材料并制造汽车。压制机是一个具体类。从这个具体类我们可以创建汽车对象。抽象类就像压制机的蓝图。您不能使用压制机的蓝图来制造汽车,您需要先根据蓝图创建压制机类。


为什么我们称它为具体类? - coming out of void
8
由于定义:混凝土是“存在于现实或真实经验中;可被感官感知;真实的”,而抽象意味着“不适用或实际的;理论的”。 - Jansen Price

3

抽象类不能被实例化,而具体类可以。抽象类作为派生类的“蓝图”,可以被实例化。

例如,Car类(抽象类),而Audi S4类(从Car派生)是一个具体的实现。


我对抽象类很清楚,但是普通派生类和具体类之间有什么区别呢? - coming out of void


2

基类与派生类是抽象类与具体类的正交概念。

基类是不继承任何其他类的类。派生类从另一个类继承而来。

抽象类具有一个或多个纯虚函数。具体类没有纯虚函数。

抽象类可以是基类,也可以是派生类(它从另一个抽象类派生而来)。具体类也可以是基类或派生类。你甚至可以通过将纯虚函数添加到派生类中来将抽象类派生自具体类。但在一般情况下,有一个基础抽象类和一个或多个具体派生类。


1

C++ Faq Lite是一个寻找此类问题答案的优秀网站。

在设计层面,抽象基类(ABC)对应于抽象概念。如果你问一个机械师是否修理车辆,他可能会想知道你想要修理什么样的车辆。很可能他不修理航天飞机、海洋轮船、自行车或核潜艇。问题在于术语“车辆”是一个抽象概念(例如,除非你知道要建造什么类型的车辆,否则你无法建造“车辆”)。在C++中,Vehicle类将是一个ABC,Bicycle、SpaceShuttle等为派生类(OceanLiner是一种Vehicle)。在真实世界的OO中,ABC随处可见。

抽象类是具有一个或多个纯虚拟成员函数的类。您不能创建抽象类的对象(实例)。

 class Shape {
 public:
   virtual void draw() const = 0;  // = 0 means it is "pure virtual"
   ...
 }; 

这并没有解释抽象类和具体类之间的区别,只是说明了什么是抽象类。 - wich
我阅读了一个抽象类的解释,它暗示了具体类是什么。此外,C++ FAQ Lite 是一个非常有用的网站。因此,我点赞了。 - Liz Albin
对我来说很清楚,如果一个抽象类是“具有一个或多个纯虚成员函数的类”,那么具体类就是不是它的类!或者有半抽象类吗? - Diego Dias

1
使用抽象类的一个很好的例子是当你正在构建非常模块化的东西时。假设你正在使用数据存储,但这些数据可能在MySQL数据库、SQLite数据库、XML文件或纯文本中。为了保持代码的多样性,你可以创建一个名为AbstractDatastore的类,该类定义了从数据存储中获取信息所需使用的公共方法。然后,你可以创建AbstractDatastore的具体实现,例如XmlDatastoreSQLiteDatastore等。然后,你的程序只需要知道它正在获取一个AbstractDatastore,并且必须在AbstractDatastore中定义这些函数,但它不知道也不关心数据是如何存储或检索的。

1
这并没有解释抽象类和具体类之间的区别,只是说明了何时使用它们。 - wich
1
其他人给出了合理的解释,而我提供了一个实际的例子。 - Corey D

0

具体类已实现其所有方法。抽象类则除了某些(至少一个)未实现的方法外,其余方法都已实现,以便您可以扩展它并实现未实现的方法。

优点:通过从抽象类扩展,您将获得基类的所有功能,并且您将被“强制”实现未实现的方法。因此,类的设计者基本上是在强迫您在使用类之前编写抽象方法的代码。


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