C++中的抽象类 - 详解

一、什么叫做抽象类

1、 定义

抽象类,英文名称Abstract Class是指包含至少一个纯虚函数(Pure Virtual Function) 的类。纯虚函数是一种未提供具体实现、仅声明接口的虚函数,语法为:

virtual 返回类型 函数名(参数列表) = 0;  // "=0" 表示纯虚函数

抽象类的核心作用是定义接口规范,强制派生类实现其声明的接口,自身无法实例化对象(不能创建抽象类的对象)。

一段比较专业化的解释,给大家看的云里雾里,继续阅读下,大家就会了解了。

下面我们一步一步拆解出来看

1.1 、是指包含至少一个纯虚函数(Pure Virtual Function) 的类

先来了解什么是纯虚函数,纯虚函数,的前提是必须是一个虚函数,即使用关键字Virtual修饰的函数。而纯这个性质,则使用赋值等于0的表达式来实现。

virtual 返回类型 函数名(参数列表) = 0;  // "=0" 表示纯虚函数

如定义一个返回值int类型的,包含一个参数x的纯虚函数

virtual int PV_Fun(int x) = 0;  // "=0" 表示纯虚函数

这里需要注意,虚函数从函数功能上来说,可以分为

(1)纯虚成员函数

(2)纯虚析构函数

1.2、纯虚函数是一种未提供具体实现、仅声明接口的虚函数

未提供具体实现,还是以1.1中的代码为例:

virtual int PV_Fun(int x) = 0; 

注意看赋值号,=0,这是纯的定义,这里有C语言基础的同学就会问,这里虚纯函数怎么成为左值了?回到1.1中括号里补充的说明,这里的=不是赋值号,记住这点即可。

不提供具体实现,那么我定义这个函数的作用是什么喃?另外我需不需要在其他地方提供具体实现?

先回答,确实需要在其他地方提供具体实现。就是在这个类的子类中提供具体实现,看代码

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() override {  // 实现基类的纯虚函数
        return 3.14 * radius * radius;
    }
};

double area() override {  // 实现基类的纯虚函数
        return 3.14 * radius * radius;
    }

这段代码就是提供了,具体的实现,再强调一遍,这里的定义可以在前面加上virtual或者不加,在{}前加上关键字override,即说明是对虚函数具体的实现。代码编译时会自动生成虚函数表。

此外,还有两点需要注意:

  • 必须被继承:抽象类的意义在于通过派生类实现其纯虚函数,从而实现多态。

  • 派生类需实现所有纯虚函数:若派生类未完全实现基类的纯虚函数,则该派生类仍为抽象类(也不能实例化)。

1.3 抽象类中提供了接口,这句话该如何理解

先看一段资料中的解释,“抽象类定义模块间的交互接口,确保不同实现类遵循统一规范

我们还是以上面代码为例:

在图形库中,Shape作为抽象类定义area()计算体积draw()画出图形等接口,派生类CircleRectangle分别实现具体逻辑;

这也很好理解,circle是圆形,面积计算公式是piR平方,Rectangle是矩形,面积计算公式是宽*长。具体画法上更是不同。所以这两个函数必须在具体的类中实现。

此时又会有人问?那只要定义一个虚函数,也就能实现这样的功能,为什么还要搞一个抽象类?还要搞一个纯虚函数。

这就涉及到,1.2最后讲解的两个注意点:

这两点语法就规定了,抽象类必须被继承,不然编译错误,派生类需实现所有纯虚函数。这两点就是不能虚函数无法比拟的。

以工作的场景为例:基类是领导,派生类是他底下的员工。使用虚函数实现多态,就像是领导事无巨细的管所有事务,它会给派生类说今天干什么事,然后自己干一遍,让员工跟着这个模板干。而抽象类的领导则是,我只管布置任务,具体怎么实现,它不会干预。本质上来说,这种不干预的机制下,只要员工能力合格,工作上的效率会更高。

1.4 抽象类的其他操作与应用

这一点很重要,我们之前说,抽象类无法被实例化,但是可万万没说,抽象类不能被定义为指针。直接看代码:

int main() {
    Shape* shape = new Circle(2.0);  // 基类指针指向派生类对象(多态)
    cout << "Area: " << shape->area() << endl;  // 调用Circle的area()
    delete shape;
    return 0;
}

 Shape* shape则是定义了一个抽象类的指针,然后将这个基抽象类指向其派生类。。

以下内容抽象类,可以正常包含,需要注意!!

  • 正常的成员变量、普通成员函数(虚或非虚)、静态成员;
  • 嵌套其他抽象类、正常类;
  • 包含结构体、共用体等复合类型。

二、抽象类中的比较特殊的纯虚析构函数

2.1. 定义

纯虚析构函数是指被声明为纯虚函数的析构函数,语法为:

class Base {
public:
    virtual ~Base() = 0;  // 纯虚析构函数
};

注意:纯虚析构函数必须提供定义(这是它与普通纯虚函数的关键区别),否则会导致链接错误。定义方式为类外实现

Base::~Base() {  // 纯虚析构函数的定义
    // 释放基类资源(若有)
}

这里的代码,可能有点误导人,注意这个纯虚析构函数的定义,一定要放在class外,且不能放在其他class中

2.2. 核心特性

  • 使类成为抽象类:包含纯虚析构函数的类是抽象类(即使没有其他纯虚函数),无法实例化。示例:

    class Base {
    public:
        virtual ~Base() = 0;  // 纯虚析构函数,使Base成为抽象类
    };
    Base::~Base() {}  // 必须定义
    int main() {
        Base b;  // 编译错误:抽象类不能实例化
        return 0;
    }
  • 同时具备虚析构函数的特性纯虚析构函数本质是虚析构函数(即释放内存),因此在多态场景中,通过基类指针删除派生类对象时,会正确调用派生类析构函数,再调用基类析构函数(包括纯虚析构的定义),避免资源泄漏。

    示例:

    class Base {
    public:
        virtual ~Base() = 0;  // 纯虚析构函数(同时是虚析构)
    };
    Base::~Base() {  // 定义纯虚析构
        cout << "Base destructor called" << endl;
    }
    class Derived : public Base {
    private:
        int* data;
    public:
        Derived() : data(new int) {}
        ~Derived() override {  // 派生类析构(自动为虚函数)
            delete data;
            cout << "Derived destructor called" << endl;
        }
    };
    int main() {
        Base* ptr = new Derived();  // 基类指针指向派生类对象
        delete ptr;  // 多态删除
        return 0;
    }

    输出结果:

    Derived destructor called  // 先调用派生类析构
    Base destructor called    // 再调用基类纯虚析构的定义

注意和构造函数的继承,不一样,构造是先调用基类,基类调用完成,再来调用派生类的构造函数。

此外,还需要注意:构造函数,是不存在“虚”的状态,更不存在“纯虚”状态。

2.3. 为什么需要纯虚析构函数?

主要用于两种场景的结合:

  • 需要将类定义为抽象类(禁止实例化),但类中没有其他适合作为纯虚函数的接口(即类的核心逻辑不需要其他纯虚函数);
  • 同时需要确保析构函数的多态性(避免派生类资源泄漏)。

例如,一个作为接口的基类,仅需要派生类实现构造逻辑,而无需其他接口,但又必须禁止基类实例化,此时可用纯虚析构函数。

4. 常见误区
  • 误以为纯虚析构函数可以不定义:普通纯虚函数(如virtual void f() = 0)可以不定义(由派生类实现),但纯虚析构函数必须定义。因为派生类的析构函数会自动调用基类析构函数,若基类纯虚析构无定义,链接时会报错(未找到函数实现)。
  • 混淆纯虚析构与普通虚析构:纯虚析构是 “纯虚 + 虚析构” 的结合,既让类抽象,又保证析构多态;普通虚析构仅保证析构多态,不使类抽象。

三、抽象类与纯虚析构函数的关系

  • 纯虚析构函数是抽象类的一种特殊情况:包含纯虚析构函数的类必然是抽象类;
  • 抽象类不一定需要纯虚析构函数:多数抽象类通过其他纯虚函数(如area()run())成为抽象类,仅在特殊场景下使用纯虚析构。

总结

  • 抽象类是含纯虚函数的类,用于定义接口,禁止实例化,需派生类实现接口;
  • 纯虚析构函数是特殊的纯虚函数,既使类成为抽象类,又保证析构多态性,且必须提供定义
  • 两者结合可满足 “接口抽象 + 析构安全” 的设计需求,是 C++ 多态和资源管理的重要工具。
posted @ 2025-12-09 17:41  yangykaifa  阅读(4)  评论(0)    收藏  举报