C++多态与虚函数相关实验说明

void Test_Polym_Virtual_Func()
{
	cout << "Test_Polym_Virtual_Func" << endl;
	// 测试虚函数
	class A
	{
	public:
		A(){ cout << "A Constructor." << endl; f(); g(); m_p1_ = new int; *m_p1_ = 1; }
		virtual~A(){ cout << "A Destructor." << endl;  f(); g(); delete m_p1_; m_p1_ = nullptr; }
		 
		// 非虚函数
		void f(){ cout << "A f()" << endl; }
		// 虚函数
		virtual void g(){ cout << "A g()" << endl; }
	private:
		int m_i_ = 6;
		int* m_p1_ = new int;
	};

	class B : public A
	{
	public:
		B(){ cout << "B Constructor." << endl;  
		f(); g(); 
		m_p2_ = new int; *m_p2_ = 2; }
		virtual ~B(){ cout << "B Destructor." << endl;  f(); g(); delete m_p2_; m_p2_ = nullptr; }

		// 非虚函数
		void f(){ cout << "B f()" << endl; }
		// 虚函数
		void g(){ cout << "B g()" << endl; } // 从A继承过来的虚函数默认也是虚函数
	private:
		int m_j_ = 7;
		int* m_p2_;
	};

	{
		cout << "*1*" << endl;
		A& a = B();	// A类型引用指向B类型对象
		a.f();		// 输出:A f()
		a.g();		// 输出:B f()
		/*说明
		1.变量a地址连续排列地址含义
		在a变量地址依次排列了一个虚函数表指针__vfptr、和A::m_i_、A::m_p1_指针地址、B::m_j_、B::m_p2_指针地址
		在__vfptr中存储了B类型的析构函数和函数g()地址,虚函数表是一个数组
		*/
	}
	
	{
		cout << "*2*" << endl;
		A* p_a = new B;	// A类型指针指向B类型指针对象
		p_a->f();		// 输出:A f()
		p_a->g();		// 输出:B f()
		delete p_a;
		p_a = nullptr;
		/*说明
		1.p_a指向的堆内存地址连续排列地址含义
		在p_a指向的堆内存地址依次排列了一个虚函数表指针__vfptr、和A::m_i_、A::m_p1_指针地址、B::m_j_、B::m_p2_指针地址
		在__vfptr中存储了B类型的析构函数和函数g()地址,虚函数表是一个数组
		*/
	}
}

上面实验输出如下:

1.基类对象的指针指向派生类对象时,如果基类析构函数不是虚函数,派生类的析构函数不会调用 这种情况下,如果派生类存在堆分配内存成员变量时,本来期望在派生类析构函数中释放堆分配内存,但不能调用析构函数,就会内存泄漏,派生类其他的非堆分配内存成员变量资源都会被释放;

基类引用指向派生类对象时,派生类对象是栈上分配的内存对象,即使基类析构函数不是虚函数,也会调用派生类的析构函数,因为派生类对象本来就是栈上分配的内存对象,离开作用域时,自然被系统自动调用析构函数回收资源;

测试这个异常,可以将A类型析构函数不设置成virtual,那么输出结果如下图,

在析构a引用对象时,调用了B的析构函数,析构p_a指针对象时,确实没有调用B的析构函数

2.无论基类还是派生类构造函数或析构函数中调用虚函数,都不会实现多态,都是调用自己的函数,因为基类构造函数是在派生类之前执行的,所以在基类构造函数运行的时候派生类的数据成员还没有被初始化。如果在基类的构造过程中对虚函数的调用传递到了派生类,派生类对象当然可以参照引用局部的数据成员,但是这些数据成其实尚未被初始化,这样是及其不安全的,所以在基类的构造过程中,虚函数调用从不会被传递到派生类中。在对象的析构期间,存在与上面同样的逻辑。一旦一个派生类的析构函数运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++就把它们当做是不存在一样,如果基类的析构过程中,虚函数调用被传递到派生类中,这样也及其不安全。其实,一旦进入到基类的构造函数或析构函数中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。

4.虚函数表指针__vfptr属于类,不属于对象,在编译期已经生成,并且__vfptr值在对象首地址存储,也就是对象地址开始前面4个字节就是__vfptr(32位)

下图是a引用对象中虚标指针:0x0034eb94

下图是p_a指针对象中虚标指针:0x0034eb94,并且看不到B类型对象m_p2的指针成员变量,是因为现在表现为A类型对象,实际的函数调用会多态执行B类型的重写虚函数的;

5.构造函数不能是虚函数,否则编译报错;因为在调用构造函数构造对象时肯定已经知道了具体的类型,而虚函数行为是在运行期间确定实际类型的,不可能在构造派生类对象时,通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值