C++继承中虚函数和多态

本文详细介绍了C++中的虚函数、虚函数重写、多态的概念及其实现方式,并通过具体示例展示了如何利用虚函数实现多态。此外,还探讨了纯虚函数与抽象类的作用,以及在继承体系中同名成员函数之间的关系。
(1)虚函数

在类的成员函数前面加 virtual 关键字,这个成员函数就是虚函数。

(2)虚函数重写

当在子类中定义了一个与父类完全相同的虚函数的时候,就称这个子类的函数重写(覆盖)了父类的这个虚函数。

(3)多态

当使用基类的指针或引用调用重写的虚函数是,当指向父类时,调用的函数就是父类的虚函数;当指向子类的时候,调用的函数就是子类的虚函数。也就是说,函数的参数不再与类型有关,而是跟对象有关。
例子如下:

#include<iostream>
using namespace std;

class Person{
public:
    virtual void BuyTickets(){
        cout << "买票" << endl;
    }
};

class Student : public Person{
public:
    virtual void BuyTickets(){
        cout << "买半价票" << endl;
    }
};

void Fun( Person& p){
    p.BuyTickets();
}

int main()
{
    Person p;
    Student s;
    Fun(p);
    Fun(s);
    return 0;
}

这里写图片描述

上面的代码中,Fun函数中形参是 Person的指针,通过这个指针调用了一个BuyTickets()函数。但是当main函数中两次调用Fun函数的时候,第一次传的是 Person对象,第二次传递的是Student对象。他们调用的函数分别是各自的BuyTickets()函数。见上图。这就是多态的意义,同一个函数名,传递不同的参数。

这里写图片描述

(4)重载和虚函数总结
  1. 构成重载,需要返回值、函数名、传参都一模一样。
  2. 去掉 Person 的 virtual 不再构成多态,这个时候,与对象无关,与类型有关。
  3. 去掉 Student 的 virtual ,保留 Person 的 virtual 。依然构成重载,因为 Student 默认继承 virtual。
  4. 重载有一个例外,协变。返回值可以是父子关系的指针或者引用。(见下文代码1)
  5. 只有成员函数才能定义为虚函数。
  6. 静态的成员函数不能定义为虚函数。因为没有this指针。
  7. 在类外面定义函数的时候,virtual只能加在声明,不能在类外的定义前加virtual。
  8. 构造函数不能的定义为虚函数。同时不要在构造函数和析构函数中调用虚函数,在这两个函数中,对象不是完整的,可能会出现,未定义的场景。
  9. 最好把基类的析构函数声明为虚函数。(见下文代码2)
//代码1、协变
class Person {
    public:
    //使用了父类的引用
        virtual Person& BuyTickets(){
            return this;
        }
};
class Student : public Person{
    public:
    //使用了子类的引用
        virtual Student& BuyTickets(){
            return this;
        }
};
//代码2、有一个场景,如果父类的指针指向了子类的对象。
class A{
    public:
        A(int a=10 )
            :_pa( new int (a) )
        {}

        //~A(){
        virtual ~A(){
            delete _pa;
            cout << "~A" << endl;
        }
    protected:
        int *_pa;
};

class B : public A{
    public:
        B()
            :A(10)
             ,_pb( new int(10) )
        {}
        ~B(){
            delete _pb;
            cout << "~B()" << endl;
        }
    protected:
        int *_pb;
};

int main()
{
//父类的指针pa指向了子类B对象
    A* pa = new B;
    delete pa;
    pa = new A;
    delete pa;
    return 0;
}

这里写图片描述
此时~A()跟类型有关,调用了父类的析构,对象B的空间未释放,造成内存泄漏。

这里写图片描述
此时,virtual ~A()跟对象有关,调用了子类的析构函数,正确释放B的空间。

(5)纯虚函数

纯虚函数的格式就是在成员函数的括号之后 =0,他强制子类重写该函数。
其中包含了纯虚函数的类叫做抽象类(接口类)。抽象类不能实例化出对象,纯虚函数在派生类中重新定义之后,派生类可有实例化出对象。

class Person{
    public:
    //纯虚函数
        virtual void Display() = 0;
    protected:
        string _name;
};

class Student{
    public:
        virtual void Display()
        {}
};
//Person 对象不能存在,但是 Person的指针和引用是可以存在的
Person *p = new Student;
(6)继承体系同名成员函数关系
名称作用域要求其他
重载同一个作用域函数名相同,形参不同返回值可以不同
重写(覆盖)不同作用域1、函数名、形参、返回值都相同(协变除外)2、基类函数必须要有virtual关键字访问限定符无要求
重定义(隐藏)不同作用域函数名相同不同作用域,不是重写就是重定义
(7)继承与友元、静态成员
  1. 友元关系不能继承。
  2. 基类的友元函数,不能访问子类的保护、私有成员。
  3. 基类定义了一个讲台成员,整个继承体系中只能有一个,子类和父类共用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值