3-8 基类指针、虚函数 override、final 、虚纯虚函数、多态性、虚析构

 

3-8 基类指针、虚纯虚函数、多态性、虚析构

虚函数:https://blog.csdn.net/samkieth/article/details/49737757

C++为什么要用虚函数:https://blog.csdn.net/noricky/article/details/80051219

一、基类指针、派生类指针

父类指针可以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 父类指针。
 
}
Human *phuman = new Man; //父类的指针指向了一个new出来的子类对象。
phuman->func_human(); //父类指针可以调用父类的成员函数。
phuman->func_man(); //不可以,虽然new子类对象,但是父类指针无法调用子类的成员函数。

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

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

二、虚函数

//Human.h
class Human{
public:
    Human();
    virtual void eat();
};
 
//Human.cpp
Human::Human(){
    cout << "父类Human" << endl;
}
 
void Human::eat(){
    cout << "父类eat" << endl;
}
 
//---------------------------------------------------------------------
//Man.h
class Man: public Human{
public:
    Man();
    virtual void eat();
};
 
//Man.cpp
Man::Man(){
    cout << "子类Man" << endl;
}
 
void Man::eat(){
    cout << "Man:eat" << endl;
}
 
//----------------------------------------------------------------------
//Woman.h
class Woman: public Human{
public:
    Woman();
    virtual 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;
}

有没有一个解决方法,使我们只定义一个对象指针,就可以调用父类,以及各个子类的同名函数?

有解决方案,这个对象指针必须是一个父类类型,我们如果想通过一个父类指针调用父类、子类中的同名函数的话,这个函数是有要求的

在父类中,eat函数声明之前必须要加virtual声明eat()函数为虚函数。

一旦某个函数被声明为虚函数,那么所有派生类(子类)中eat()函数都是虚函数。

为了避免你在子类中写错虚函数,在C++11中,你可以在函数声明中增加一个override关键字,这个关键字用在子类中,而且是虚函数专用。

override就是用来说明派生类中的虚函数,你用了这个关键字之后,编译器就会认为你这个eat是覆盖了父类中的同名函数(只有虚函数才存在子类可以覆盖父类中同名函数的问题),那么编译器就会在父类中找同名的虚函数,如果没找到,编译器就会报错,如果你不小心在子类中把虚函数写错了名字,写错了参数,编译器能够帮你进行纠错

final也是虚函数专用,用在父类中,如果我们在父类的函数声明中加了final,那么任何尝试覆盖在函数的操作都会引发错误。

 

调用虚函数执行的是“动态绑定”。动态表示我们程序运行的时候才能知道调用了那个子类的中的eat()虚函数。

动态的绑定到Men上去,还是Women上去,取决于new的Men还是Women;

动态绑定:运行的时候才决定你的phuman对象绑定到那个eat()函数上运行。

三、多态性

多态性只是针对虚函数来说的;

多态性:体现在具有继承关系的父类和子类之间,子类重新定义(重写)父类的成员函数eat(),同时父类把这个eat()函数声明为virtual虚函数;

通过父类的指针,只有到了程序运行时期,找到动态绑定到父类指针上的对象,这个对象有可能是某个子类对象,也可能是父类对象;

然后系统内部实际上是要查找一个虚函数表,找到函数eat()的入口地址,从而调用父类或子类的eat()函数,这就是运行时的多态性。

四、纯虚函数

纯虚函数是在基类中声明的函数,但是他在基类中没有定义,但是要求任何派生类都要定义该虚函数自己的实现方法;

基类中实现纯虚函数的方法使在函数原型后面增加 =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的时候,系统不会调用派生类的析构函数,存在问题;

解决方案:将基类的析构函数声明为虚析构函数;

在public继承中,基类对派生类及其对象的操作,只能影响那些从基类继承下来的成员,如果想要用基类对非继承的成员进行操作,则要把基类的这个函数定义为虚函数,析构函数也为虚函数,基类中的析构函数的虚属性也会被派生类继承,即派生类的析构函数也为虚函数。

Human这个类中的析构函数就要声明为virtual的,也就是说C++11中为了获得运行时多态,所调用的成员必须是virtual的。

如果一个类想要做基类,我们必须将类的析构函数声明为virtual虚函数;

只要基类中的析构函数为虚函数,就能保证我们delete基类指针时能够运行正确,不会出现内存泄漏。

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

 

本节案例:

// Human.h
// 头文件防卫式声明
#ifndef __HUMAN__
#define __HUMAN__

#include "stdafx.h"
class Human
{
public:
    Human();
    virtual ~Human();

public:
    virtual void eat();
    virtual void eat2() = 0;
};

#endif

// Human.cpp
#include "stdafx.h"
#include "Human.h"
#include <iostream>

Human::Human()
{
    std::cout << "调用了Human::Human()" << std::endl;
}

void Human::eat()
{
    std::cout << "人类喜欢吃各种美食" << std::endl;
}

Human::~Human()
{
    std::cout << "调用了Human::~Human()" << std::endl;
}

// Men.h
#ifndef __MEN__
#define  __MEN__

#include "stdafx.h"
#include "Human.h"

class Men : public Human
{
public:
    Men();
    ~Men();
public:
    virtual void eat() override;
    virtual void eat2();
};

#endif

// Men.cpp
#include "stdafx.h"
#include "Men.h"
#include <iostream>

void Men::eat()
{
    std::cout << "男人喜欢吃米饭" << std::endl;
}

void  Men::eat2()
{
    std::cout << "男人喜欢吃米饭2" << std::endl;
}

Men::Men()
{
    std::cout << "调用了Men::Men()" << std::endl;
}

Men::~Men()
{
    std::cout << "调用了Men::~Men()" << std::endl;
}

// Women.h
#ifndef __WOMEN__
#define  __WOMEN__
#include "stdafx.h"
#include "Human.h"
class Women : public Human
{
public:
    Women();
    ~Women();

public:
    virtual void eat() override;
    virtual void eat2() override;
};

#endif

// Women.cpp
#include "stdafx.h"
#include "Women.h"
#include <iostream>

Women::Women()
{
    std::cout << "调用了Women::Women()" << std::endl;
}

Women::~Women()
{
    std::cout << "调用了Women::~Women()" << std::endl;
}

void Women::eat()
{
    std::cout << "女人喜欢吃面食" << std::endl;
}

void Women::eat2()
{
    std::cout << "女人喜欢吃面食2" << std::endl;
}

// main.cpp
// Project3.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include "Human.h"
#include "Men.h"
#include "Women.h"

using namespace std;

int main()
{
    Human *phuman = new Men;
    phuman->eat();  // 男人喜欢吃米饭
    delete phuman;

    phuman = new Women;
    phuman->eat(); // 女人喜欢吃面食
    delete phuman;

    //phuman = new Human;
    //phuman->eat(); // 人类喜欢吃各种美食
    //delete phuman;
    phuman = new Men;
    phuman->eat2();  // 男人喜欢吃米饭
    delete phuman;

    phuman = new Women;
    phuman->eat2(); // 女人喜欢吃面食
    delete phuman;

    //Men men;
    Men *pmen = new Men;
    delete pmen;

    Human *phuman1 = new Men;
    delete phuman1;  // 没有执行子类的析构函数

    return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值