前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】———— 多态

【C++】———— 多态

作者头像
用户11036582
发布2024-07-15 07:51:37
960
发布2024-07-15 07:51:37
举报
文章被收录于专栏:跟我一起学编程

一、什么是多态

什么是多态呢?通俗的来讲,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生不同的状态。

举个例子:就比如买票这个行为,成人买成人票,学生买学生票,军人优先买票,这就是一个简单的例子。

二、多态的定义和实现

1.多态构成条件

在继承中要形成多态还有两个条件:

  1. 调用时必须要通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须含有对基类的虚函数的重写

这里我们插入一个概念,关于重载与重写的概念及区别:

概念: 重载(Overloading) 重载是指在同一个作用域内,函数名字相同,但参数的类型、个数或顺序不同。 重写(Overriding) 重写发生在子类和父类之间。子类中有一个与父类中函数签名(包括函数名、参数类型和个数、返回值类型)完全相同的函数,此时子类中的这个函数就重写了父类中的函数。

重载和重写的区别

  1. 范围不同:重载发生在同一个类中,重写发生在子类和父类之间。
  2. 函数签名要求不同:重载只要求参数不同,重写要求函数签名完全相同(包括参数类型、个数、返回值类型)。
  3. 权限要求不同:重载对访问权限没有要求,重写要求子类中的重写函数不能比父类中的被重写函数有更严格的访问权限。
  4. 与虚函数的关系:重载与虚函数无关,重写的函数通常是父类中的虚函数。

下面我们接着来看多态,我们先来看一串多态的代码:

代码语言:javascript
复制
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{ 
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

注意:接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数, 在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样写

2.虚函数的重写和协变

上面例子中,我们实现了虚函数的重写(覆盖): 派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

虚函数重写的两个例外:
2.1协变

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

这里不仅仅可以返回当前基类和子类的类型,还可以返回其他有继承关系的类和类型。

2.2析构函数的重写 (析构函数名统一处理成destructor)

首先,我们来看看析构函数不处理成virtual的情况

我们本义是想让p1调用Person的析构,p2先调用Person的析构在调用Student的析构,但是这里并没有调用Student的析构,只析构了父类,就可能发生内存泄漏。

这是为什么呢? 因为这里发生了隐藏,~Person()变为 this->destructor() ~Student()为this->destructor() 编译器将他们两个的函数名都统一处理成了destructor,因此调用的时候只看自身的类型,是Person就调用Person的函数,是Student就调用Student的函数,根本不构成多态,这并不是我们期望的那样。

我们给析构函数添加上virtual

发现子类对象,Student对象就能正常析构了

注意:析构函数加virtual是在new场景下才需要, 其他环境下可以不用

3.重载、覆盖(重写)、隐藏(重定义)的对比

三个概念的对比:

  1. 重载:两个函数在同一作用域,然后参数类型不同
  2. 重写(覆盖):两个函数分别在基类和派生类,返回值/参数/函数名都必须相同
  3. 重定义:两个基类和派生类的同名函数不构成重写就是重定义,函数名形同,分别在基类和派生类

4.final 和 override

添加在父类虚函数后面添加final代表不能再被重写

final修饰类,代表不能被继承:

override代表必须要重写虚函数,如果没有重写便会报错

三、抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

注意这里的包含,只要类里面有一个有纯虚函数,就是抽象类,就无法实例化对象,间接强制派生类重写。

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

四.多态的原理

1.虚函数表

以下代码环境在X86中,涉及到的指针是4个字节

我们定义一个Base类,里面有虚函数,还有一个变量int,按照我们之前学习到了,这里Base类的大小应该是4个字节,图中却是8个字节

为什么会发生这种现象呢?

用监视窗口看一下

除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表。 其实应该叫__vftptr(多个t代表table)

我们多添加几个虚函数,看看这个表里面的内容是怎么样的 可以发现虚函数会放到虚函数表中,普通函数不会,并且表里面的内容是一个数组,是函数指针数组

2.多态的原理

有了虚函数表的概念,我们可以尝试通过虚函数表,去找到多态的原理

下面是测试代码

代码语言:javascript
复制
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	virtual void fun(){}
private:
	int a;
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
	int b;
};
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person p;
	Student s;
	Func(&p);
	Func(&s);
	return 0;
}
2.1虚表指针里的内容

从图中我们可以看到,在内存1里面输入&p可以找到p的地址, 因为p的第一个内容就是__vfptr,因此p的地址也是__vfptr的地址,那么我们通过__vfptr的地址就可以找到虚函数表里面的内容,因此我们在内存2里面输入__vfptr的地址,我们便找到了两个虚函数的地址。

去找s的虚表虚函数也同理

五、做一道题吧

这道题选B,很难相信

首先,B类型的对象p去调用test(); test()是B类继承下来的,但是里面默认存放的this指针依然是A*,将一个B类型的指针传给A类型的指针,会发生多态,B类里面的func()是重写了A类的func() (A类func()为虚函数,B类重写了可以不写virtual)。 注意重写的关键点,仅仅是重写了A类的实现,而前面的那些声明,依然是调用的A类的声明,因此给到的val默认值是1,调用了B类的函数实现!!! 所以输出B->1

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、多态的定义和实现
    • 1.多态构成条件
      • 2.虚函数的重写和协变
        • 2.1协变
        • 2.2析构函数的重写 (析构函数名统一处理成destructor)
      • 3.重载、覆盖(重写)、隐藏(重定义)的对比
        • 4.final 和 override
        • 三、抽象类
        • 四.多态的原理
          • 1.虚函数表
            • 2.多态的原理
              • 2.1虚表指针里的内容
          • 五、做一道题吧
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档