面试题答案
一键面试1. 设计类层次结构和虚函数实现可扩展性、可维护性和高效性
- 类层次结构设计:
- 基类设计:在图形绘制系统中,创建一个基形状类
Shape
。这个基类应包含所有形状共有的属性和方法,例如颜色、位置等属性,以及用于绘制和交互的虚函数接口。例如:
class Shape { public: Shape(int x, int y, const std::string& color) : x(x), y(y), color(color) {} virtual void draw() const = 0; virtual void interact() = 0; protected: int x; int y; std::string color; };
- 派生类设计:圆形、矩形等具体形状类从
Shape
类派生。在派生类中重写基类的虚函数,实现各自特定的绘制和交互逻辑。例如圆形类:
矩形类同理:class Circle : public Shape { public: Circle(int x, int y, const std::string& color, int radius) : Shape(x, y, color), radius(radius) {} void draw() const override { // 具体的圆形绘制逻辑 std::cout << "Drawing a circle at (" << x << ", " << y << ") with color " << color << " and radius " << radius << std::endl; } void interact() override { // 圆形的交互逻辑 std::cout << "Interacting with a circle at (" << x << ", " << y << ") with color " << color << " and radius " << radius << std::endl; } private: int radius; };
class Rectangle : public Shape { public: Rectangle(int x, int y, const std::string& color, int width, int height) : Shape(x, y, color), width(width), height(height) {} void draw() const override { // 具体的矩形绘制逻辑 std::cout << "Drawing a rectangle at (" << x << ", " << y << ") with color " << color << " and width " << width << ", height " << height << std::endl; } void interact() override { // 矩形的交互逻辑 std::cout << "Interacting with a rectangle at (" << x << ", " << y << ") with color " << color << " and width " << width << ", height " << height << std::endl; } private: int width; int height; };
- 基类设计:在图形绘制系统中,创建一个基形状类
- 利用多态实现绘制和交互功能:
- 多态的使用:通过指针或引用实现多态。例如,当需要绘制一组形状时,可以这样做:
这里通过std::vector<Shape*> shapes; shapes.push_back(new Circle(10, 10, "red", 5)); shapes.push_back(new Rectangle(20, 20, "blue", 10, 5)); for (const auto& shape : shapes) { shape->draw(); } for (const auto& shape : shapes) { delete shape; }
Shape*
指针调用draw
函数,根据实际对象的类型(圆形或矩形),调用相应的draw
实现,实现了多态。这种方式使得代码具有很好的可扩展性,当需要添加新的形状时,只需创建新的派生类并实现虚函数,而不需要修改绘制和交互的主逻辑代码,提高了可维护性。同时,由于虚函数表机制,运行时的函数调用开销相对较小,保证了一定的高效性。
2. 不满足某个必要条件的负面影响
- 不满足虚函数:
- 问题:如果基类的
draw
和interact
函数不是虚函数,那么通过基类指针或引用调用这些函数时,将始终调用基类版本的函数,而不会调用派生类中重写的版本。这就失去了多态性,使得每个形状都不能按照自己的逻辑进行绘制和交互。例如,在上述代码中,如果Shape
类的draw
函数不是虚函数:
那么在调用class Shape { public: Shape(int x, int y, const std::string& color) : x(x), y(y), color(color) {} void draw() const { std::cout << "Base shape draw" << std::endl; } virtual void interact() = 0; protected: int x; int y; std::string color; };
shape->draw()
时,无论shape
指向的是圆形还是矩形,都只会输出Base shape draw
,无法实现每个形状特定的绘制逻辑。这将导致代码的可扩展性和可维护性极差,每次添加新形状都需要在调用处添加大量的条件判断来区分不同形状的绘制逻辑。 - 问题:如果基类的
- 不满足继承:
- 问题:如果没有继承关系,每个形状类将是独立的,无法从基类中继承共同的属性和方法,代码会出现大量重复。例如,圆形和矩形都需要自己实现颜色、位置等属性的管理,以及类似的绘制和交互接口,这大大增加了代码量,降低了可维护性。而且,由于没有继承关系,无法通过基类指针或引用统一管理不同形状,也就无法实现多态,使得代码在处理多种形状时变得非常复杂和低效。
- 不满足指针或引用:
- 问题:如果不使用指针或引用,而是直接使用对象,那么在编译时就会确定调用的函数版本,不会发生动态绑定。例如:
这里Shape shape(10, 10, "green"); Circle circle(10, 10, "red", 5); shape = circle; shape.draw();
shape.draw()
调用的仍然是Shape
类的draw
函数,因为shape
是Shape
类型的对象,而不是Circle
类型。只有通过指针或引用,如Shape* ptr = &circle; ptr->draw();
,才能实现运行时根据实际对象类型调用正确的函数版本,从而实现多态。如果不使用指针或引用,将无法充分利用C++的多态特性,限制了代码的灵活性和可扩展性。