多态定义及产生的条件
#include <iostream>
using namespace std;
// 多态: 相同的行为方式导致了不同的行为结果,即为多态性。在程序上,也就是同一行语句展现了多种不同的表现形态。
// 父类指针可以指向任何继承于该类的子类,且具有子类的行为方式,多种子类具有多种形态,由父类指针进行统一管理,父类指针具有多种形态。
//多态产生的条件:1. 必须有继承关系存在(父类、子类);2. 父类的指针指向子类的对象;3. 子类必须重写父类的虚函数
//重写:子类中存在和父类函数一模一样(函数名 参数列表 返回值 都相同)的虚函数
class CFather
{
public:
//void show() {
// cout << "void show()" << endl;
//}
virtual void show() {// virtual:虚函数的关键字
cout << "void show()" << endl;
}
protected:
private:
};
//class CSon1 :CFather //默认继承是私有的
class CSon1 :public CFather
{
public:
void show() {
cout << "CSon1 show()" << endl;
}
protected:
private:
};
class CSon2 :public CFather
{
public:
void show(int a) { //这个就不能算是重写 其参数列表和父类不一样
cout << "CSon2 show()" << endl;
}
protected:
private:
};
int main() {
CFather* pFather = new CSon1;
pFather->show();
CFather* pFather1 = new CSon2;
pFather1->show();
system("pause");
return 0;
多态原理
#include <iostream>
using namespace std;
class CTest {
public:
int m_a;
int m_b;
public:
CTest() {
cout << "CTest" << endl;
}
public:
virtual void show() {
cout << "void show()" << endl;
}
virtual void play() {
cout << "void play()" << endl;
}
void code() {
cout << "void code()" << endl;
}
};
// 一个类中的虚函数由一个虚函数列表进行管理,该虚函数列表为一个数组,数组的每个元素是一个函数指针,函数指针指向虚函数,虚函数指针将在构造初始化列表中进行初始化,指向虚函数列表,虚函数指针是属于对象的,只有定义对象了才会存在。
// 在调用虚函数时,需要使用虚函数指针(指向虚函数列表)来遍历虚函数列表,进行查找对应的函数指针,然后用函数指针去调用具体的虚函数。
// 虚函数列表属于类,编译期便存在
// __vfptr 虚函数指针; vftable 虚函数列表
int main() {
cout << sizeof(CTest) << endl;
//空类大小输出为占位1,类中有1个虚函数后输出4,有2个虚函数也输出4,有2个虚函数+1个正常函数的情况下依然大小输出为4
//说明:普通函数不影响类的大小;类中存在1个或多个虚函数时,定义对象时分配的空间将多出4个字节。
/*CTest tst1;
tst1.play();
tst1.code();*/
CTest* pTst = NULL; //空类指针
pTst->code(); //直接通过类指针调用普通函数
//pTst->play(); //该句无法编译通过 因为没有对象亦没有虚函数指针 无法通过空类指针调用虚函数
//可作如下修改:
CTest tst2;
CTest* pTst1 = &tst2;
pTst1->play();
cout << sizeof(CTest) << endl;
CTest tst;
cout << &tst << endl;
cout << &tst.m_a << endl; //首地址和m_a的地址差4,实际上就是虚函数指针的大小(虚函数指针在定义对象的空间的前4个字节)
cout << &tst.m_b << endl;
system("pause");
return 0;
}
继承关系下多态发生的原理
class CFather
{
public:
CFather() {
cout << "father" << endl;
}
virtual void show() {
cout << "CFather::show" << endl;
}
virtual void code() {
cout << "CFather::code" << endl;
}
protected:
private:
};
class CSon :public CFather
{
public:
CSon() {
cout << "son" << endl;
}
virtual void play() {
cout << "CSon::play" << endl;
}
/*virtual*/ void show() {
//覆盖:子类的虚函数重写了父类的虚函数,在虚函数列表中会替换掉父类的虚函数
//同时,由于子类重写父类的虚函数,子类前无论加不加虚函数关键字virtual,该子类函数均为虚函数
cout << "CSon::show" << endl;
}
protected:
private:
}; //继承关系下,子类不但继承父类成员,还会继承父类的虚函数列表
int main() {
//CFather* pFa = new CSon;
new的是哪个子类,那么vfptr(虚函数指针)最终就指向哪个子类的虚函数列表
//pFa->show();
pFa->play(); //无法使用,play并非CFather的成员
//CSon* pSon = new CSon;
//pSon->play();
//除了上述方式,还可以通过手动遍历数组来实现以虚函数指针调用虚函数
CFather* pFa = new CSon; //父类指针指向new的子类的对象
//(int*)pFa:以int(同为4个字节)强制转换成new的子类的前四位,也就是虚函数指针
//(*(int*)pFa):间接引用(int*)pFa,虚函数指针指向虚函数列表的首地址
//(int*)(*(int*)pFa):以int(同为4个字节)强制转换成虚函数列表的前四位,也就是第一个虚函数的指针
//(((int*)(*(int*)pFa)) + 0):偏移0后(取第0个元素)间接引用,也就是取到第一个元素【0】里的值,也就是第一个虚函数指针,指向的第一个虚函数
typedef void(*P_FUN)();//定义一个函数指针
P_FUN p_fun1 = (P_FUN) * (((int*)(*(int*)pFa)) + 0);
P_FUN p_fun2 = (P_FUN) * (((int*)(*(int*)pFa)) + 1);//获取虚函数列表第二个元素【1】
P_FUN p_fun3 = (P_FUN) * (((int*)(*(int*)pFa)) + 2);//获取虚函数列表第三个元素【2】
(*p_fun1)();
(*p_fun2)();
(*p_fun3)();
system("pause");
return 0;
}
虚析构
#include <iostream>
using namespace std;
class CFather
{
public:
CFather(){
cout << "father" << endl;
}
virtual ~CFather() { // 虚析构
cout << "~father" << endl;
}
protected:
private:
};
class CSon :public CFather
{
public:
CSon() {
cout << "son" << endl;
}
~CSon() {
cout << "~son" << endl;
}
protected:
private:
};
void Fun(CFather* pFa) {
delete pFa;// 此处无法进行强转,因为根本不知道其指向的是哪个子类,因此还是无法执行子类的析构.
}
int main() {
//CSon* pson = new CSon;
//delete pson;
//pson = NULL;
// 父类构造 子类构造 子类析构 父类析构
//CFather* pFather = new CSon;
//delete pFather;
//pFather = NULL;
// 父类构造 子类构造 父类析构
// delete pFather销毁的是父类的空间 是按照父类类型进行删除的 所以和子类析构没有关系
// 利用强转: delete (CSon*)pFather; // 父类构造 子类构造 子类析构 父类析构
// 但该方式比较勉强,如果是void Fun(CFather* pFa)处的情况则无法完成要求,子类的析构还是执行不了,子类构造new了空间,如果不执行析构的话会造成空间浪费,所以要想办法执行子类的析构,故采用虚析构
// 虚析构是虚函数,因此调用时流程和虚函数一样,需要虚函数指针,遍历虚函数列表,此时虚函数列表内的析构函数实际上为子类的析构函数,因此,之所以能够调用子类的析构函数,其本质原理是多态行为的发生
// 虚函数列表内的析构函数实际上为子类的析构函数的原因如下:
//覆盖:子类的虚函数重写了父类的虚函数,在虚函数列表中会替换掉父类的虚函数
//同时,由于子类重写父类的虚函数,子类前无论加不加虚函数关键字virtual,该子类函数均为虚函数
// 因此虚函数列表里是子类的析构,而程序后面还会走父类的析构,这个部分就不再是走虚函数列表的原因了,而是因为父类类型指针本身要去删除其所占用的父类的空间
CFather* pFather = new CSon;
delete pFather;
system("pause");
return 0;
}
多态的缺点
- 效率较低(正常的函数直接调用,但虚函数要指针遍历虚函数列表……)
- 占用额外空间(每定义一个对象虚函数指针占多4字节,每个类的虚函数列表也占用空间……)
- 安全性隐患(可以通过某些方法调用访问私有的虚函数,私有的虚函数也在虚函数列表里……例:P_FUN p_fun1 = (P_FUN) * (((int*)((int)pFa)) + 0))
纯虚函数
#include <iostream>
using namespace std;
class CFather
{
public:
virtual void play() = 0;//纯虚函数
//以前面三种人,每种人有不同玩法为例,如果父类中的玩没有公共的可以实现的部分(or父类play函数不需要实现),需要各个子类的人种中有各自的玩法,此时可以将父类中设置为纯虚函数。
protected:
private:
};
class CSon:public CFather
{
public:
virtual void play() {
cout << "play" << endl;
}
protected:
private:
};
int main() {
CSon son; //在CSon中没有函数的情况下会报错:纯虚函数没有替代项
//说明:子类必须重写父类的纯虚函数,纯虚函数必须在子类中实现,父类无法实现
son.play();
//CFather father;
//不允许使用抽象类类型定义对象,也就是说有纯虚函数的CFather类是抽象类,抽象类是不允许定义对象的。
//抽象类:类中存在纯虚函数 VS 接口类:类中虚函数都是纯虚函数(接口类当然也不允许定义对象)
system("pause");
return 0;
}
头文件 -源文件
//6-2.cpp
#include <iostream>
using namespace std;
#include "6-2-0.h"
// 头文件-源文件
// 头文件:变量声明、函数声明、类型声明、类的定义、宏
// 源文件:函数的实现
int main() {
//cout << a << endl;
//cout << c << endl;
show(2);
CTest tst;
tst.show();
tst.play(); // 静态成员函数可以通过对象调用
CTest::play(); // 也可以通过类名作用域直接调用
tst.code();
tst.run();
system("pause");
return 0;
}
//6-2-0.h
// 头文件:变量、函数、类型声明、类的定义、宏
void show(int a); //函数的声明
//int a; // 根据在内存中是否申请空间来确定其为声明还是定义
// 该句为变量的定义
//int b = 10; // 定义并初始化
//extern int c; // 变量的声明
//extern int c = 30; // 定义
class CTest
{
public:
int m_a;
const int m_b;
//const一旦定义就不能修改,所以要去设置其初始值时应该在参数列表里设置
static int m_c;
//类外初始化,编译期就存在,不属于对象
public:
CTest();
~CTest();
void show();// 类成员函数的声明
static void play();
void code()const; // 常函数
virtual void run(); // 虚函数
protected:
private:
};
//int CTest::m_c = 60; //报错:多重定义
//原因:这个头文件在6-2.cpp和6-2-1.cpp都被include了一次,所以m_c被定义了两次,重定义了,说明静态的这个定义最好是不要定义在头文件中,应该放到源文件里进行初始化
//6-2-0.cpp
// 源文件:函数的实现
#include <iostream>
using namespace std;
#include"6-2-0.h"
int CTest::m_c = 60;
void show(int a) {
cout << "show" << a << endl;
}
//int c = 30;
CTest::CTest():m_b(20){
m_a = 10;
};
CTest::~CTest() {
};
void CTest::show() {
cout << m_a << endl;
cout << m_b << endl;
cout << m_c << endl;
};
void CTest::play() { //静态成员函数(声明于头文件,在源文件中实现时)的实现需要去掉静态关键字statuc
cout << "static play" << endl;
}
void CTest::code()const { //常函数(声明于头文件,在源文件中实现时)在源文件中定义需要保留关键字const
cout << "const code" << endl;
}
void CTest::run() { //虚函数(声明于头文件,在源文件中实现时)的实现需要去掉virtual关键字
cout << "virtual run" << endl;
}