C++学习笔记 (六) ---- 多态与虚函数

①、多态的概念

先上一个示例

#include <iostream>
using namespace std;

//基类People
class People{
public:
    People(char *name, int age);
    void display();
protected:
    char *m_name;
    int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
    cout<<m_name<<m_age<<"岁了."<<endl;
}

//派生类Student
class Student: public People{
public:
    Student(char *name, int age, int salary);
    void display();
private:
    int m_score;
};
Student::Student(char *name, int age, int score): People(name, age), m_score(score){}
void Student::display(){
    cout<<m_name<<m_age<<"岁了,是一名学生,成绩:"<<m_score<<endl;
}

int main(){
    People *p = new People("李明", 16);
    p -> display();

    p = new Student("晓红", 15, 95);
    p -> display();

    return 0;
}

//运行结果
李明16岁了.
晓红15岁了.

如上,当基类指针 p 指向派生类 Student 的对象时,虽然使用的是 Student 的成员变量,但是却没有使用它的成员函数。所以为了避免这种错误的发生,引入了虚函数(virtual)。

使用时在函数前增加 virtual 关键字就行。上个示例中在基类的成员函数 display() 的声明前加上 virtual 即可。

      因为有了虚函数,所以基类指针在指向基类的时候就执行基类的操作函数,指向派生类的时候就使用派生类的成员函数,即 p->display() 这一条语句实现不同的操作,这种现象就叫做多态。虚函数的唯一作用就是构成多态。

②、引用也可以实现多态

引用的本质是通过指针实现的,所以引用也可以实现多态。

将上述例子中的 main() 函数内部改为引用的形式,结果也是一样

int main(){
    People p("李明", 16);
    Teacher t("晓红", 15, 95);
   
    People &rp = p;
    People &rt = t;
   
    rp.display();
    rt.display();

    return 0;
}

      多态的用途:通过基类指针对所有派生类(直接或间接)的成员变量和成员函数进行访问,要是没有多态,只能访问成员变量。在派生类比较多的情况下,要是不使用多态,就需要定义多个指针变量,很容易造成混乱;有了多态,就只需要一个指针就可以调用所有派生类中的虚函数。

③、虚函数

只需在基类的虚函数声明处加上 virtual 关键字即可;当在基类中定义了虚函数时,要是派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数;只有派生类的虚函数遮蔽基类的虚函数才能形成多态。

如:基类虚函数为 virtual void fun();,派生类虚函数为 virtual void fun(int);,那么基类指针指向派生类对象的时候,p->fun(100);将会出错,而语句 p->fun(); 将调用基类的函数。

构造函数不能是虚函数,析构函数可以声明为虚函数。

示例:

#include <iostream>
using namespace std;

//基类Base
class Base{
public:
    virtual void func();
    virtual void func(int);
};
void Base::func(){
    cout<<"void Base::func()"<<endl;
}
void Base::func(int n){
    cout<<"void Base::func(int)"<<endl;
}

//派生类Derived
class Derived: public Base{
public:
    void func();
    void func(char *);
};
void Derived::func(){
    cout<<"void Derived::func()"<<endl;
}
void Derived::func(char *str){
    cout<<"void Derived::func(char *)"<<endl;
}

int main(){
    Base *p = new Derived();
    p -> func();  //输出void Derived::func()
    p -> func(10);  //输出void Base::func(int)
    p -> func("hello C++");  //compile error

    return 0;
}

语句 p -> func(); 调用的是派生类的虚函数,构成了多态。
语句 p -> func(10); 调用的是基类的虚函数,因为派生类中没有函数遮蔽它。
语句 p -> func("hello C++"); 出现编译错误,因为基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。

④、虚析构函数的重要性

虚析构函数主要是为了避免内存泄漏,只有当派生类中有指针成员变量时才会使用。因此虚析构函数的作用是在删除指向派生类对象的基类指针时,可以调用派生类的析构函数来释放派生类中的堆内存。

具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用 virtual 虚析构函数。

在做 C++开发的时候,基类的析构函数一般都是虚函数。

猜你喜欢

转载自blog.csdn.net/hsl416604093/article/details/83622609