final和override的作用?final为什么能提高代码执行效率?
override:保证在派生类中声明的重载函数,与基类的虚函数有相同的签名,作用就是用于编译期代码检查。
final:阻止类的进一步派生和虚函数的进一步重写,同时也是一种名为去虚拟化的优化技巧,相当于把运行期多态转换为了编译期多态,提高了执行效率。
final 和 override 的作用:
- 
final:- 在 C++11 引入了 
final关键字,它有两个主要作用:- 防止继承:当 
final修饰一个类时,表示这个类不能被继承。例如:class Base { // 基类 }; class Derived final : public Base { // 错误,不能从 `Derived` 类继承 };
 - 防止重写:当 
final修饰一个虚函数时,表示该函数不能在派生类中被重写。例如:class Base { public: virtual void func() final { // 不能被重写 std::cout << "Base func" << std::endl; } }; class Derived : public Base { public: // 错误:无法重写 `func` 因为它被标记为 `final` void func() override { std::cout << "Derived func" << std::endl; } };
 
 - 防止继承:当 
 
 - 在 C++11 引入了 
 - 
override:override是用于显式标明某个函数是重写基类的虚函数。这样做可以让编译器检查这个函数是否确实重写了一个虚函数。- 如果函数没有正确重写基类中的虚函数(比如拼写错误、签名不匹配等),编译器会报错。
class Base { public: virtual void func() { std::cout << "Base func" << std::endl; } }; class Derived : public Base { public: void func() override { // 明确表示重写了 `Base` 类的 `func` 方法 std::cout << "Derived func" << std::endl; } };
 
 
为什么 final 能提高代码执行效率?
- 
避免虚函数查找:
final表示该类或虚函数不允许被继承或重写。这样编译器在生成代码时,可以使用更高效的方式来调用函数,而不需要进行虚函数查找。- 对于一个类中的虚函数,编译器通常会使用虚函数表(VTable)来动态查找对应的函数。而 
final标记的类或函数,编译器知道它不会再被继承或重写,因此可以直接调用函数,而不需要查找虚函数表,从而提高了运行时效率。 
 - 
优化机会:
- 对于 
final类,编译器可以进行更激进的优化,因为它知道该类无法被扩展。 - 对于 
final虚函数,编译器可以直接调用函数而不使用虚表查找。也就是说,函数调用会变成普通的静态函数调用,从而去除了虚拟调用的开销。 
 - 对于 
 
示例代码:
#include <iostream>
class Base {
public:
    virtual void func() final { // `final` 防止重写
        std::cout << "Base func" << std::endl;
    }
};
class Derived : public Base {
public:
    // 错误:不能重写 `func` 因为它是 `final`
    void func() override {
        std::cout << "Derived func" << std::endl;
    }
};
int main() {
    Base* obj = new Base();
    obj->func(); // 调用 Base 类的 func,虚函数调用会被优化为静态调用
    delete obj;
    return 0;
}总结:final 可以减少继承和重写的开销,使得编译器可以进行更多优化,从而提高代码执行效率。
在 C++ 中,虚函数的实现依赖于 虚函数表(VTable) 和 虚函数指针(VPtr) 机制。这个机制是 C++ 支持多态性和动态绑定的关键。
虚函数在内存中的实现:
- 
虚函数表(VTable):
- 每个含有虚函数的类都会有一个虚函数表(VTable)。虚函数表是一个指向类的虚函数的指针数组。它包含了该类所有虚函数的地址。每个类只有一张虚函数表,除非它有虚函数被重写。
 - 如果一个类没有虚函数,编译器就不会创建虚函数表。
 
 - 
虚函数指针(VPtr):
- 每个对象实例都有一个指向虚函数表的指针(VPtr)。这个指针通常作为对象的一个隐藏成员存储在对象的内存布局中。每个对象实例会有自己的 VPtr,指向其类的虚函数表。
 
 
内存布局:
- 假设我们有一个类 
Base,它含有一个虚函数func。每个Base对象都含有一个虚函数指针(VPtr),指向该类的虚函数表(VTable)。如果类Derived继承自Base并重写了func,则Derived对象也会包含自己的 VPtr,但该 VPtr 会指向Derived类的虚函数表。 
示例:
#include <iostream>
class Base {
public:
    virtual void func() {
        std::cout << "Base func" << std::endl;
    }
};
class Derived : public Base {
public:
    void func() override {
        std::cout << "Derived func" << std::endl;
    }
};
int main() {
    Base* base = new Derived();
    base->func();  // 调用 Derived::func,使用虚函数机制
    delete base;
    return 0;
}内存分析:
- 
虚函数表(VTable):
Base类的虚函数表会包含指向Base::func的指针。Derived类的虚函数表会包含指向Derived::func的指针(即使Derived类重写了func,它会覆盖Base类的虚函数)。
 - 
虚函数指针(VPtr):
- 对于 
Base类型的指针base指向Derived类对象时,base对象的 VPtr 会指向Derived类的虚函数表,而不是Base类的虚函数表。 - 这样,当调用 
base->func()时,实际调用的是Derived::func,而不是Base::func,这是因为base的 VPtr 指向的是Derived类的虚函数表。 
 - 对于 
 
内存结构示意:
假设我们有如下类结构:
class Base {
public:
    virtual void func() {
        std::cout << "Base func" << std::endl;
    }
};
class Derived : public Base {
public:
    void func() override {
        std::cout << "Derived func" << std::endl;
    }
};Base类的虚函数表(VTable):Base::func的地址。
Derived类的虚函数表(VTable):Derived::func的地址。
对于一个 Derived 对象:
- 它的内存结构可能是:
- VPtr(指向 
Derived类的虚函数表)。 - 成员变量(如果有的话)。
 
 - VPtr(指向 
 
在 main 函数中,创建了一个 Base* 类型的指针 base,指向一个 Derived 对象。在调用 base->func() 时,实际执行的是 Derived::func(),而不是 Base::func(),因为 base 的 VPtr 指向的是 Derived 类的虚函数表。
为什么使用虚函数表:
- 多态性:虚函数表允许动态绑定,支持在运行时决定调用哪个函数。即使你使用基类指针或引用,程序也会根据对象的实际类型来调用正确的函数。
 - 性能代价:虽然虚函数提供了多态性,但它们引入了一定的性能开销,因为每次调用虚函数时,都需要通过虚函数表进行查找。
 
虚函数的内存开销:
- VTable:每个含有虚函数的类需要一个虚函数表。虚函数表的大小通常等于类中虚函数的个数,每个虚函数的表项通常是一个指向该虚函数的指针。
 - VPtr:每个包含虚函数的对象会有一个指向虚函数表的指针,通常这个指针是对象内存布局的一部分。
 
结论:
虚函数的内存实现依赖于虚函数表和虚函数指针机制。虚函数表是一个包含虚函数地址的数组,每个对象会有一个指向该表的指针。通过这种方式,C++ 实现了多态性和动态绑定。虽然引入了一些内存开销和执行时的查找开销,但它使得 C++ 可以支持运行时的多态性特性。
虚函数的定义和作用:
虚函数是 C++ 中的一种成员函数,它允许在派生类中重写(覆盖)基类中的函数,并通过基类指针或引用来调用派生类的函数。这种特性是 C++ 支持 多态性(polymorphism)的核心之一。
关键点:
- 虚函数是基类中的函数,通常使用 
virtual关键字标记。 - 动态绑定:虚函数调用是在运行时根据对象的实际类型决定的,而不是编译时根据指针或引用的类型决定。
 - 虚函数允许派生类对基类的函数进行 重写。
 
为什么需要虚函数?
C++ 中的多态性是通过虚函数实现的。通过虚函数,程序可以根据对象的实际类型(而非指针或引用的类型)来调用合适的函数。这使得我们可以用相同的接口(基类指针或引用)操作不同类型的对象。
语法示例:
#include <iostream>
class Base {
public:
    // 声明虚函数
    virtual void func() {
        std::cout << "Base class function" << std::endl;
    }
    virtual ~Base() {}  // 虚析构函数
};
class Derived : public Base {
public:
    // 重写基类的虚函数
    void func() override {
        std::cout << "Derived class function" << std::endl;
    }
};
int main() {
    Base* basePtr = new Derived();  // 基类指针指向派生类对象
    basePtr->func();  // 调用的是 Derived 类的 func
    delete basePtr;  // 动态分配内存需要手动释放
    return 0;
}输出:
Derived class function
解释:
- 
虚函数的声明:
- 在 
Base类中,func函数被声明为虚函数,意味着我们可以在派生类中重写它。 
 - 在 
 - 
动态绑定:
- 在 
main函数中,basePtr是一个指向Base类的指针,但它指向的是Derived类的对象。 - 当我们调用 
basePtr->func()时,实际上调用的是Derived类中重写的func()函数,而不是Base类中的func()。 - 这种通过指针或引用来决定调用哪个函数的行为叫做 动态绑定(或称为 后期绑定)。
 
 - 在 
 - 
多态性:
- 通过虚函数,我们可以在运行时根据对象的实际类型来选择调用哪个函数。这就是 多态性 的核心,它使得程序可以处理不同类型的对象,而不需要显式地检查对象的类型。
 
 
为什么使用虚函数?
- 
实现多态性:
- 虚函数使得在使用基类指针或引用时,可以调用派生类中重写的函数。这样,我们可以写出更加通用和灵活的代码。例如,在处理不同类型的对象时,可以通过基类接口统一进行操作,而不关心对象的具体类型。
 
 - 
允许派生类扩展和修改基类的行为:
- 通过虚函数,派生类可以修改基类中方法的行为(即重写基类的虚函数)。这使得我们能够通过继承和重写的方式,定制对象的行为。
 
 
虚函数的内存开销:
- 每个类(如果它有虚函数)都需要一张 虚函数表(VTable),它是一个包含指向虚函数的指针的数组。虚函数表存储了该类的虚函数的地址。
 - 每个对象实例(如果是含有虚函数的类)会包含一个指向虚函数表的指针(即虚函数指针,VPtr)。这个指针指向类的虚函数表。
 - 这样,每次调用虚函数时,程序会通过虚函数指针查找虚函数表,并在运行时决定调用哪个版本的函数。
 
虚函数的总结:
- 定义:虚函数是基类中的函数,用 
virtual关键字声明,允许在派生类中被重写。 - 动态绑定:虚函数支持动态绑定,即在运行时根据对象的实际类型来选择调用哪个版本的函数。
 - 多态性:虚函数是实现 C++ 中多态性的关键。它使得基类指针或引用可以用于操作不同类型的对象,并自动调用正确的函数。
 - 内存开销:虚函数机制需要虚函数表和虚函数指针,这会带来一些内存开销和运行时性能损耗。
 
虚函数是面向对象编程中实现多态性、继承和扩展行为的基础工具,使得代码更具通用性和灵活性。