引入原因:为了方便使用多态特性,常在基类中定义虚函数,在很多情况下,基类本身生产对象是不合理的,例如动物作为一个基类可以派生出老虎、孔雀等子类,但是动物本身生成对象明显不符合常理。
因此为了解决上述问题,引入纯虚函数的概念,将函数定义为纯虚函数,
virual retType Function() = 0
,则编译器会要求在派生类中必须对该虚函数重载以实现多态性,同时将含有纯虚函数的类称为抽象类,它不能生成对象。这就很好的解决了上面的问题。
C++中的抽象类是指至少包含一个纯虚函数(pure virtual function)的类。抽象类不能实例化,只能用作基类(base class)。抽象类主要用于定义接口(interface),实现多态性(polymorphism)。
抽象类的规定
- 不能实例化:抽象类不能直接创建对象实例。
Shape shape; // 错误,Shape是抽象类
- 可以有构造函数:抽象类可以有构造函数,但是智能在派生类的构造函数中调用。
-
可以包含具体函数:抽象类除了纯虚函数,还可以包含具体实现的函数,这里函数在派生类中可以被继承和使用。
-
派生类必须实现基类的纯虚函数:派生类必须实现所有从基类继承的纯虚函数,除非派生类本身也是抽象类。
在复杂的继承关系中建议使用
override
显式重写父类虚函数。 -
抽象类不能作为参数类型、函数返回类型或显式转换的类型(抽象类的指针和引用除外)。
-
可以定义指向抽象类的指针和引用,这样做是为了可以指向它的派生类,进而实现多态性。
示例代码
#include <iostream>
// 抽象类
class Shape {
public:
// 纯虚函数
virtual void draw() = 0;
virtual double area() = 0;
};
// 派生类:Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing Circle" << std::endl; }
double area() override { return 3.14 * radius * radius; }
};
// 派生类:Rectangle
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void draw() override { std::cout << "Drawing Rectangle" << std::endl; }
double area() override { return width * height; }
};
int main() {
Shape* shape1 = new Circle(5.0);
Shape* shape2 = new Rectangle(4.0, 6.0);
shape1->draw();
std::cout << "Area: " << shape1->area() << std::endl;
shape2->draw();
std::cout << "Area: " << shape2->area() << std::endl;
return 0;
}
Shape类是一个抽象类,包含两个纯虚函数draw
和area
。
Circle类和Rectangle类继承自Shape,并且实现了纯虚函数。
在main
函数中,我们通过基类指针(Shape*
)创建了派生类的对象,并调用了它们的成员函数。这样实现了多态性。
使用场景
抽象类通常用于以下场景:
- 定义接口:提供一个统一的接口,不同的派生类可以有不同的实现。
- 实现多态性:可以通过基类指针或引用调用派生类的函数。
- 代码重用和扩展:通过继承和多态,可以更容易地扩展和维护代码。
通过使用抽象类,C++程序可以更好地实现面向对象的设计原则,如封装、继承和多态。
抽象类和接口的区别
抽象类(abstract classes)和接口(interface)的区别如下:
抽象类:定义了一个或多个纯虚函数的类称为抽象类,抽象类是特殊的类,除此之外,具有类的其他特性,例如可以包含具体实现的函数和成员变量,可以提供一些默认的行为。
接口:在C++中通过全纯虚类来实现,全纯虚类即只包含纯虚函数的类,不含有任何具体实现和成员变量,而且接口只定义了行为,没有任何实现细节。
这里的接口,有两种含义:
interface
:面向对象的接口,也是本文所说的接口,通过全纯虚类实现,没有实现细节。application programming interface
:应用程序编程接口,通常也称之为API,API是一组不同软件组件进行交互的定义的协议,例如计网中各个层次之间相互开放的API,在或者是进行网络编程所用到的Socket
等这一类都称为API,通常简称接口。API可以是库、框架、对外服务的公开函数、类等的集合,开发者可以通过这些接口来访问特定的功能。
- 操作系统API:如
Windows API
,提供操作系统服务的访问接口。- Web API:如
RESTful API
,允许不同系统通过HTTP协议进行通信和数据交换。- 库和框架API:如标准模板库(
STL
)或Qt库,提供开发者使用的类和函数。
抽象类和接口的关系,可以看作包含关系,例如可以在接口(interface)的基础上添加非虚函数、构造函数、成员变量等,这时接口便不再是接口,它已经进化为一个抽象类。因此可以说接口是抽象类的一部分。
接下来使用两个例子来分析:
#include <iostream>
// 抽象类
class AbstractClass {
public:
// 成员变量
int memberVariable;
// 纯虚函数
virtual void pureVirtualFunction() = 0;
// 具体函数
void concreteFunction() { std::cout << "This is a concrete function in AbstractClass" << std::endl; }
// 构造函数
AbstractClass() : memberVariable(0) { std::cout << "AbstractClass constructor" << std::endl; }
// 虚析构函数
virtual ~AbstractClass() {}
};
// 派生类
class DerivedClass : public AbstractClass {
public:
void pureVirtualFunction() override { std::cout << "Implementation of pureVirtualFunction in DerivedClass" << std::endl; }
};
int main() {
DerivedClass obj;
obj.pureVirtualFunction();
obj.concreteFunction();
return 0;
}
抽象类特点:
- 可以包含纯虚函数和具体实现的函数。
- 可以有成员变量。
- 可以有构造函数和析构函数。
- 用于提供默认行为和部分实现。
#include <iostream>
// 接口类
class Interface {
public:
// 纯虚函数
virtual void function1() = 0;
virtual void function2() = 0;
// 虚析构函数
virtual ~Interface() {}
};
// 实现类
class Implementation : public Interface {
public:
void function1() override { std::cout << "Implementation of function1" << std::endl; }
void function2() override { std::cout << "Implementation of function2" << std::endl; }
};
int main() {
Implementation obj;
obj.function1();
obj.function2();
return 0;
}
接口的特点:
- 只包含纯虚函数,没有任何具体实现的函数。
- 不包含成员变量。
- 通常只有析构函数,没有构造函数。
- 用于定义行为,不提供任何实现。
使用场景
-
抽象类:当你有一些共享的实现或状态时,可以使用抽象类。抽象类允许你在基类中提供部分实现,并在派生类中进行特化。例如,图形库中可以有一个抽象的
Shape
类,包含计算面积的纯虚函数和保存形状颜色的成员变量。 -
接口:当你只需要定义一组行为,而不涉及任何实现时,使用接口。接口确保所有派生类提供这些行为的具体实现。例如,插件系统可以定义一个
PluginInterface
接口,要求所有插件实现初始化和执行功能。
抽象类的一个具体实例
#include <iostream>
using namespace std;
class AbstractDrinking {
public:
// 烧水
virtual void Boil() = 0;
// 冲泡
virtual void Brew() = 0;
// 倒入杯中
virtual void PourInCup() = 0;
// 加入佐料
virtual void PutSomething() = 0;
// 规定流程
void MakeDrinking() {
Boil();
Brew();
PourInCup();
PutSomething();
}
virtual ~AbstractDrinking() = default;
};
class Coffe : public AbstractDrinking {
public:
// 烧水
void Boil() { cout << "烧矿泉水" << endl; }
// 冲泡
void Brew() { cout << "冲咖啡" << endl; }
// 倒入杯中
void PourInCup() { cout << "咖啡倒入杯中" << endl; }
// 加入佐料
void PutSomething() { cout << "加入牛奶" << endl; }
};
class Tea : public AbstractDrinking {
public:
// 烧水
void Boil() { cout << "烧自来水" << endl; }
// 冲泡
void Brew() { cout << "冲茶叶" << endl; }
// 倒入杯中
void PourInCup() { cout << "茶倒入杯中" << endl; }
// 加入佐料
void PutSomething() { cout << "加入柠檬" << endl; }
};
void makeDrinking(AbstractDrinking* p) {
p->MakeDrinking();
delete p;
}
int main() {
makeDrinking(new Coffe);
cout << "---------------------" << endl;
makeDrinking(new Tea);
return 0;
}
烧矿泉水
冲咖啡
咖啡倒入杯中
加入牛奶
---------------------
烧自来水
冲茶叶
茶倒入杯中
加入柠檬