c++之多态


多态的定义和实现


构成多态的条件

  1. 必须满足继承条件
  2. 调用的相同函数必须是虚函数
  3. 该虚函数必须完成重写
  4. 调用时应该用基类的指针或引用来接收基类或派生类的指针或引用;再根据指针具体指向谁去调用那个类的函数

下面我们就通过题目来具体感悟一下多态

关于虚函数说法正确的是( B)
A.被virtual修饰的函数称为虚函数
B.虚函数的作用是用来实现多态
C.虚函数在类中声明和类外定义时候,都必须加虚拟关键字
D.静态虚成员函数没有this指针

  1. a:被virtual修饰的成员函数称为虚函数
  2. c:虚函数在类中声明加在类外实现时不能加
  3. d:静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名::成员函数名 直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数

以下程序输出结果是什么(B)
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

class A
{
public:
virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
virtual void test(){ func();}
};
class B : public A
{
public:
void func(int val = 0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0;
}

``
下面我们来分析一下

首先我们第一次调用的是类B中继承下来的test所以func里面放的是类A的指针,又因为func构成多态了,我们的p是类B的指针因此去调用类B的func但是虚函数得重写不会对不会改变函数头部只是改变函数体内的实现因此此时的val默认是0


关于重载、重写和重定义的区别


  • 重写即覆盖,针对多态, 重定义即隐藏, 两者都发生在继承体系中
  • 重载只能在一个范围内,不能在不同的类里
  • 只有重写要求原型相同
  • 重写和重定义是两码事,重写即覆盖,针对多态, 重定义即隐藏
  • 重写和重定义是两码事,重写即覆盖,针对多态, 重定义即隐藏
  • 重写要求函数完全相同,重定义只需函数名相同即可

协变


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


override 和 final关键字


override,可以帮助⽤⼾检测是否重写如果我们不想让派⽣类重写这个虚函数,那么可以⽤final去修饰。

例子

virtual void Drive() override { cout << "Benz-舒适" << endl; }
//检查该虚函数是否被重写
virtual void Drive() final {}
//代表该函数不允许被重写final关键字如果加在类后面代表该类无法被继承


纯虚函数和抽象类


在虚函数的后⾯写上 =0 ,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写,但是语法上可以实现)但不是不可实现,只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了派⽣类重写虚函数,因为不重写实例化不出对象。


多态原理


  • 对于一个有虚函数的类里面都有一个叫做一个指针指向一个虚函数表
  • 虚函数表实际上就是一个虚函数指针数组里面存放的是虚函数的地址
  • 对于派生类来说会去继承基类的虚表但继承下来的是各自独立的也就是基类有一个虚表,派生类也有一个虚表;如果实现了对虚函数的重写,重写的虚函数会覆盖掉基类被重写的虚函数指针,再添加自己本身的虚函数指针

虚函数表的存放以及虚函数存放


虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函
数的地址⼜存到了虚表中。

虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码可以
对⽐验证⼀下。vs下是存在代码段(常量区)下面我们用代码来看一下

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};
class Derive : public Base
{
public:
	// 重写基类的func1
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func1" << endl; }
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
int main()
{
	int i = 0;
	static int j = 1;
	int* p1 = new int;
	const char* p2 = "xxxxxxxx";
	printf("栈:%p\n", &i);
	printf("静态区:%p\n", &j);
	printf("堆:%p\n", p1);
	printf("常量区:%p\n", p2);
	Base b;
	Derive d;
	Base* p3 = &b;
	Derive* p4 = &d;
	//在32位去前四个字节就是虚表指针
	printf("Person虚表地址:%p\n", *(int*)p3);
	printf("Student虚表地址:%p\n", *(int*)p4);
	printf("虚函数地址:%p\n",   & Base::func1);
	printf("普通函数地址:%p\n", &Base::func5);
	return 0;
}

vs运行如下:
在这里插入图片描述
Linux运行如下:
在这里插入图片描述
可以看到虚表和虚函数都在我们的常量区(代码段)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值