C++抽象类与接口

引入原因:为了方便使用多态特性,常在基类中定义虚函数,在很多情况下,基类本身生产对象是不合理的,例如动物作为一个基类可以派生出老虎、孔雀等子类,但是动物本身生成对象明显不符合常理。

因此为了解决上述问题,引入纯虚函数的概念,将函数定义为纯虚函数,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类是一个抽象类,包含两个纯虚函数drawarea

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;
}
烧矿泉水
冲咖啡
咖啡倒入杯中
加入牛奶
---------------------
烧自来水
冲茶叶
茶倒入杯中
加入柠檬
作者:WuQiling
文章链接:https://www.wqlblog.cn/c抽象类/
文章采用 CC BY-NC-SA 4.0 协议进行许可,转载请遵循协议
暂无评论

发送评论 编辑评论


				
默认
贴吧
上一篇
下一篇