C++日记——Day13:基类指针、虚函数、多态性、纯虚函数、基类的析构函数

基类指针、派生类指针

父类指针可以指向子类对象

Human *phuman = new Man; //父类的指针指向了一个new出来的子类对象。
phuman->func_human(); //父类指针可以调用父类的成员函数。
phuman->func_man(); //不可以,虽然new子类对象,但是父类指针无法调用子类的成员函数。
//Human.h
class Human{
public:
    Human();
    void eat();
};
 
//Human.cpp
Human::Human(){
    cout << "父类Human" << endl;
}

Human::eat(){
    cout << "父类eat" << endl;
}

//---------------------------------------------------------------------
//Man.h
class Man: public Human{
public:
    Man();
    void eat();
};
 
//Man.cpp
Man::Man(){
    cout << "子类Man" << endl;
}

void Man::eat(){
    cout << "Man:eat" << endl;
}

int main(){
    Human *phuman = new Man();
    phuman -> eat();  //调用了父类Human的eat函数。因为phuman 父类指针。

}

既然父类指针没办法调用子类成员函数,那么为什么要让父类指针可以指向子类对象?其实我们可以通过一些方法调用父类及各个子类的函数。

如果我们想要通过一个父类指针调用父类、子类中的同名同参函数的化,那么对这个函数是有要求的:在父类中必须在声明这个函数时在函数之前加 virtual ,声明成虚函数(建议在子类中也加上virtual,方便阅读)

 

虚函数:

//Human.h
class Human{
public:
    Human();
    virtual void eat();
};
 
//Human.cpp
Human::Human(){
    cout << "父类Human" << endl;
}

Human::eat(){
    cout << "父类eat" << endl;
}

//---------------------------------------------------------------------
//Man.h
class Man: public Human{
public:
    Man();
    void eat();
};
 
//Man.cpp
Man::Man(){
    cout << "子类Man" << endl;
}

void Man::eat(){
    cout << "Man:eat" << endl;
}

//----------------------------------------------------------------------
//Woman.h
class Woman: public Human{
public:
    Woman();
    void eat() override;
};
 
//Woman.cpp
Woman::Woman(){
    cout << "子类Woman" << endl;
}

void Woman::eat(){
    cout << "Woman:eat" << endl;
}

int main(){
    Human *phuman = new Man();
    phuman->eat();  //调用的时Man中的eat函数
    phuman->Human::eat(); //通过这种方式可以调用父类中的eat函数
    delete phuman;

    Human *phuman = new Woman();
    phuman->eat();  //调用的时Woman中的eat函数
    delete phuman;

    Human *phuman = new Human();
    phuman->eat();  //调用的时Human中的eat函数
    delete phuman;
}

override:为了避免在子类中写错虚函数,在C++11中,可以在函数声明处增加一个override关键字,这个关键字用在子类中,是虚函数专用。只有虚函数才存在子类可以覆盖父类中同名函数的问题。如果在子类中实现父类中虚函数时使用override可以帮助纠错

final:也是虚函数专用,使用在父类中,如果我们在父类中函数加了final,那么任何子类试图覆盖这个父类函数的行为都会报错

总结:使用父类指针调用子类以及父类对象的虚函数时执行的时动态绑定。就是我们在程序运行的时候才能知道调用了那个子类的虚函数。

动态绑定就是运行的时候才决定phuman绑定到那个eat函数上运行。

正常的生成对象时因为产生的是确定的对象,所以涉及不到虚函数调用,比如:

Man man;
man.eat();


Woman woman;
woman.eat();


Human human;
human.eat();

 

多态性:

多态性只是针对虚函数来说的;随着虚函数的提出面向对象的编程思想里这个“多态性”的概念就提了出来;

多态性:体现在具有继承关系的父类和子类之间,子类重新定义了父类的成员函数,同时把父类的函数声明成了virtual虚函数,通过父类指针,在程序运行时,找到动态绑定到父类指针的对象,这个对象可能是某个子类对象,也可能是父类对象,然后系统内部实际上是要查一个虚函数表,找到函数的入口地址,从而调用父类或者子类的函数,这就是运行时的多态性。

 

纯虚函数:

是在基类中声明的虚函数,但是他在基类中没有定义(实际上可以有定义并且被子类对象调用,但是不常见),但是要求任何派生类都要定义该虚函数自己的实现方法。

基类中实现纯虚函数的方法是在函数原型后加 =0

//Human.h
class Human{
public:
    Human();
    virtual void eat() =0; //定义纯虚函数
};
 
//Human.cpp
Human::Human(){
    cout << "父类Human" << endl;
}

1、一旦一个类中有纯虚函数了,那么你就不能生成这个类的对象了主要用于当作基类来生成子类;

2、子类中必须要实现该基类中定义的纯虚函数;

 

基类的析构函数通常写成虚函数(虚析构函数)

//Human.h
class Human{
public:
    Human();
    ~Human();
};
 
//Human.cpp
Human::Human(){
    cout << "父类Human" << endl;
}

Human::~Human(){
    cout << "父类~Human()" << endl;
}

//---------------------------------------------------------------------
//Man.h
class Man: public Human{
public:
    Man();
    ~Man();
    void say();
};
 
//Man.cpp
Man::Man(){
    cout << "子类Man" << endl;
}

~Man(){
    cout << "子类~Man()" << endl;
}

void Man::say(){
    cout << "say()" << endl;
}

//--------------------------------------------------------------------
int main(){
    Man *pman = new Man(); //调用 Human() Man()
    delete pman; //调用 ~Man() ~Human()
    
    Human *phuman = new Man(); //调用 Human() Man()
    delete phuman; //只调用了 ~Human()

    phuman->say(); //报错,因为基类中没有say(),基类指针无法指向子类中基类没有的成员函数
}

结论:用基类指针 new子类对象,在delete时系统不会调用派生类的析构函数。

1、把基类的析构函数写成虚函数即可解决这个问题。

2、在public继承中,基类对派生类及其对象的操作只能影响到那些从基类继承下来的成员,如果想用基类对非继承成员进行操作则要把这个函数定义为虚函数,另外基类中的虚构函数也会被继承给子类,这个样的话,析构函数自然成为了虚函数,虽然名字和基类中的析构函数不同,delete phuman的时候肯定要调用父类的析构函数,但是在父类析构函数中他要是想要调用子类Man中的析构函数的,那么Human这个类中的析构函数要声明为virtual,也就是说C++中为了获得运行时的多态行为,所调用的成员函数必须得是virtual

如果一个类,想要做基类,我们务必要把这个类的析构函数写成virtual析构函数;

只要基类的析构函数是虚函数,就能够保证我们delete基类指针时能够运行正确的析构函数版本;

普通类我们可以不写虚析构函数,但是如果是基类,就必须写析构函数,而且这个析构函数还必须是虚析构函数。

虚函数会增加内存开销,类中定义虚函数,编译器就会给这个类增加一个虚函数表,在这个表中存放着虚函数的指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值