目录
多态的概念
定义
C++直接支持多态条件
举例
回顾继承中遇到的问题
虚函数-虚函数指针-虚函数列表
虚函数
虚函数指针
虚函数列表
虚函数调用流程
虚函数于普通成员函数的区别
多态的概念
定义
多态:相同的行为方式导致了不同的行为结果,同一行语句展现了多种不同的表现形态,即多态性。C++多态,父类的指针可以指向任何继承于该类的子类对象,父类指针具有子类的表现形态,多种子类表现为多种形态由父类指针统一管理,那么这个父类指针就具有了多种形态,即多态。
C++直接支持多态条件
1.在继承关系下,父类指针指向子类对象(而非子类指针指向子类对象),通过该指针调用虚函数。
2.父类中存在虚函数(virtual修饰),且子类中重写了父类的虚函数。
重写:在继承条件下,子类定义了与父类中虚函数一摸一样的函数(包括:函数名、参数列表、返回值)我们称之为重写。

举例
class CFather {
public:
	//虚函数 virtual: 定义虚函数的关键字
	virtual void fun() {
		cout << __FUNCTION__ << endl;
	}
};
class CSon :public CFather {
public:
	void fun() {  //子类的函数一旦重写了父类的虚函数,即使不加关键字,也会被认定为是虚函数
		cout << __FUNCTION__ << endl;
	}
}; 
int main() {
	CFather* pFa = new CSon;//父类指针指向子类对象
	pFa->fun();
	CSon* pSon = new CSon;//不叫多态
	pSon->fun();
    return 0;
} 

用子类指针调用出子类对象不叫多态。
回顾继承中遇到的问题
还记得我们在继承中遇到这样一个问题,我们无法通过父类指针调用子类中不统一的函数,而最后我们是通过类成员函数指针来解决这个问题的,不过实现的过程也是十分的艰难。如今我们学了多态,那么这个问题解决起来就十分简单了。
我们直接在父类中创建一个eat虚函数,使子类中的eat也变为虚函数,那么他就可以指向子类中的不统一的函数了。
class CPeople {
public:
	int m_money;
	CPeople() :m_money(100) {}
	void cost(int n) {
		cout << __FUNCTION__ << endl;
		m_money -= n;
	}
	void play() {
		cout << "溜溜弯" << endl;
	}
	void drink() {//如果增加公共的功能,属性,只需要在父类中增加一份即可
		cout << "喝饮料" << endl;
	}
	virtual void eat() {
	}
};
class CWhite : public CPeople {
public:
	//CWhite():m_money(100){}
	void eat() {
		cout << "吃牛排" << endl;
	}
};
class CYellow : public CPeople {
public:
	void eat() {
		cout << "小烧烤" << endl;
	}
};
class CBlack : public CPeople {
public:
	void eat() {
		cout << "西瓜炸鸡" << endl;
	}
}; 
调用的时候就可以直接用父类指针指向子类成员函数了
void fun(CPeople* pPeo) {
	pPeo->cost(10);
	//(pPeo->*p_fun)(); //类成员函数指针
	pPeo->eat();  //多态解决
	pPeo->play();
	pPeo->drink();
}
 
int main() {	
    fun(new CBlack);
	fun(new CYellow);
	return 0;
} 

虚函数-虚函数指针-虚函数列表
虚函数
定义虚函数使用关键字virtual,虚函数是实现多态必不可少的条件之一。
我们知道,如果创建的类为空类,那么这个类所占用的空间为1个字节,并且如果在这个类中创建一个函数,那么此时类所占空间仍为1个字节,因为普通函数不会占用类的空间。但是如果我们在类中创建一个或多个虚函数,那此时类所占的空间就为4个字节了,那是为什么呢?
class CTest {
public:
	void fun() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
    	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}
}; 
int main() {
	cout << sizeof(CTest) << endl;
	return 0;
}
 

由于我们不管创建多少个虚函数类占的内存空间都为4,所以可以得出占的这个内存与虚函数有关,但是与虚函数的数量无关。
我们通过调试器发现,在局部变量中多出了一个名为__vfptr二级指针,由于我的系统为x86 32位操作系统,所以这个指针占的字节为4,那么这个指针就是虚函数指针。

虚函数指针
__vfptr (虚函数指针):在一个类中,当存在虚函数时,在定义对象的内存空间的首地址会多分配出一块内存,在这块内存中增加一个指针变量(二级指针 void**),也就是虚函数指针。
 
· 属于对象的,由编译器默认添加,可以看作是一般的成员属性。
· 定义对象时才存在(内存空间得以分配),多个对象多份指针。
· 指向了一个函数指针数组(虚函数列表,vftable)。
· 每个对象中的虚函数指针指向的是同一个虚函数列表。
· 定义对象调用构造函数,执行初始化参数列表时,被初始化才指向了虚函数列表。
class CTest {
public:
	//int m_a;
	CTest()/* : __vfptr(vftable) */ /*:m_a(1)*/{
		cout << __FUNCTION__ << endl;
	}
	void fun() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}
}; 
int main() {
	cout << sizeof(CTest) << endl;
	CTest tst;
	CTest tst2;
	return 0;
} 
注意:这里两个对象的虚函数指针是指向的地址相同,并不是他们俩本身的地址相同,不要弄混了。

测试虚函数指针在对象内存空间的首地址被创建:
class CTest {
public:
	int m_a;
	CTest()/* : __vfptr(vftable) */ :m_a(1){
		cout << __FUNCTION__ << endl;
	}
	void fun() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}
}; 
int main() {
	cout << sizeof(CTest) << endl;
	CTest tst;
	CTest tst2;
	CTest tst3;
	cout << &tst3 << "  " << &tst3.m_a << endl;
	return 0;
} 

虚函数列表
虚函数列表(vftable):是一个函数指针数组,数组每个元素为类中虚函数的地址。
 
· 属于类的,在编译期存在,为所有对象共享。
· 必须通过真实存在的对象调用,无对象或空指针对象无法调用虚函数。
	CTest* ptst = nullptr;
	ptst->fun();  //普通 可以调
	//ptst->fun2(); //虚函数 不能调  程序异常  虚函数指针要找到对象的首地址,但是对象指向空根本就没有地址 
虚函数调用流程
1.定义对象获取对象内存首地址中的__vfptr。
2.间接引用找到虚函数指针指向的虚函数列表vftable。
3.通过下标定位到要调用的虚函数元素(虚函数地址)。
4.通过这个地址(函数入口地址)调用到了虚函数。

模拟虚函数调用过程:
	//*(int*)&tst == vfptr;
	typedef void (*P_FUN)();
	P_FUN p_fun1 = (P_FUN)((int*)(*(int*)&tst))[0];
	P_FUN p_fun2 = (P_FUN)((int*)(*(int*)&tst))[1];
	(*p_fun1)();
	(*p_fun2)(); 

虚函数于普通成员函数的区别
· 调用流程不同:虚函数的调用流程相比普通成员函数而言复杂得多,这是他们的本质区别。
· 调用效率不同:普通的成员函数通过函数名(即函数入口地址)直接调用执行函数,效率高速度快,虚函数的调用需要虚函数指针-虚函数列表的参与,效率低,速度慢。
· 使用场景不同:虚函数主要用于实现多态,这一点是普通函数无法做到的。



















