【理解C++中的多态】

一、多态的概念

从字面上就可以知道,多态意指多种形态,具体而言,不同的对象在完成某个行为时会产生出不同的形态。

二、多态的定义及实现

1、重写(覆盖)

在派生类中有一个和基类完全一样(即函数名称、参数列表、返回类型都一样)的函数,就称为重写。

1、协变

派生类重写基类的虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变

class A{};
class B:public A{};

class Person{
public:
    //返回父类的指针
    virtual A* func(){
        return new A;
    }
};

class Student:public Person{
public:
    //返回子类的指针
    virtual B* func(){
        return new B;
    }
};

2、析构函数的重写

析构函数的名称在编译器中会被统一处理为destructor。
当用户自己定义的析构函数不是虚函数时:

class Person{
public:
    ~Person(){cout<<"~Person()"<<endl;}
};
class Student{
public:
    ~Student(){cout<<"~Student()"<<endl;}
};
int main(){
    Person* p1 = new Person;
    Person* p2 = new Student;//new一个子类,用基类指针指向该子类对象
    delete p1;
    delete p2;
    return 0;
}

程序运行结果为:

~Person()
~Person()

本意是让p1调用Person的析构,让p2先调用Person的析构再调用Student的析构,但是输出结果显示,p2并没有调用Student的析构,只调用了Person的析构,原因是:编译器将析构函数 ~Person()~Student() 都统一处理成destructor,发生了隐藏。
给析构函数加上virtual后:

class Person{
public:
    virtual ~Person(){cout<<"~Person()"<<endl;}
};
class Student{
public:
    virtual ~Student(){cout<<"~Student()"<<endl;}
};
int main(){
    Person* p1 = new Person;
    Person* p2 = new Student;//new一个子类,用基类指针指向该子类对象
    delete p1;
    delete p2;
    return 0;
}

程序运行结果为:

~Person()
~Student()
~Person()

☆注意:当在new场景下,必须需要析构函数是虚函数,避免内存泄漏。

2、多态的两个构成条件

  1. 必须是基类的指针或者引用调用虚函数;
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
class human{
public:
    //被调用的函数为虚函数,用关键字virtual修饰
    virtual void BuyTicket(){
        cout<<"买票-全价"<<endl;
    }
};
//继承human
class student:public human(){
public:
    //对基类的虚函数进行重写
    virtual void BuyTicket(){
        cout<<"买票-半价"<<endl;
    }
};
//以基类引用的方式调用虚函数BuyTicket()
void func(human& h){
    h.BuyTicket();
}

int main(){
    human hu;
    student st;
    func(hu);
    func(st);
    return 0;
}

程序运行结果为:

买票-全价
买票-半价

3、override和final

在基类虚函数后加override,表示必须要对其进行重写:

class Person{
public:
    virtual void Test(){
        cout<<"此函数必须被重写"<<endl:
    }
};

class Student:public Person{
public:
    virtual void Test() override {
        cout<<"已经被重写"<<endl;
    }
};

int main(){
    Person p;
    Student st;
    p.Test();
    st.Test();
    return 0;
}

程序运行结果为:

此函数必须被重写
已经被重写

在基类名后加final表示该基类不能被继承;在基类成员函数后加final表示该函数不能被重写。

4、重载、覆盖和隐藏的对比

重载重写(覆盖)重定义(隐藏)
两个函数在同一作用域两个函数分别在基类和派生类的作用域两个函数分别在基类和派生类的作用域
函数名/参数列表相同完全相同(协变除外)不构成重写就是重定义

5、抽象类(abstract class)

在虚函数后面加上 = 0,则这个函数就为纯虚函数。纯虚函数只有函数声明,没有函数体,最后的=0并不表示函数返回值,只起形式上的作用,告诉编译系统“这是纯虚函数”。包含纯虚函数的类被称为抽象类,无法实例化,即无法创建对象。派生类继承后也不能实例化对象,只有重写纯虚函数,派生类才能实例化对象。纯虚函数规范了派生类必须重写,更体现出了接口继承。

class Person{
public:
    virtual void test() = 0;//只有函数声明没有函数体
};

class Student:public Person{
public:
    vitual void test(){
        cout<<"重写纯虚函数"<<endl;
    }
}

int main(){
    Person p;//会报错
    Student st;//由于在派生类Student中已经对test()进行了重写,所以此时正确,不会报错
    return 0}

普通函数继的承是一种实现继承。 派生了继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口的继承。 派生类继承的是基类虚函数的接口,目的是对纯虚函数进行重写,达成多态,继承的是接口。
所以,如果不实现多态,就不要把函数定义为虚函数。

三、多态的原理

1、虚函数的定义

在函数前加上关键字virtual,称其为虚函数。
虚函数的核心作用:

  1. 实现动态联编,在函数运行阶段动态地选择合适的成员函数;
  2. 在定义了虚函数后,可实现在派生类中对虚函数的重写,从而实现多态。

2、虚函数表

下列代码在×64的环境中运行:

class Person{
public:
    virtual void test(){}
private:
    int _a = 1;
};

int main(){
    Person p;
    cout<<sizeof(p)<<endl;
    return 0;
}

程序运行结果为:

16

虚表、虚表指针
p对象的长度为16bytes,原因是在Person类中,除了_a成员,还有一个 _vfptr的成员,它被叫做虚函数表指针,v表示virtual,f表示function。一个含有虚函数的类中至少含有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称为虚表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值