我正在学习C ++,但我对抽象类和具体类感到困惑。一些现实世界的例子将不胜感激。
答案 0 :(得分:96)
抽象类是一个声明了一个或多个方法但未定义的类,这意味着编译器知道这些方法是类的一部分,而不是该方法要执行的代码。这些被称为抽象方法。这是一个抽象类的例子。
class shape {
public:
virtual void draw() = 0;
};
这声明了一个抽象类,它指定类的任何后代应该实现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 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)
此代码让您将所有形状收集到一个容器中。如果你的场景中有很多形状和许多不同的形状,这将使它变得容易多了。例如,我们现在可以一次性绘制所有形状,而这样做的代码甚至不需要知道我们拥有的不同类型的形状。
现在终于我们需要知道为什么shape的draw函数是抽象的,而不仅仅是一个空函数,即为什么我们不只是定义:
class shape {
public:
virtual void draw() {
/* do nothing */
}
};
这样做的原因是我们并不真正想要类型形状的对象,无论如何它们都不是真实的东西,它们是抽象的。因此,为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 */
};
答案 1 :(得分:22)
抽象类不能用于创建对象。然而,具体类可用于创建对象。
具体表示“现实存在或实际经验;被感官察觉;真实''。然而,抽象表示'未应用或实际; theoritical 强>”。
abstract 类无法实例化。然而,具体可以。
abstract 类是具有一个或多个纯虚函数的类。而具体类没有纯虚函数。
答案 2 :(得分:20)
具体类是可用于创建对象的类。抽象类不能用于创建对象(您必须扩展抽象类并使具体类能够创建对象)。
假装有一台可以“盖章”原材料并制造汽车的机器。压模是一个具体的类。由此我们可以创建汽车对象。抽象类将是压模的蓝图。你不能用压模的蓝图制造汽车,你需要先从蓝图上制作压模类。
答案 3 :(得分:3)
抽象类无法实例化,而具体可以实例化。抽象类充当派生类的“蓝图”,可以实例化。
E.g。 Car
class(abstract)虽然Audi S4
类(派生自Car
)类是具体的实现。
答案 4 :(得分:3)
这些其他问题涵盖了很多内容:
答案 5 :(得分:1)
使用抽象类的一个很好的例子是当你构建一个非常模块化的东西时。假设您正在使用数据存储,但该数据可能位于MySQL数据库,SQLite数据库,XML文件或纯文本中。要在代码中保留这种多功能性,可以创建一个类AbstractDatastore
,用于定义要用于从数据存储区获取信息的公共方法。然后,您创建AbstractDatastore
的特定实现,例如XmlDatastore
,SQLiteDatastore
等。然后,您的程序只需要知道它正在获得AbstractDatastore
并且它必须具有AbstractDatastore
中定义的那些函数,但它不知道或不关心如何存储或检索数据。
答案 6 :(得分:1)
基类与派生类是抽象类与具体类的正交概念。
基类是不从任何其他类继承的基类。派生类确实从另一个类继承。
抽象类是具有一个或多个纯虚函数的类。具体类没有纯虚拟。
抽象类可以是基类,也可以是派生类(它是从另一个抽象类派生的)。具体类也可以是基类或派生类。您甚至可以通过向派生类添加纯虚函数,从具体类派生抽象类。但在一般用途中,有一个基本抽象类和一个或多个具体派生类。
答案 7 :(得分:0)
C++ Faq Lite是寻找这类问题答案的绝佳网站。
在设计层面,抽象基类(ABC)对应于抽象概念。如果你问机修工他是否修理了车辆,他可能会想知道你的车辆是什么样的。他可能无法修理航天飞机,远洋轮船,自行车或核潜艇。问题是“车辆”一词是一个抽象概念(例如,除非你知道要建造什么样的车辆,否则你不能建造“车辆”)。在C ++中,类Vehicle将是一个ABC,其中Bicycle,SpaceShuttle等是派生类(OceanLiner是一种类型的Vehicle)。在现实世界的OO中,ABC遍布各地
Abstract Class是一个具有一个或多个纯虚拟成员函数的类。您不能创建抽象类的对象(实例)
class Shape {
public:
virtual void draw() const = 0; // = 0 means it is "pure virtual"
...
};
答案 8 :(得分:0)
具体类已实现其所有方法。除了一些(至少一个)方法之外,抽象类的所有方法都是未实现的,这样您就可以扩展它并实现未实现的方法。
优点:通过从抽象类扩展,您可以获得基类和基类的所有功能。您将被“强制”实施未实现的方法。因此,类的设计者基本上强迫您在类对任何用途之前在抽象方法中编写代码。