这部分细看,忘差不多了
28 虚函数
定义
以下内容为虚函数引入,虚函数实现了子类可以干自己的事情,而不仅仅只是完成基类
代码说明
#include <iostream>
#include <string>
//定义基类,有一个公用方法getName;
class Entity
{
public:
std::string getNmae()
{
return "Entity";
}
};
//定义子类
class Player:public Entity
{
private:
std::string m_Name;
public:
Player(const std::string& name):m_Name(name)//形参传给实参
{
//todo
}
std::string getName()
{
return m_Name;
}
};
int main()
{
Entity* e = new Entity(); //常见一个Entity实例,
std::cout << e->getNmae() << std::endl;
Player* p = new Player("wangbo");
std::cout << p->getName() << std::endl;
//预期输出 Entity wagnbo
//实际输出 Entity wagnbo
//如果使用多态的概念,所写内容内容将会失效 example
Entity* entity = p;
std::cout << entity->getNmae() << std::endl;
//预期输出 wagnbo
//实际输出 Entity
//从而引出虚函数。虚函数表创建一个基类的映射,并且能够调取子类的函数
std::cout << "Hello World!\n";
}
一个更好的例子是以PrintName函数为例引出虚函数
static void printNmae(Entity* entity) //名为printNmae的函数,输入一个Entity实体,看清楚输入是Entity哦
{
std::cout << entity->getNmae() << std::endl;
}
int main()
{
printNmae(e);
printNmae(p); //传递p不出问题是因为p是一个Player,而Player继承了Entity; 也就是说Player也是一个Entity;
printNmae(entity);
//预期输出 Entity wagnbo wagnbo
//实际输出 Entity Entity Entity
//出现实际输出的原因恰恰是我们正确的在类中申明了我们的函数,方法。在调用这个方法时候,无论如何都去调取了属于这个基类Entity的方法
//我们如何让C++知道我们想调取基类的子类所属的方法呢,————虚函数出来了 虚函数表创建一个基类的映射,并且能够调取子类的函数
std::cout << "Hello World!\n";
}
虚函数的写法
在基类函数前面加上 virtual,函数就变成了虚函数。虚函数引入了动态分布的概念,通常用虚函数表来实现编译。虚函数表包括基类中所有虚函数的映射,在运行时候能够映射它们向正确的覆写(重写)函数。
virtual添加后生成虚函数表,在子类中一般还可以将覆写的函数用关键字override标记出来,便于观察,使用等。或者在子类函数前面再加上virtual也可以。(祖类不是虚函数,父类是虚函数,子类和父类的关系依旧成立,这里好像设计到了纯虚函数=接口)
class Entity
{
public:
virtual std::string getName() //virtual function
{
return "Entity";
}
};
class Player :public Entity
{
private:
std::string m_Name;
public:
//Player(const std::string& name) :m_Name(name) {} //构造函数初始化列表用法
Player(const std::string& name)
{
m_Name = name;
}
virtual std::string getName() override //virtual function ovrride 覆写标识可以帮助检查虚函数拼写错误,很有用的
{
return m_Name;
}
};
29 纯虚函数(接口)
定义
纯虚函数允许我们定义一个在基类中没有实现的函数,在子类中实际实现。 如果在基类的函数里面有实际函数体(body),意味着在子类中重写函数是可以选择的(可写可不写) ,如果不在子类中覆写,则调取的就是基类的函数体。
如下说明
代码说明
class Entity
{
public:
virtual std::string getName()
{
return "Entity";
}
};
class Player :public Entity
{
private:
std::string m_Name;
public:
//Player(const std::string& name) :m_Name(name) {} //构造函数初始化列表用法
Player(const std::string& name)
{
m_Name = name;
}
virtual std::string getName() override //不写这个函数,依旧可以调取getName,只不过调取结果是基类Entity的
{
return m_Name;
}
接口
在面向对象程序设计中,创建一个只包含为实现方法并交由子类去实际实现功能的类是非常普的。通常就被称之为接口interface。在接口中,类仅仅包含未实现的方法并充当以勉强的模板,并且由于接口类实际上不包含实现方法,所以我们无法实例化该类。
实例化一个类,这个类必须提供所属纯虚函数的实现。或者祖类是纯虚函数,父类覆写实现后就不是纯虚函数,子类可以继承、(复杂的继承 祖类,父类,子类 理论上应该一样的原理 )。
代码说明
#include <iostream>
class Printable //这就是一个接口,一个类,里面只有纯虚函数。接口只是C++的一种规定
{
public:
virtual std::string GetClassName() = 0;
};
class PureEntity: public Printable
{
public:
virtual std::string getName()
{
return "Entity";
}
std::string GetClassName() override { return "类名为PureEntity"; }
};
class PurePlayer :public PureEntity
{
private:
std::string m_Name;
public:
//Player(const std::string& name) :m_Name(name) {} //构造函数初始化列表用法
PurePlayer(const std::string& name)
{
m_Name = name;
}
virtual std::string getName() override
{
return m_Name;
}
std::string GetClassName() override { return "类名为PurePlayer"; }
};
static void pureprintNmae(PureEntity* entity) //名为printNmae的函数,输入一个PureEntity实体,看清楚输入是PureEntity哦
{
std::cout << entity->getName() << std::endl;
}
class A :public Printable
{
public:
//virtual std::string GetClassName() override { return "类名为A"; }//如果没有实现这个函数,就不能实例化这个类
std::string GetClassName() override { return "A"; }
};
//打印所有我们所写的类名的函数实现
//static void print(??? obj) // print(??? obj) ???代表接受某种类型的参数 一种写程序技巧
static void print(Printable* obj) // print(printable obj) ???代表接受某种类型的参数,将这种类型命名为printable
{
std::cout << obj->GetClassName() << std::endl;
}
static void PureVirFct()
{
PureEntity *pe1 = new PureEntity();
//pureprintNmae(pe1);
PurePlayer* pp1 = new PurePlayer("有参数的构造函数 王博");
//pureprintNmae(pp1);
print(pe1); //传入指针调用 PureEntity
print(pp1);
print(new(A));//这么写可能内存泄露
/*所有打印都来自于print函数,接受Printable对象。它不关心实际是什么类(基于虚函数表实现的多态性)
比如随便建立一个类A ,继承Printable这个接口,只需要覆写其中的纯虚函数即可完成实例化。而且print函数也生效
*/
}