面试八股文:C++ 多态 继承 重载 虚函数

C++ 支持多态、继承和函数重载,这些是面向对象编程(OOP)的基本概念。以下是这些概念的简要介绍:

  1. 多态(Polymorphism)

    • 多态是面向对象编程的核心概念之一,它允许不同的子类对象对相同的方法做出不同的响应。
    • C++ 支持两种多态:编译时多态(静态多态)和运行时多态(动态多态)。
    • 编译时多态是通过函数重载实现的,根据函数参数的类型或数量来确定调用哪个版本的函数。
    • 运行时多态是通过虚函数(virtual function)和继承实现的,允许子类重写父类的虚函数,并根据对象的实际类型来调用适当的函数。
  2. 继承(Inheritance)

    • 继承是面向对象编程的另一个重要概念,允许创建新类,该类继承了现有类的属性和行为。
    • C++ 支持单继承和多继承。单继承表示一个类只能从一个基类派生,而多继承允许一个类从多个基类派生。
    • 派生类可以访问基类的公共和受保护成员,可以重写虚函数,或添加新的成员和方法。
  3. 函数重载(Function Overloading)

    • 函数重载是一种编程技巧,允许在同一个作用域内定义多个具有相同名称但不同参数列表的函数。
    • C++ 根据函数参数的数量和类型来区分重载的函数。
    • 函数重载使代码更加灵活,可以根据不同的参数来选择不同的函数实现。
  4. #include 
    
    class Shape {
    public:
        virtual void draw() {
            std::cout << "Drawing a shape" << std::endl;
        }
    };
    
    class Circle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a circle" << std::endl;
        }
    };
    
    class Square : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a square" << std::endl;
        }
    };
    
    void draw(Shape* shape) {
        shape->draw();
    }
    
    int main() {
        Shape s;
        Circle c;
        Square sq;
    
        draw(&s);  // 调用基类的 draw
        draw(&c);  // 调用派生类的 draw
        draw(&sq); // 调用派生类的 draw
    
        return 0;
    }
    

    在上面的示例中,Shape 是基类,CircleSquare 是派生类,它们都重写了 draw 虚函数。在 main 函数中,使用多态性来调用不同类型的对象的 draw 方法,它会根据实际对象的类型来选择正确的函数实现。这是多态的一个示例,同时还涵盖了继承和虚函数。

虚函数 的实现原理

虚函数的实现原理涉及到虚函数表(Virtual Function Table,通常缩写为 vtable)和虚函数指针(vptr)。虚函数是实现多态(Polymorphism)的核心机制,允许在运行时确定调用哪个函数,而不是在编译时确定。

下面是虚函数的实现原理:

  1. 虚函数表(vtable)

    • 每个包含虚函数的类都有一个对应的虚函数表。虚函数表是一个特殊的数据结构,包含了类中的虚函数的地址。
    • 虚函数表通常是一个数组,每个元素对应一个虚函数。每个类的实例都包含一个指向其虚函数表的指针(vptr)。
    • 当类被实例化时,vptr 会被初始化为指向正确的虚函数表。
  2. 虚函数指针(vptr)

    • 每个类的实例中都包含一个虚函数指针(vptr),它指向该类的虚函数表。
    • 虚函数指针通常位于对象的内存布局中的开头位置,以便快速访问。
    • 当调用一个虚函数时,实际上是通过虚函数指针找到正确的虚函数表,并从中获取要调用的函数的地址。
#include 

class Base {
public:
    virtual void print() {
        std::cout << "Base::print()" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived::print()" << std::endl;
    }
};

int main() {
    Base* basePtr;
    Base baseObj;
    Derived derivedObj;

    basePtr = &baseObj;  // 指向基类对象
    basePtr->print();    // 调用 Base::print()

    basePtr = &derivedObj;  // 指向派生类对象
    basePtr->print();       // 调用 Derived::print()

    return 0;
}

继承的方法(Method Overriding)

  • 继承的方法是指在派生类中重新定义(重写)基类的方法。
  • 继承的方法不依赖于关键字(如 virtual),而只是通过函数名和参数列表匹配来实现方法的重写。
  • 重写继承的方法不要求使用关键字 override(C++11及以后版本支持),但强烈建议使用,以提高代码可读性和可维护性。
  • 继承的方法允许子类提供自己的实现,但不一定需要与基类方法相同的名称或参数列表。
class Base {
public:
    void print() {
        std::cout << "Base::print()" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived::print()" << std::endl;
    }
};

虚函数(Virtual Function)

  • 虚函数是一种特殊的成员函数,用关键字 virtual 声明。它是实现多态的关键,因为它允许在运行时动态选择要调用的函数实现。
  • 基类中的虚函数可以被派生类重写,然后通过指向基类的指针或引用来调用派生类的方法,实现多态。
  • 虚函数通常用于实现多态,而继承的方法可以是非虚函数。
  • class Base {
    public:
        virtual void print() {
            std::cout << "Base::print()" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void print() override {
            std::cout << "Derived::print()" << std::endl;
        }
    };
    

    虚函数中的重写:

  • 在 C++ 中,子类中的虚函数不一定要被重写。虚函数的重写(或覆盖)是一种可选操作,取决于子类的需求。以下是关于虚函数重写的一些要点:

  • 虚函数的重写

    • 基类中的虚函数可以被子类重写,以提供子类自己的实现。
    • 子类中的重写虚函数的名称、参数列表和返回类型必须与基类中的虚函数相同。这是 C++ 的函数签名匹配规则。
  • 非重写虚函数

    • 如果子类不重写基类中的虚函数,子类将继承基类的虚函数实现。
    • 子类可以选择在需要时调用基类的虚函数实现,或者完全替换它。
  • 纯虚函数

    • 基类可以声明纯虚函数,它没有默认的实现,而是在派生类中必须被重写。
    • 子类必须提供纯虚函数的实现,否则它也将成为一个抽象类,不能被实例化。
class Base {
public:
    virtual void virtualFunction() = 0; // 纯虚函数
};

class Derived : public Base {
public:
    void virtualFunction() override {
        // 重写纯虚函数
    }
};

虚函数重写和普通函数重写的区别

虚函数重写(Virtual Function Override)

  • 虚函数是在基类中声明并标记为虚函数(使用 virtual 关键字)的成员函数。
  • 子类可以选择是否重写虚函数,使用 override 关键字来明确表示重写,但不是必须的。
  • 虚函数实现可以根据对象的实际类型在运行时进行动态绑定,即调用适当的虚函数版本。这称为多态。
  • 通过基类指针或引用调用虚函数时,实际调用的是对象的实际类型的虚函数版本。
  • class Base {
    public:
        virtual void print() {
            std::cout << "Base::print()" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void print() override {
            std::cout << "Derived::print()" << std::endl;
        }
    };
    
    Base* obj = new Derived();
    obj->print(); // 调用 Derived::print(),多态行为
    

    普通函数重写(Non-virtual Function Override)

  • 普通函数是在基类和子类中声明的非虚函数,没有使用 virtual 关键字。
  • 子类中的普通函数可以与基类中的函数具有相同的名称和参数列表,但不会自动触发多态行为。
  • 调用普通函数时,只会根据引用或指针的类型来决定使用哪个函数版本,不会动态绑定到对象的实际类型。
    class Base {
    public:
        void print() {
            std::cout << "Base::print()" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        void print() {
            std::cout << "Derived::print()" << std::endl;
        }
    };
    
    Base* obj = new Derived();
    obj->print(); // 调用 Base::print(),没有多态行为
    

    总结:

  • 虚函数重写用于实现多态,允许在运行时动态绑定到对象的实际类型,实现灵活的方法调用。
  • 普通函数重写没有多态行为,根据引用或指针的类型来确定使用哪个函数版本,通常用于普通方法覆盖和重载。

你可能感兴趣的:(面试,c++,职场和发展)