【c++ 之 多态】

这篇具有很好参考价值的文章主要介绍了【c++ 之 多态】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

打怪升级:第61天
【c++ 之 多态】

多态

认识多态

所谓多态,通俗来讲就是多种形态,也就是当一件事情由不同的人去完成会表现出不同的形态,例如买车票:成人全价,学生半价,军人优先等;
再例如测量体重,不同的人去测量,体重仪的表现也会不同。

多态的定义与实现

构成多态的条件

下面我们先来“见一见猪跑”:

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "成人,全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "学生,半价" << endl;
	}
};

void Buy(Person& p)
{
	p.BuyTicket();
}

void Test_p2()
{
	Person p1;
	Student t1;
	Buy(p1);
	Buy(t1);
}

【c++ 之 多态】

虚函数

  • 虚函数的定义
    虚函数就是使用 virtual 修饰的成员函数
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "成人,全价" << endl;
	}
};
  • 虚函数的重写
    **重写(覆盖)**的条件:
    在子类中存在与父类完全相同的虚函数(三同:函数名、参数、返回值都必须相同),我们称为子类对父类的虚函数进行了重写。
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "成人,全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "学生,半价" << endl;
	}
};

【c++ 之 多态】

上面,我们说的十分肯定 – 必须由三同才可构成重写,
然而,其实是有两个特例存在的 – 1.子类的重写返回值在特殊情况下可以不同;2.析构函数的重写

1.协变(基类与派生类虚函数返回值不同)

当子类和父类的虚函数返回值为有父子关系的类对象时,返回值也可以不同。

class A
{

};

class B :public A
{

};

class Person
{
public:
	virtual Person& BuyTicket()
	{
		cout << "成人,全价" << endl;
		return *this;
	}
};

class Student : public Person
{
public:
	virtual Student& BuyTicket()
	{
		cout << "学生,半价" << endl;
		return *this;
	}
};

【c++ 之 多态】

可以让父类虚函数返回子类引用,子类虚函数返回父类引用吗?
不可,虚函数的重写实际上是对 从父类继承下来的虚函数的实现进行重写,声明部分是完全继承的,因此,
如果父类虚函数返回子类引用,就会使得子类中的虚函数使用父类对象初始化子类对象,(我们可以使用子类对象初始化父类对象 – 会进行切片,但是父类中不一定拥有子类的全部成员,无法完成对子类的初识化)。

2.析构函数的重写

如果基类的析构函数是虚函数,此时派生类的析构函数无论是否加 virtual,都与基类的析构函数构成重写。
这里虽然基类与派生类的析构函数函数名不同,看起来好像违反了 三同 的规则,
其实不然,这里是编译器在底层做了特殊处理:编译之后所有析构函数的名称都会被处理为destruction

【c++ 之 多态】【c++ 之 多态】

c++11.两个虚函数修饰关键字:final & override

final修饰父类虚函数:该虚函数不可再在子类中进行重写了。
override修饰子类虚函数:该虚函数必须是父类的虚函数的重写。

【c++ 之 多态】

重载、重写、重定义再理解

【c++ 之 多态】

也就是说:两个基类和派生类中的同名函数 不构成重写 就是重定义。


抽象类

抽象类的概念

虚函数后面写上 = 0 ,这个虚函数就变成了纯虚函数, 包含纯虚函数的类称为抽象类(也叫接口类),包含纯虚函数的类无法实例化出对象,抽象类的派生类想要实例化出对象必须对纯虚函数实现重写,否则派生类也是抽象类。

接口继承与实现继承

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

【c++ 之 多态】


多态的原理

多态,如果只看表面应用 – 不同的对象调用"同一个函数"表现也不一样,看起来感觉好像很神奇、很厉害,居然可以“进行判断?”,
那么到底是不是这样呢,让我们去底层一探究竟吧~。
(注:以下数据测试环境为 vs2022,x86)

虚函数表

class Base
{
public:
	virtual void Print()
	{
		cout << "Base::Print" << endl;
	}
	int _bval;
};


void Test_p3()
{
	Base b1;
	cout << sizeof(b1) << endl;

}

我们来计算一下Base类的大小:
按照我们以前的知识:成员函数是放在代码段,对象中只有普通成员变量, 因此,Base的大小应该是4;

【c++ 之 多态】

【c++ 之 多态】【c++ 之 多态】

class Base
{
public:
	virtual void Print1() {}

	virtual void Print2() {}

	int _bval = 1;
};

class Derive :public Base
{
public:
	virtual void Print1() {}

	virtual void Print3() {}

	int _dval = 10;
};

void Test_p4()
{
	Base b1;

	Derive d1;
}

【c++ 之 多态】

这里有一点我们需要注意:虚函数表存在哪里?虚函数又存在哪里?
虚函数表存在对象中,虚函数存在虚函数表中,吗?
不是的,对象中存的是一个虚函数表指针,虚函数表中存的也只是虚函数的指针,
至于虚函数表和虚函数,其实都存在于内存中的代码段

打印虚函数表

typedef void(*VFPTR)();  //  定义 VFPTR为  void(*)() -- 函数指针类型

void VFTable(VFPTR*table)
{
	/*while (*table)
	{
		(*table)();
		++table;
	}*/
	for (int i = 0; table[i]; ++i)
	{
		printf("[%d]->", i);
		table[i](); // 函数调用
	}
	cout << endl;
}

void Test_p4()
{
	Base b1;
	Derive d1;

	//  要打印虚函数表,我就要先获取虚函数表的地址 -- 通过上面几次的查看我们可以看到 -- 虚函数表地址存放在对象的最前面
	VFTable((VFPTR*)(*(int*)&b1));
	VFTable((VFPTR*)(*(int*)&d1));

	VFTable(*(VFPTR**)&b1); 
	VFTable(*(VFPTR**)&d1);
}

【c++ 之 多态】【c++ 之 多态】


原理剖析

【c++ 之 多态】【c++ 之 多态】

补充:

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

经典例题

  • 1
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };

int main() 
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	return 0;
}

【c++ 之 多态】

  • 2
class AA
{
public:
	virtual void Print(int a = 1)
	{
		cout << "a = " << a << endl;
	}

	virtual void Call() { Print(); }
};

class BB :public AA
{
public:
	virtual void Print(int b = 0) 
	{ 
		cout << "b = " << b << endl; 
	}
};

void Test_p1()
{
	BB p;
	p.Call();
}

【c++ 之 多态】

总结

多态的重点文章来源地址https://www.toymoban.com/news/detail-454740.html

  1. 就是要了解多态构成的条件:父类的指针或引用;虚函数重写。
  2. 就是知道了解虚函数表的原理:存的是虚函数地址。
  3. 清楚多态实现的原理。
  4. 虚表地址存放在地址空间的最上方,此处要区分虚继承中的虚基表,虚基表地址存放在地址空间的最下方。


到了这里,关于【c++ 之 多态】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • pytorch升级打怪(三)

    处理数据样本的代码可能会变得混乱且难以维护;理想情况下,我们希望我们的数据集代码与模型训练代码解耦,以提高可读性和模块化。PyTorch提供了两个数据原语:torch.utils.data.DataLoader和torch.utils.data.Dataset,允许您使用预加载的数据集以及您自己的数据。Dataset存储样本及

    2024年03月14日
    浏览(38)
  • [Linux打怪升级之路]-管道

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃   如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标:理解什么是管道,学会使用匿名管道和命名管道进行通信 在学

    2024年02月08日
    浏览(44)
  • [Linux打怪升级之路]-文件操作

    前言 作者: 小蜗牛向前冲 名言: 我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 目录 一、认识操纵系统下的文件 1、什么是文件 2、文件的类型 3、文件的共识

    2024年02月01日
    浏览(44)
  • Java打怪升级路线的相关知识

      1、计算机基础 2、java入门学习 3、java基础语法 4、流程控制和方法 5、数组 6、面向对象编程 7、异常 8、常用类 9、集合框架 10、IO 11、多线程 12、GUI编程 13、网络编程 14、注解与反射 15、JUC编程 16、JVM探究 17、23种设计模式 18、数据结构与算法 19、正则表达式 1、MySQL初级

    2024年02月16日
    浏览(41)
  • [Linux打怪升级之路]-缓冲区

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃    如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正  本期学习目标:认识什么是缓冲区,缓冲区在哪里,模拟实现一个简单的缓

    2024年02月07日
    浏览(49)
  • 打怪升级之AD9226使用方法

    AD9226是一种流水线形式的ADC模数转换器。它支持12位宽、65MHz的采样精度和速度。阿美利加ADI公司设计的经典芯片(2001年)。 ADC的输入先通过SHA保持模拟信号的输入;再通过MDAC(Multiplying Digital-to-Analog Converter)进行数模转换。 上图是从电子技术应用网找来的。 ADC的工作原理大

    2023年04月25日
    浏览(69)
  • 【打怪升级】【微服务】聊聊微服务拆分设计

    并不是所有的场景都适合微服务,我理解技术开发者都有一颗追求新技术的心,但是更重要的是业务场景及团队。 微服务架构,说白了就是一种上层体系的演变。从最早的单体架构,到前后分离,SOA,甚至微服务架构,其实它们都在做一件事,并且都朝着一个方向去发展:那

    2023年04月16日
    浏览(33)
  • 【树莓派打怪升级】:玩转个人Web世界!

    最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站。 🎉博客主页:小智_x0___0x_ 🎉欢迎关注:👍点赞🙌收藏✍️留言 🎉系列专栏:小智带

    2024年02月11日
    浏览(32)
  • [Linux打怪升级之路]-system V共享内存

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃   如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标:认识什么是 system V共享内存,认识共享内存的接口函数,学会

    2024年02月08日
    浏览(46)
  • [Linux打怪升级之路]-信号的保存和递达

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃    如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、信号的保存  1、信号其他相关常见概念 2、信号在内核中的表示 3、

    2024年02月05日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包