在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据所指对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。
注意:如果子类没有重写虚函数,那么子类对象仍然有虚指针,虚指针指向的是基类的虚函数表!
纯虚函数(Pure Virtual Function)是C++中的一个特殊类型的虚拟函数,它在基类中声明但没有定义。纯虚函数的声明使用virtual
关键字,并在函数声明的末尾添加= 0
来表示它是一个纯虚函数。子类(派生类)必须提供纯虚函数的实际实现,否则子类也会被标记为抽象类,无法创建对象。
- class Shape {
- public:
- // 声明纯虚函数
- virtual void draw() = 0;
-
- // 普通成员函数
- void displayInfo() {
- // 这里可以包含一些通用的代码
- std::cout << "This is 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;
- }
- };
-
- int main() {
- Circle circle;
- Square square;
-
- circle.displayInfo(); // 调用基类函数
- circle.draw(); // 调用派生类函数
-
- square.displayInfo(); // 调用基类函数
- square.draw(); // 调用派生类函数
-
- return 0;
- }
在上述示例中,Shape
类包含一个纯虚函数draw()
,因此Shape
类本身是一个抽象类,不能创建它的对象。然后,Circle
和Square
类都继承自Shape
类,并必须提供对draw()
的实际实现。这种机制允许多态性(Polymorphism)的实现,允许不同的派生类以不同的方式实现相同的虚拟函数。
根据上图举例分析:
- #include
- #include
- using namespace std;
- class A {
- public:
- virtual void prints() {
- cout << "A::prints" << endl;
- }
- A() {
- cout << "A:构造函数" << endl;
- }
- };
- class B:public A {
- public:
- virtual void prints() {
- cout << "B::prints" << endl;
- }
- B() {
- cout << "B:构造函数" << endl;
- }
- };
- class C :public A {
- public:
-
- };
- int main() {
- A *b = new B();
- b->prints();
- b = new C();
- b->prints();
- return 0;
- }
在基类中声明虚函数时,使用virtual
关键字。这告诉编译器将该函数视为虚函数,它可以在派生类中被覆盖(重写)。
派生类中的虚函数覆盖基类中的虚函数时,必须使用override
关键字,以确保正确的函数被覆盖。这也使得代码更容易阅读和维护
当您使用基类指针或引用调用虚函数时,编译器不会在编译时决定要调用哪个函数版本。相反,它会在运行时根据对象的实际类型来选择正确的函数版本。
这个过程大致如下:
总结一下,多态的原理基于虚函数和虚表,它允许在运行时根据对象的实际类型来选择要调用的函数版本,从而实现了面向对象编程中的灵活性和可扩展性。多态性是C++和其他面向对象编程语言的核心概念之一,有助于构建可维护和可扩展的软件系统。
由于类的多态性,通常通过父类指针或引用来操作子类对象。因为多态允许我们以统一的方式处理不同的派生类对象,并且在运行时确定要调用的方法。
如果析构函数不被声明为虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样会造成派生类析构不完全,造成内存泄漏。
这种行为是为了确保资源的正确释放。由于我们只知道父类的类型,编译器无法确定指针指向的是哪个子类对象,因此只能调用父类的析构函数来释放资源。
- #include<iostream>
- #include<vector>
- using namespace std;
- class A {
- public:
- virtual void prints() {
- cout << "A::prints" << endl;
- }
- A() {
- cout << "A:构造函数" << endl;
- }
- virtual ~A() {
- cout << "A:析构函数 " << endl;
- }
- };
- class B:public A {
- public:
- virtual void prints() {
- cout << "B::prints" << endl;
- }
- B() {
- cout << "B:构造函数" << endl;
- }
- ~B() {
- cout << "B:析构函数 " << endl;
- }
- };
- int main() {
- A *b = new B();
- b->prints();
- delete b;
- b = NULL;
- return 0;
- }
- #include<iostream>
- #include<vector>
- using namespace std;
- class A {
- public:
- virtual void prints() {
- cout << "A::prints" << endl;
- }
- A() {
- cout << "A:构造函数" << endl;
- }
- virtual ~A() {
- cout << "A:析构函数 " << endl;
- }
- };
- class B:public A {
- public:
- virtual void prints() {
- cout << "B::prints" << endl;
- }
- B() {
- cout << "B:构造函数" << endl;
- }
- ~B() {
- cout << "B:析构函数 " << endl;
- }
- };
- int main() {
- A *b = new B();
- b->prints();
- delete b;
- b = NULL;
- return 0;
- }
分析:可以看到析构函数是,先从子类析构,再到父类析构