【C++学习笔记】----详解虚函数相关问题(多态,覆盖(重写),隐藏(重定义),切片,虚函数表)

1.常见问题

1.什么是多态?

多态是不同对象调用同一个函数,产生不同的结果(状态),
例如买票函数,大人->全价,小孩->半价。

2.多态实现的原理?
基类的引用或者指针调用虚函数。
被调用的函数必须是虚函数,派生类的虚函数必须进行重写(覆盖)。

3.构造函数可以是虚函数吗?
构造函数不可以是虚函数,因为对象还没有产生,
没有虚函数表指针,对象初始化才产生虚函数表指针。

4.析构函数可以是虚函数吗?
析构函数可以是虚函数(非必须),在实现多态的时候必须是虚函数,
编译器将析构函数优化成同名的(destructor),如果不写成虚函数,
就会造成内存泄漏,调用是根据对象类型调用,调用的是父类指针。

5.静态成员函数可以是虚函数吗?
静态成员函数没有this指针,是所有对象共有,
而多态就是为了体现对象不同状态不同,所以静态成员函数不能是虚函数。

6.虚函数在什么时候生成,存在哪个位置。
虚函数在编译阶段生成,通过反汇编查看到,存在代码段的常量区。
同类对象共用一个虚函数表。

7.什么是抽象类?
抽象类只声明了纯虚函数的类,不能实例化出对象,作为接口继承。

8.对象访问普通函数和虚函数哪个快?
如果直接通过对象访问,普通函数和虚函数一样都是直接通过地址访问。在这里插入图片描述
如果通过基类对象的引用或者指针访问的话(多态),就会先去虚函数指针表中找对应的虚函数。普通函数快。
在这里插入图片描述

2.代码展示

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
/*class A {};
class B :public A {};*/
class person
{
public:
	 person()
	{
		cout << "person()" << endl;
	 }
	   ~person()
	{
		cout << "~person()" << endl;
	}
	  virtual void buyticket()//final虚函数不能被子类重写
	{
		cout << "全价" << endl;
		cout << _name << endl;
		
	}
	/* virtual person& buyticket()
	 {
		 cout << "全价" << endl;
		 cout << _name << endl;
		 return*this;
	 }*/
	  /*virtual A* buyticket()
	 {
		 cout << "全价" << endl;
		 cout << _name << endl;
		 return new A;
	 }*/
	string _name;
};

class stu: public person
{
public:
	stu()
	{
		cout << "stu()" << endl;
	}
	 ~stu()
	{
		cout << "~stu()" << endl;
	}
	 void buyticket()//override检查是否重写基类的虚函数
	{
		cout << "半价" << endl;
		cout << _name << endl;
		cout << _num << endl;
		cout << person::_name << endl;
	}
	/*virtual void buyticket(int)//虚函数重写,返回值,函数名,参数都相同时,成为子类重写了基类的虚函数
	{
		cout << "半价" << endl;
		cout << _name << endl;
		cout << _num << endl;
	}*/
	/*virtual stu& buyticket()//虚函数重写,函数名,参数都相同时,成为子类重写了基类的虚函数(协变引用都必须是引用,指针都指针)
	{
		cout << "半价" << endl;
		cout << _name << endl;
		cout << _num << endl;
		return *this;
	}*/
	/*virtual B* buyticket()//只要返回值满足父类对父类,子类对子类的引用或者指针,都可以是协变。其他类继承也可以。
	{
		cout << "半价" << endl;
		cout << _name << endl;
		cout << _num << endl;
		return new B;
	}*/
	int _num;
	string _name;
};
void test1()
{
	//同名隐藏 - 》重定义
	person p;
	p._name = "xff";
	stu s;
	s._name = "cf";
	s._num = 1;
	s.person::_name = "name";
	//p.buyticket();
	//s.buyticket();
	//person*pa = new stu;
	//cout << pa->_name << endl;
	//delete pa;
	//父类调用子类对象出现切片
	//person* pp = &s;
	//person& ps = s;
	//ps.buyticket();
	//pp->buyticket();//调用指针对象(person)类型的,数据是指向(stu)类型的。
	//stu*pps = &p;//子类调用不合法
	//stu* ps = (stu*)&p; //强转
	//ps->buyticket();    //强转成stu类型,调用的函数是stu对象指向的,数据是指向(person)类型的,有越界访问。
}
void func(person p)
{
	p.buyticket();
}
void func1(person& p)//父类引用
{
	p.buyticket();
}
void func2(person* p)//父类指针
{
	p->buyticket();
}
void test2()
{
	person p;
	p._name = "xff";
	stu s;
	s._name = "cf";
	s._num = 1;
	//func(p);
	//func(s);
	func1(p);
	func1(s);
	//func2(&p);
	//func2(&s);
}

void test3()
{
	//person p;
	//stu s;
	person *pp = new stu;//不是多态会内存泄漏
	//person *pp = new person;
	delete pp;
}
//抽象类(接口类) 纯虚函数的类
class A 
{
public :
	virtual void  fun1()
	{
		cout << "A::fun1()" << endl;
	}
	virtual void  fun2()
	{
		cout << "A::fun2()" << endl;
	}
	 /* void  fun3()
	{
		cout << "A()" << endl;
	}*/
	//virtual void  fun1() = 0;
	int _num1;
	
};
class B 
{
public:
	virtual void  fun1() 
	{
		cout << "B::fun1()" << endl;
	}
	/*virtual void  fun()//不重写纯虚函数子类就不能实例化出对象
	{

	}*/
	void  fun3()
	{
		cout << "B()" << endl;
	}
	virtual void  fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _age;
};
class C :public B,public A
{
public:
	virtual void  fun1()
	{
		cout << "C::fun1()" << endl;
	}
	virtual void  fun3()
	{
		cout << "C::fun3()" << endl;
	}
};

typedef void(*VFPTR)();
void PrintVTable(VFPTR* vTable)
{
	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
	cout << " 虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%p,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
void test4()
{
	A a;//抽象类不能实例化出对象
	B b;
	C c1;
	C c2;
	//A*pa = new B;
	//pa->fun1();
	//printf("%p\n", *((int*)&a));
	//printf("%p\n", *((int*)&b));
	VFPTR* va = (VFPTR*)(*(int*)&a);
	PrintVTable(va);
	VFPTR* vb = (VFPTR*)(*(int*)&b);
	PrintVTable(vb);
	VFPTR* vc1 = (VFPTR*)(*(int*)&c1);
	PrintVTable(vc1);
	VFPTR* vc2 = (VFPTR*)(*(int*)&c2);
	PrintVTable(vc2);
	//a.fun3();
	//b.fun3();
 	//cout << sizeof(a) << endl;//虚函数表指针32位-4字节大小
	//cout << sizeof(b) << endl;

}
int main()
{
	//test1();//隐藏重定义名字相同
	//test2();//多态重重写
	//test3();
	//test4();
 	system("pause");
	return 0;
}

3.结果展示

1.test1()

通过对象调用,函数与调用对象有关。
在这里插入图片描述
同名隐藏,隐藏父类。
在这里插入图片描述
基类指针调用虚函数,子类重写虚函数。
在这里插入图片描述
基类对象的引用,调用子类的重写的虚函数。
在这里插入图片描述
子类调用父类的不合法
在这里插入图片描述
强转成子类对象,可以实现多态,但是存在越界访问。
父类对象里面没有子类的_num
在这里插入图片描述
在这里插入图片描述
父类的引用或者指针直接访问的话,会出现切片,也就是只能访问到子类对象继承父类的部分。
在这里插入图片描述
在这里插入图片描述

2.test2()

父类的指针或者引用,虚函数重写实现多态。
第一个没有传这两个类型,所以没有构成多态,函数是通过对象调用,
所以我们可以看出来,传递的时候切片了,子类显示的_name是继承父类对象里面的_name.
有很好的展示了隐藏(重定义)
在这里插入图片描述

3.test3()

析构函数没写virtual,只析构了基类对象的。
在这里插入图片描述
析构写成虚函数,构成多态。
在这里插入图片描述
基类的不影响。
在这里插入图片描述

4.test4()

在这里插入图片描述
在这里插入图片描述
我们可以看出,多继承中虚函数的继承以及重写,C类中的A B类的对象重写了fun1(),fun2()直接继承了之前的,fun3()是没有重写的虚函数,它放在第一继承基类的虚函数最后面,我们改一下第一继承类,发现未重写的虚函数,会放到它后面。

相同类对象共用一个虚函数表。

在这里插入图片描述

4.总结

只有满足基类的引用或者指针调用派生类虚函数重写,才能构成多态。
对象直接调用的话,和对象类型有关,基类调子类会切片。
同一类不同对象共用一个虚函数表,不同类对象不共用,多继承的时候,一个对象不止只有一个虚函数表。
多继承中,派生类未重写的虚函数放在第一继承类的后面。
协变是虚函数重写中,返回类型可以不同,但必须是基类与派生类的关系,不能调换顺序。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页