【C++笔记】多态

记得向前看,别烂在过去和梦里

@目录

什么是多态

概念:

指的是为不同数据类型的实体提供统一的接口

目的:

实现接口重用;

虚函数:

virtual修饰的成员函数叫虚函数;
虚函数实现多态;

虚函数限制

A、非类的成员函数不能定义为虚函数
B、类的静态成员函数不能定义为虚函数
C、构造函数不能定义为虚函数,但可以将析构函数定义为虚函数
D、只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
E、当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。

虚函数语法

class 类名{
 public:
		virtual 返回值类型 函数名(参数列表);
....
protected:
		virtual 返回值类型 函数名(参数列表);
....
private:
		virtual 返回值类型 函数名(参数列表);
....
};

静态链接与动态链接

#include <iostream>
using namespace std;
class Base{
    public :
        Base(int val=0):value(val)
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        ~Base()
        {
             cout << __func__ << ":" << __LINE__ << endl;
        }
        public :
        void setval(int val)
        {
            this->value = val;
        }
        int getval() const
        {
            return this->value;
        }
        void prnmsg()
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }

        private:
        int value;

};

class Inherit : public Base
{
    public:
        Inherit(int val = 0):myval(val), Base(val)
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        ~Inherit()
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        public:
            void prnmsg()
            {
                cout << __func__ << ":" << __LINE__ << endl;
            }
        private:
            int myval;

};

void test(Base &obj)
{
    obj.prnmsg();//是静态连接,即在编译时根据类型决定调用的,这里的类型是Base,所以他调用的是基类里面的prnmsg()
}

int main()
{
    Base a;
    Inherit b;
    test(a);
    test(b);
    return 0;
}
//运行结果
Base:7
Base:7
Inherit:37
prnmsg:24
prnmsg:24
~Inherit:41
~Base:11
~Base:11

结论:
它并没有得到我们想要结果

Base a;
Inherit b;
test(a);//得到的是Base里面的prnmsg
test(b);//得到的也是Base里面的prnmsg。被隐式类型转换 Inherit -> Base

我们想要的是
test(a);//得到的是Base里面的prnmsg
test(b);//得到的是Inherit里面的prnmsg
所以我们引入了虚函数
在基类里面添加void prnmsg()成员函数前面添加virtual,它就变成了虚函数。

virtual void prnmsg() //虚函数
{
cout << __func__ <<":" << __LINE__ << endl;
}

运行结果

Base:7
Base:7
Inherit:37
prnmsg:24
prnmsg:47//我们可以清楚的看见与静态链接相比这里发生了变化,得到的是Inherit里面的prnmsg()函数。
~Inherit:41
~Base:11
~Base:11

为什么可以实现动态链接呢?

1.静态链接
在编译阶段将函数的定义和函数的调用关联;
2.动态链接
在程序运行时将函数的定义和函数的调用关联;
3.虚函数表

为什么可以实现动态链接:虚函数表,定义虚函数就会给虚函数表开空间,8bit(64).
这是因为,在父类和子类的内部结构中,有一个东西叫做虚函数表指针vfptr,即 virtual function pointer的意思。这个虚函数表指针是用于指向虚函数表vftable,而虚函数表的作用就是记录虚函数的函数入口地址。

覆盖、重载、隐藏

1.重载
1)概念
函数名相同但参数不同(个数、类型、个数类型都不同)
2)特点

A、相同的范围(在同一个作用域中)
B、函数名字相同
C、参数不同
D、virtual 关键字可有可无
E、返回值可以不同

2.覆盖(重写)
1)概念
子类重写基类的接口
2)特点

A、不同的作用域(分别位于派生类与基类)
B、函数名字相同
C、参数相同
D、基类函数必须有virtual关键字,不能有static
E、返回值相同 F、重写函数的权限访问限定符可以不同

3.隐藏

A、不在同一个作用域(分别位于派生类与基类)
B、函数名字相同
C、返回值可以不同
D、参数不同。此时,不论有无 virtual
关键字,基类的函数将被隐藏(注意与重载的区别)
E、参数相同,但是基类函数没有
virtual关键字。此时,基类的函数被隐藏(注意与覆盖的区别)

注意:
我们知道虚函数之后,建议基类的析构函数写成这样virtual ~Base()

我们比较一下虚析构函数与析构函数跟直观的了解

#include <iostream>
using namespace std;
class Base{
   public :
        Base(int val=0):value(val)
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
       virtual ~Base()
        {
             cout << __func__ << ":" << __LINE__ << endl;
        }
        public :
        void setval(int val)
        {
            this->value = val;
        }
        int getval() const
        {
            return this->value;
        }
        void prnmsg()
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }

        private:
        int value;

};

class Inherit : public Base
{
    public:
        Inherit(int val = 0):myval(val), Base(val)
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        ~Inherit()//默认为虚析构
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        public:
            void prnmsg()
            {
                cout << __func__ << ":" << __LINE__ << endl;
            }
        private:
            int myval;
};

void test(Base &obj)
{
    obj.prnmsg();//是静态连接,即在编译时根据类型决定调用的
}

int main()
{
    Base a;
    //因为此语句是静态联编的,编译到此处时,
    //编译器无法获知指针base实际指向的对象究竟为哪个类型,
    //它只会根据base的类型是Base*来决定调用Base类的析构函数。
    //所以我们给基类的析构函数声明为虚函数即可解决。
    Base *p = new Inherit;
    delete p;//根据对象决定调用Inherit的析构函数,子函数时会自动析构基类对象
    //Inherit b;
    //test(a);
   // test(b);

    return 0;
}

虚析构函数的运行结果:

Base:7
Base:7
Inherit:37
~Inherit:41
~Base:11
~Base:11

析构函数的结果:

Base:7
Base:7
Inherit:37
~Base:11
~Base:11

所以要记得用虚析构函数。析构函数容易造成内存泄露。

注意:
下面三点来自CSDN博主「司徒若寒」原文链接:https://blog.csdn.net/qq_26501341/article/details/116071394

1.只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数,即使派生类的析构函数与基类的析构函数名字不相同。
2.一般来说,一个类如果定义了虚函数,则最好将析构函数也定义为虚函数。
3.析构函数可以是虚函数,但构造函数不能声明为虚函数,这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_9420

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值