C++ 接口
C++ 中的接口 (抽象类)
Section titled “C++ 中的接口 (抽象类)”在面向对象编程中,接口为类定义了契约。它规定了一个类 能做什么(它的能力或方法),而不强制规定它 如何做。这促进了松耦合和多态性。
C++ 使用 抽象类 来实现接口。抽象类是不能直接实例化的类,它旨在用作派生类的基类。
如果一个类至少声明了一个 纯虚函数,它就成为抽象类。纯虚函数是一个虚函数,它在基类中声明,但不一定定义。通过在其签名末尾附加 = 0 来声明:
#include <string>
// 定义 'Drawable' 接口的抽象基类class Drawable {public: // 纯虚函数 - 派生类必须实现此函数 virtual void draw() const = 0;
// 抽象类仍然可以有常规成员和方法 virtual std::string getName() const { return "Generic Drawable"; }
// 重要提示:多态使用的抽象基类需要一个虚析构函数 virtual ~Drawable() = default; // 使用默认实现};关于抽象类的要点:
- 不能实例化: 你不能直接创建抽象类类型的对象(
Drawable d;会导致编译错误)。 - 接口作用: 它们主要用作接口,定义了一组派生类必须实现的通用函数。
- 多态性: 它们通常与指针或引用一起使用,以便通过通用的基类接口处理不同派生类的对象。
- 强制实现: 任何继承自抽象类的具体(非抽象)类 必须 为 所有 继承的纯虚函数提供实现。
- 虚析构函数: 如果你打算通过基类指针删除派生对象(
Drawable* ptr = new Circle(); delete ptr;),基类 必须 有一个虚析构函数。否则,只会运行基类析构函数,导致资源泄漏(未定义行为)。添加virtual ~ClassName() = default;通常就足够了。
可以实例化(即非抽象)的类称为 具体类。从抽象类派生的具体类会为所有继承的纯虚函数提供实现。
抽象类示例:
Section titled “抽象类示例:”让我们用具体的 Circle 和 Square 类来实现 Drawable 接口:
#include <iostream>#include <string>#include <vector>#include <memory> // 用于 std::unique_ptr
// 抽象基类(接口)class Shape {public: // 纯虚函数 - 定义接口契约 virtual double getArea() const = 0; virtual std::string getName() const = 0;
// 虚析构函数对于带有虚函数的基类至关重要 virtual ~Shape() { std::cout << "Destroying Shape\n"; }; // 非默认析构函数示例
// 通用工具函数(可选) void displayInfo() const { std::cout << "Shape Type: " << getName() << ", Area: " << getArea() << std::endl; }
protected: // 如果需要,可以是保护构造函数,但 Shape 不能直接实例化 Shape() = default;};
// 具体派生类:Rectangleclass Rectangle : public Shape {public: Rectangle(double w, double h) : width(w), height(h) {}
// 重写纯虚函数 double getArea() const override { return width * height; } std::string getName() const override { return "Rectangle"; }
~Rectangle() override { std::cout << "Destroying Rectangle\n"; }
private: double width; double height;};
// 具体派生类:Triangleclass Triangle : public Shape {public: Triangle(double b, double h) : base(b), height(h) {}
// 重写纯虚函数 double getArea() const override { return 0.5 * base * height; } std::string getName() const override { return "Triangle"; }
~Triangle() override { std::cout << "Destroying Triangle\n"; }
private: double base; double height;};
int main() { // 使用 unique_ptr 进行自动内存管理 (RAII) std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Rectangle>(5.0, 7.0)); shapes.push_back(std::make_unique<Triangle>(5.0, 7.0));
std::cout << "Processing shapes polymorphically:\n"; for (const auto& shapePtr : shapes) { // 根据实际对象类型调用正确的 getArea/getName shapePtr->displayInfo(); }
std::cout << "\nShapes vector going out of scope...\n"; // unique_ptr 会自动在其包含的指针上调用 delete, // 借助虚基类析构函数调用正确的派生类析构函数。 return 0;}编译并执行上述代码时,会产生类似于以下的输出:
Processing shapes polymorphically:Shape Type: Rectangle, Area: 35Shape Type: Triangle, Area: 17.5
Shapes vector going out of scope...Destroying RectangleDestroying ShapeDestroying TriangleDestroying Shape注意我们如何通过 Shape 指针统一处理 Rectangle 和 Triangle 对象。override 关键字有助于确保我们正确实现了基类中的虚函数。虚析构函数确保了正确的清理。
使用抽象类(接口)是一种强大的设计策略:
- 解耦: 使用接口的代码(例如使用
Shape*的main函数)无需了解具体的具体类型(Rectangle、Triangle)。它只依赖于Shape接口。 - 可扩展性: 通过从
Shape派生并实现所需函数,你可以轻松添加新的形状(例如Circle),而无需修改使用Shape接口的代码。 - 标准化: 确保所有派生类都提供基类中纯虚函数定义的一组最小功能。
现代 C++ (C++20) 还引入了 Concepts,它提供了一种在编译时指定模板参数要求的方法,有时可作为泛型编程中传统接口的替代或补充。