ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++多态二三事

2021-10-29 19:58:20  阅读:178  来源: 互联网

标签:虚表 函数 多态 C++ 二三 virtual 重写 cout


多态:

多态的概念

多态:多种形态;不同的对象完成同一件事情会发生不同的行为,产生不同的结果

多态包括
静态的多态:函数重载(静态绑定:静态指编译时)
动态的多态:父类指针或引用调用重写了的虚函数(动态绑定:是指运行时)

1.多态的定义和实现

构成多态还需要两个条件:

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

虚函数:即被virtual修饰的类非静态成员函数称为虚函数

静态函数没有this指针,无法形成切片,那就无法调用派生类重写了的虚函数,无法形成多态

虚函数是为了形成多态

虚函数的重写(覆盖)
派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)

注意与重定义区分:
如果函数名相同,不是重写就是重定义(隐藏)

①虚函数重写的三个例外

虚函数重写要求重写函数与基类完全相同

但也有例外:

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

#include<iostream>
using namespace std;
class A{};
class B :public A{};
class C
{
public:
	virtual A* show()
	{
		cout << "C" << endl;
		return new A;
	}
};
class D :public C
{
public:
	virtual B* show()
	{
		cout << "D" << endl;
		return new B;
	}
};
int main()
{
	D d;
	C* c = &d;
	c->show();
	return 0;
}

运行结果:
在这里插入图片描述
②析构函数的重写
编译器会自动对基类,派生类的析构函数名统一处理成destructor,所以父子类析构函数自动构成重定义(不重写的话)

一般情况下,重不重写并没有影响:
在这里插入图片描述
在这里插入图片描述
无论重不重写并没有影响,都是先调用D的析构,D析构调用完后调用父类析构,接着C再析构

但是在特殊情况下,重写就十分必要
在这里插入图片描述

在这种情况下,我们是想通过delete分别调用它们的析构函数

但是c2的析构函数没重写,发生切片后,c2只能调用从父类继承下来的成员,所以也调用了父类的方法,并没有析构释放动态开辟的空间,这样就容易造成资源泄漏

重写后:
在这里插入图片描述
所以编译器为什么要对子类和父类的析构函数名进行处理
想必已经很明确了,就是让父子类的析构函数构成重写,让它们调用指向的对象的析构函数

③不写子类的virtual
子类的virtual不写,编译器也认为它是虚函数完成了重写

②C++11: override 和 final

  1. final:修饰虚函数,表示该虚函数不能再被重写
    在这里插入图片描述

  2. override: 检查派生类虚函数是否重写
    在这里插入图片描述

2.重载、覆盖、隐藏对比

在这里插入图片描述

3.抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象
纯虚函数派生类必须重写

在这里插入图片描述
C类就是抽象类

1.抽象类能更好地表示没有实例对象对应的抽象类型 比如动物
2.体现了接口继承(跟Java的接口相似),强制子类重写虚函数

通过父类对象的指针或引用指向子类对象来使用

4.接口继承和实现继承

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

不用多态,就不要被函数定义成虚函数

多态原理

虚表指针(铺垫):

只要类中有虚函数就会有虚表指针

这个指针存放的是函数指针数组

#include<iostream>
using namespace std;

class C
{
public:
	virtual void show() = 0;
	
	
	virtual ~C()
	{
		cout << "C" << endl;
	}
};
class D :public C
{
public:
	
	virtual void show( )override
	{
		cout << "hello" << endl;
	}

	 virtual ~D()
	{
		cout << "D" << endl;
	}
	 int* a;
};
int main()
{
	D d;
	return 0;
}

在这里插入图片描述
注意:
虚继承和这里完全是不同的两个东西,虽然都有virtual

虚继承的叫虚基表,存放的是偏移量
而这里叫虚表,存放的是函数指针

完成重写:
在这里插入图片描述
如果没完成重写:
在这里插入图片描述
可以看到c跟d里面的虚函数指针指向同一个函数,那这个函数一定是父类的

说明这个虚函数指针是来自父类的拷贝,如果子类重写了该虚函数,就把这个函数的地址拷贝到对应位置上

所以满足多态后,虚函数表被修改了,在运行时,再到指向的对象中的虚表中去找对应的虚函数调用。

所以指向父类就调用的是父类的虚函数,指向子类就调用的是子类的虚函数

如果不构成多态:

比如这样修改一下

void test(C c)
{
	c.show();
}

调用的就是父类的show函数,与传入的是什么对象无关

总结:
以我的理解,本质上就是在找这个虚函数指针,虚表里面函数指针存的是谁的地址,就调用的是谁

比如C c=d/c 这样根据切片是重新开辟了一份空间,此时c是一个对象了,虚表指针在C上(每个类实例化出的对象共用一个虚表指针),调用的就是C的虚函数

如果形成了多态,这个c是指向传入对象的,c就会去传入对象中找到虚函数指针,调用的是该对象的虚函数(如果重写)

例如:

#include<iostream>
using namespace std;
class C
{
public:
	virtual void show()
	{
		cout << "C" << endl;
	}
	int c;
};
class D :public C
{
public:
	
	virtual void show(int )
	{
		cout << "D" << endl;
	}
	int d;
};
void test(C c)
{
	c.show();
}
int main()
{
	D d;
	test(d);
	C c;
	test(c);
	C c1;
	return 0;
}

所以要形成多态必须父类的指针或引用指向子类对象

在这里插入图片描述
在拷贝构造时并不会拷贝vfptr,容易造成紊乱,vfptr只与所属类有关

注意:
1.对象中的虚表指针是在构造函数初始化列表初始化的,虚表是在编译时就生成好的

2.虚函数表存放的是类中所有的虚函数的地址,虚函数跟普通函数一样,编译完成后放在代码段

3.虚函数的重写,也叫覆盖,子类会先调用父类的构造函数,父类会将虚函数表内容拷贝给子类,子类重写了再逐一修改

在这里插入图片描述
C类的虚函数表在这里插入图片描述

1.探究虚表存放的位置

对象的前四个字节就是虚表的地址

 ▉

#include<iostream>
using namespace std;
class C
{
public:
	C()
	{

	}
	virtual void show()
	{
		cout << "C" << endl;
	}
	virtual void show1()
	{
		
	}
	int c;
};

int a = 0;
int main()
{
	C* c = new C;
	printf("C的虚表:%p\n", *((int*)c));//取前四个字节
	
	int i;
	printf("栈上地址:%p\n", &i);
	printf("数据段地址:%p\n", &a);
	
	int* arr = new int;
	printf("堆地址:%p\n", arr);
	
	const char* str = "hello world";
	printf("代码段地址:%p\n", str);
	
	return 0;
}

在这里插入图片描述

所以虚函数在代码段

2.打印虚表

include<iostream>
using namespace std;
class A
{
public:
	virtual void test1()
	{
		cout << "A1" << endl;
	}
	virtual void test2()
	{
		cout << "A2" << endl;
	}
	int _a;
};
class B:public A
{
public:
	virtual void test1()
	{
		cout << "B1" << endl;
	}
	virtual void test3()
	{
		cout << "B3" << endl;
	}
	int _b;
};
int main()
{
	A a;
	B b;
	return 0;
}

在这里插入图片描述
可以看到VS编译器监视做了特殊的处理只能看到从父类继承下来的函数指针

但内存窗口里面真实说明了虚函数表里面有三个函数指针
打印:
在这里插入图片描述
多态虚表就非常清晰了

多继承:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void test1()
	{
		cout << "A1" << endl;
	}
	virtual void test2()
	{
		cout << "A2" << endl;
	}
	int _a;
};
class B
{
public:
	virtual void test1()
	{
		cout << "B1" << endl;
	}
	virtual void test2()
	{
		cout << "B2" << endl;
	}
	int _b;
};
class C :public A, public B
{
public:
	virtual void test1()
	{
		cout << "C1" << endl;
	}
	virtual void test3()
	{
		cout << "C3" << endl;
	}
	int _c;

};
typedef void (*VFunc)();//定义函数指针VFunc
void PrintVF(VFunc* ptr)//函数指针的数组指针
{
	printf("虚表指针:%p\n", ptr);
	for (int i = 0; ptr[i] != nullptr; i++)
	{
		printf("VF[%d]:%p\n",i, ptr[i]);
		ptr[i]();
	}
	printf("\n");
}
int main()
{
	C c;
	PrintVF((VFunc*)(*(int*)&c));//打印A继承下来的虚表
	PrintVF((VFunc*)(*(int*)((char*)&c+sizeof(A))));//打印B继承下来的虚表
	return 0;
}

在这里插入图片描述
可以看到test3只放第一张虚表

3.菱形虚继承、虚函数

#include<iostream>
using namespace std;
class A
{
public:
	virtual void show()
	{
		cout << "A" << endl;
	}
	int _a;
};
class B :virtual public A
{
public:
	virtual void show()
	{
		cout << "B" << endl;
	}
	int _b;
};
class C :virtual public A
{
public:
	virtual void show()
	{
		cout << "C" << endl;
	}
	int _c;
};
class D :public B, public C
{
public:
	virtual void show()
	{
		cout << "D" << endl;
	}
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

作如下修改,B,C各增加一个虚函数,A并不修改
在这里插入图片描述
修改后 修改前 在这里插入图片描述
此时虚基表的第一行就存了到B作用域的虚函数表的偏移量

而虚基表的18 00 00 00是存的到作用域公共基类A的偏移量

标签:虚表,函数,多态,C++,二三,virtual,重写,cout
来源: https://blog.csdn.net/hbbfvv1h/article/details/120972454

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有