C++语法|虚函数与多态详细讲解(三)|深入理解多态

本文主要内容来自《CPU眼里的C/C++》
查看本节内容前,最好 首先阅读:
C++语法|虚函数与多态详细讲解(一)|再谈构造函数(构造函数与虚函数表)
C++语法|虚函数与多态详细讲解(二)|深入理解虚函数
系列汇总讲解,请移步:
C++语法|虚函数与多态详细讲解系列(包含多重继承内容)

首先我们需要明确一点,很多语言都支持多态,与其说多态是语法规则,不如说是一种设计技巧,多态不仅有代码复用的特性,它的精髓就在于虚函数的存在,使得我们能够完成函数的“覆盖”或者说“重写”。

我们首先要明确多态的基本规则:

多态,常会用基类指针指向派生类对象
多态,会利用派生类的结构特点复用基类的属性(变量/函数)
多态,会利用虚函数来扩展派生类的特性。

多态与指针

我们写一个简单的类A,再写一个简单的类B。我们用基类指针变量 p 指向派生类对象b:

class A {
public:
	int x;
	int y;
}
class B : public A {
	public:
	int z;
}
void func1(B& b) {
	A* p = &b;
}

毫不意外,我们的编译能够顺利通过,现在我们写一个不符合多态原则的函数 func2,也就是用派生类指针 p 指向基类对象 a:

void fun2(A& a) {
	//编译报错!
	B *p = &a
}

好嘛,那我们来强制转换一下:

void fun2(A& a) {
	B *p = (B*)&a;
}

现在我们能顺利通过了,但是这样安全吗?我们甚至能这样进行类型转换:

void func3(B& b) {
	A* p = (int*)&b;
}

毫无疑问,这样绝对是不安全的。

其实只要我们做类型转换,编译器都会发出警告,唯独给多态(派生类向基类做指针转换)开了一个特例

那么这样做就是安全的吗??
其实是安全的,解析来我们一起分析他们的内存分布。


首先对于类A的结构:

开始的4字节,分配给了变量x,随后分配给了变量 y 。
如果有虚函数,x, y 就需要同时上移4个字节,把起始的4字节,留给我们的 vptr(该内容移步文章:vptr的来源),用于存放类A的虚函数表地址,如下所示:

同理,我们来看看类B的内存分布:

所以说,我们只要不考虑使用派生类B的变量 z ,我们完全可以把派生类B降级为基类A来使用。这里也就是我们常说的代码复用了!

void func4(B& b) {
	A *p = &b;
	p->x = 1;
	p->y = 2;
}

但是需要格外注意的就是,我们不能够把基类A升级为派生类B来使用,因为A的内存大小是小于派生类B的(因为派生类B实现了自己特有的属性),这样会造成非法的内存访问,程序崩溃。

多态与虚函数

如果我们这样定义基类A和派生类B呢?

class A {
public:
    int x;
	int y;
    virtual int vfunc() {
        return -1;
    }
};

class B : public A {
	int z;
    virtual int vfunc() {
        return z;
    }
};

可以知道,我们类A的虚函数地址存储在A的虚函数表中;
B的虚函数地址存储在B的虚函数表中;

所以,根据动态绑定的实现原理:
无论指针 p 是什么类型,只要他指向的是类 A 的对象时,它就会调用类 A特有的虚函数;当指针 p 指向类 B 的对象时,它就会指向类 B 特有的虚函数。

这就是多态的精髓:调用相同名称的函数,会根据对象的实际类型,执行不同的函数版本。当软件代码变得越发复杂的时候,这种设计方法可以消灭大量的switch case语句。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值