跳转至

C++ OOP 面试高频知识点 - 10

抽象类、纯虚函数、接口实现方式

1. 抽象类的基本概念

抽象类(Abstract Class) 是包含至少一个纯虚函数的类。抽象类不能直接实例化,但可以作为基类使用,用于定义接口和实现多态。

2. 纯虚函数的定义

纯虚函数是在虚函数声明后面加 = 0 的函数。

class AbstractClass {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
};

// AbstractClass obj; // 错误:抽象类不能直接实例化

3. 抽象类的继承和实现

子类必须实现所有的纯虚函数,否则子类也会成为抽象类。

class AbstractShape {
public:
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
};

class Rectangle : public AbstractShape {
public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() const override {
        return width * height;
    }

    double perimeter() const override {
        return 2 * (width + height);
    }

private:
    double width, height;
};

class Circle : public AbstractShape {
public:
    Circle(double r) : radius(r) {}

    double area() const override {
        return 3.14159 * radius * radius;
    }

    double perimeter() const override {
        return 2 * 3.14159 * radius;
    }

private:
    double radius;
};

4. 接口的实现方式

在 C++ 中,接口通常通过抽象类实现,这些类只包含纯虚函数,没有数据成员。

class Drawable {
public:
    virtual void draw() const = 0;
    virtual ~Drawable() = default; // 虚析构函数
};

class Movable {
public:
    virtual void move(int x, int y) = 0;
    virtual ~Movable() = default;
};

class Shape : public Drawable, public Movable {
    // 抽象类,因为没有实现所有纯虚函数
};

class Rectangle : public Shape {
public:
    Rectangle(int x, int y, int w, int h) 
        : x(x), y(y), width(w), height(h) {}

    void draw() const override {
        cout << "Drawing rectangle at (" << x << "," << y 
             << ") with size " << width << "x" << height << endl;
    }

    void move(int dx, int dy) override {
        x += dx;
        y += dy;
    }

private:
    int x, y;
    int width, height;
};

5. 抽象类的特性

5.1 不能实例化

抽象类不能直接创建对象,但可以作为指针或引用类型。

AbstractShape* shapePtr = new Rectangle(5, 10);
AbstractShape& shapeRef = *shapePtr;

shapePtr->area();
shapeRef.area();

delete shapePtr;

5.2 虚析构函数

抽象类应该有虚析构函数,以确保通过基类指针删除子类对象时能正确调用子类的析构函数。

class AbstractClass {
public:
    virtual void foo() = 0;
    virtual ~AbstractClass() = default; // 虚析构函数
};

class Derived : public AbstractClass {
public:
    Derived() { data = new int[100]; }
    ~Derived() { delete[] data; }

    void foo() override {}

private:
    int* data;
};

int main() {
    AbstractClass* ptr = new Derived();
    delete ptr; // 正确调用 Derived 的析构函数
    return 0;
}

5.3 可以有非纯虚函数

抽象类可以包含非纯虚函数,这些函数可以有默认实现。

class AbstractClass {
public:
    virtual void pureVirtual() = 0;

    void concreteFunction() {
        cout << "Concrete function implementation" << endl;
    }
};

class Derived : public AbstractClass {
public:
    void pureVirtual() override {
        cout << "Implementation of pure virtual function" << endl;
    }
};

int main() {
    AbstractClass* ptr = new Derived();
    ptr->pureVirtual();
    ptr->concreteFunction();

    delete ptr;
    return 0;
}

6. 抽象类的用途

6.1 定义接口

抽象类可以用于定义接口,确保所有实现类都包含必要的方法。

class DatabaseConnection {
public:
    virtual void connect() = 0;
    virtual void disconnect() = 0;
    virtual void executeQuery(const string& query) = 0;
};

6.2 实现多态

抽象类是实现多态的重要方式,它允许我们通过基类指针或引用处理不同类型的对象。

void drawAll(const vector<Drawable*>& objects) {
    for (auto obj : objects) {
        obj->draw();
    }
}

int main() {
    vector<Drawable*> shapes;
    shapes.push_back(new Rectangle(10, 20, 5, 5));
    shapes.push_back(new Circle(15, 15, 3));

    drawAll(shapes);

    for (auto obj : shapes) {
        delete obj;
    }

    return 0;
}

6.3 代码复用

通过抽象类,可以实现代码复用,让多个类继承同一个基类的默认实现。

class AbstractLogger {
public:
    virtual void log(const string& message) = 0;

    void logError(const string& message) {
        log("ERROR: " + message);
    }

    void logInfo(const string& message) {
        log("INFO: " + message);
    }
};

class ConsoleLogger : public AbstractLogger {
public:
    void log(const string& message) override {
        cout << message << endl;
    }
};

7. 常见问题和回答

问题 1:抽象类和接口的区别?

在 C++ 中,接口通常指包含纯虚函数的抽象类,但抽象类可以包含非纯虚函数和数据成员。

  • 抽象类:可以包含纯虚函数和非纯虚函数,以及数据成员
  • 接口:通常只包含纯虚函数,没有数据成员,用于定义契约

问题 2:为什么要使用抽象类?

  1. 定义接口,确保实现类遵循契约
  2. 实现多态,提高代码复用性
  3. 隐藏实现细节,提高代码安全性

问题 3:可以有抽象类的对象数组吗?

不可以,但可以有抽象类指针或引用的数组。

AbstractShape* shapes[3];
shapes[0] = new Rectangle(1, 2);
shapes[1] = new Circle(3);
shapes[2] = new Triangle(4, 5, 6);

问题 4:纯虚函数可以有默认实现吗?

在 C++ 中,纯虚函数可以有默认实现,但需要在类外部定义。

class AbstractClass {
public:
    virtual void foo() = 0;
};

void AbstractClass::foo() {
    cout << "Default implementation" << endl;
}

class Derived : public AbstractClass {
public:
    void foo() override {
        AbstractClass::foo(); // 调用默认实现
    }
};

8. 最佳实践

  1. 定义清晰的接口:抽象类应该只包含接口,避免包含实现细节
  2. 使用虚析构函数:确保正确的内存管理
  3. 保持简洁:抽象类应该保持简洁,只包含必要的方法
  4. 明确命名:接口方法应该有明确的含义,表达清晰的意图

总结

抽象类是 C++ 中实现接口和多态的重要工具。通过包含纯虚函数,抽象类定义了其他类必须遵循的契约。抽象类不能直接实例化,但可以作为基类使用,实现代码复用和多态行为。

正确使用抽象类可以提高代码的可维护性、可扩展性和安全性,使我们的程序更加模块化。


练习建议: 1. 设计一个图形系统,包含抽象类和多个具体实现 2. 实现一个日志系统,使用抽象类定义接口 3. 测试抽象类的多态行为 4. 验证虚析构函数的重要性