一、多态的条件
1. 定义
多态允许通过基类指针或引用,调用派生类对象的特定方法,实现“一个接口,多种实现”。
2. 条件
- 基类必须定义虚函数(
virtual
):虚函数是多态的核心机制。 - 派生类必须重写(覆盖)基类的虚函数:派生类提供虚函数的具体实现(虚函数的重写要求继承父子关系的两个虚函数,函数,参数,返回值相同)。
- 必须通过基类指针或引用调用虚函数:直接通过对象调用虚函数不触发多态(看的是基类指针指向的内容,如果是父类就调用父类的函数,子类就调用子类的函数)。
- 注意:派生类重写可以不加virtual但是父类必须加 因为本质是重写了父类的实现,注意是重写了父类的实现,也就是说本质还是调用父类函数,但是内容变成了调用子类的函数内容
3. 示例
class Animal {
public:virtual void speak() { cout << "Animal speaks" << endl; } // 虚函数
};class Dog : public Animal {
public:void speak() override { // 重写基类虚函数(C++98 无需写 override)cout << "Dog barks" << endl;}
};int main() {Animal* animal = new Dog();animal->speak(); // 输出 "Dog barks"(多态)delete animal;return 0;
}
二、重写与隐藏
1. 重写(覆盖)
- 定义:派生类重新定义基类的虚函数,函数签名(函数名、参数列表、返回类型)必须一致(协变除外)。
- 示例:
class Base { public:virtual void func(int x) { cout << "Base::func(int)" << endl; } };class Derived : public Base { public:void func(int x) { cout << "Derived::func(int)" << endl; } // 重写 };
2. 隐藏(重定义)
- 定义:派生类定义与基类同名的非虚函数(无论参数是否相同),将隐藏基类的同名函数。
- 示例:
class Base { public:void func(int x) { cout << "Base::func(int)" << endl; } };class Derived : public Base { public:void func(double x) { cout << "Derived::func(double)" << endl; } };int main() {Derived d;d.func(1); // 调用 Derived::func(double)(隐藏基类 func(int))d.Base::func(1); // 显式调用基类方法return 0; }
三、抽象类
1. 定义
- 抽象类包含至少一个 纯虚函数,不能实例化。
- 语法:
virtual 返回类型 函数名(参数) = 0;
- 如果派生类继承了父类,而父类是一个抽象类则派生类也无法实例化,但是如果重写了纯虚函数的话,就能够实例化了,也就是说,纯虚函数间接的强制了派生类去重写这个虚函数
- 实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实 现。
- 接口继承:虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成 多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数
2. 示例
class Shape { // 抽象类
public:virtual double area() const = 0; // 纯虚函数
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}double area() const override { // 必须实现纯虚函数return 3.14 * radius * radius;}
};int main() {// Shape s; // 错误:抽象类不能实例化Shape* shape = new Circle(5.0);cout << shape->area(); // 输出圆的面积delete shape;return 0;
}
四、虚函数表
1. 虚函数表
- 每个有虚函数的类都有一个虚函数表,表中存储该类的虚函数地址。
- 虚函数表在编译时生成,每个类的虚函数表唯一。
2. 虚表指针
- 每个对象内部包含一个指向虚函数表的指针,在对象构造时初始化。
- 调用虚函数时,通过指针找到虚函数表,再通过偏移量调用具体函数。
-
注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。
五、override与final
1. override 关键字
作用:
- 显式标记派生类中的虚函数是对基类虚函数的重写。
-
修饰派生类的虚函数,检查是否完成重写,如果没完成重写就会报错。
- 编译器检查:若函数签名与基类虚函数不一致(如参数、常量性不同),会报错,避免错误的重写。
示例:
class Base {
public:virtual void print() const {std::cout << "Base" << std::endl;}
};class Derived : public Base {
public:// 正确:签名一致,重写基类虚函数void print() const override {std::cout << "Derived" << std::endl;}
};class IncorrectDerived : public Base {
public:// 错误:参数不一致,override 触发编译错误void print(int) const override {std::cout << "IncorrectDerived" << std::endl;}
};
2. final 关键字
作用:
- 修饰类:禁止类被继承。
- 修饰虚函数:禁止派生类进一步重写该函数。
- 可同时与
override
使用(顺序无关,如override final
)。 - 修饰类时,放在类名后(
class Base final {};
)。 - 修饰函数时,放在函数参数列表后(
void foo() final {}
)。
示例:
修饰类:
class Base final {}; // Base 不能被继承// 错误:尝试继承 final 类
class Derived : public Base {};
修饰虚函数:
class Base {
public:virtual void foo() final {} // 基类虚函数标记为 final
};class Derived : public Base {
public:// 错误:无法重写 final 函数void foo() override {}
};