热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

多态和虚函数

多态与虚函数在类的定义中,前面有virtual定义的即为虚函数virtual关键字只用在定义类的函数声明时,写具体函数时不用。classBa

多态与虚函数

在类的定义中,前面有virtual定义的即为虚函数
virtual关键字只用在定义类的函数声明时,写具体函数时不用。

class Base{
private:

public:
virtual int get(){}
};

int base::get() {

}

对于派生类的对象可以赋值给基类,或者基类可以引用派生类。
如果基类指针或者引用指向基类对象,那么调用基类虚函数
如果基类指针或者引用指向派生对象,那么调用派生类对象

class A{
private:
public:
virtual void Print() { cout <<"A::print" <};

class B:public A{
private:
public:
virtual void Print() { cout <<"B::print" <};

class D :public A {
private:
public:
virtual void Print() { cout <<"D::print" <};

class E :public B {
private:
public:
virtual void Print() { cout <<"E::print" <};

int main() {
A a; B b; E e; D d;
A * pa = &a; B * pb = &b;
D * pd = &d; E * pe = &e;
pa->Print(); // a.Print()被调用,输出:A::Print
pa = pb;
pa->Print(); //b.Print()被调用,输出:B::Print
pa = pd;
pa->Print(); //d. Print ()被调用,输出:D::Print
pa = pe;
pa->Print(); //e.Print () 被调用,输出:E::Print
return 0;
}

这里写图片描述

游戏编程实例

这里写图片描述

  • 非多态的实现方法
class CCreature{
private:
int nPower; int nLifeValue;
public:
};

class CDragon:public CCreature{
private:
public:
void Attack(CWolf *pWolf) {
//表现攻击动作代码
pWolf->Hurted(nPower);
pWolf->FightBack(this);
}

void Attack(CGhost *pGhost) {
//表现攻击动作代码
pWolf->Hurted(nPower);
pWolf->FightBack(this);
}

void Hurted(int nPower) {
//表现受伤动作代码
nLifeValue -= nPower;
}

void FightBack(CWolf *pWolf) {
pWolf->Hurted(nPower / 2);
}

void FightBack(CGhost *pGhost) {
pGhost->Hurted(nPower / 2);
}
};

此时,n种生物,CDragon中会有n个Attack函数…..,其他类也如此。

  • 多态的实现方法
class CCreature{
private:
int m_nPower; int m_nLifeValue;
public:
virtual void Attack(CCreature *pCreature);
virtual void Hurted(int nPower);
virtual void FightBack(CCreature *pCreature);
};

class CDragon:public CCreature{
private:
public:
virtual void Attack(CCreature *pCreature);
virtual void Hurted(int nPower);
virtual void FightBack(CCreature *pCreature);
};

void CDragon::Attack(CCreature *pCreature) {
//表现攻击动作代码
pCreature->FightBack(this);
pCreature->Hurted(m_nPower);
}

void CDragon::Hurted(int nPower) {
//表现受伤代码
m_nLifeValue -= nPower;
}

void CDragon::FightBack(CCreature *pCreature) {
//表现反击动作代码
pCreature->Hurted(m_nPower / 2);
}

更多实例

  • 几何形体处理程序

输入: n 个数目,即n行,每行以c开头
c:R-矩形-后面接长和宽
c:C-圆形-后面接半径
c:T-三角形-后面接三条边
输出:
从小到大输出几何形体的种类和面积

class CShape
{
private:
public:
virtual double Area() = 0;//纯虚函数
virtual void PrintInfo() = 0;//纯虚函数
};

class CRectangle:public CShape{
private:
public:
int w, h;
virtual double Area();
virtual void PrintInfo();
};

class CCircle:public CShape{
private:
public:
int r;
virtual double Area();
virtual void PrintInfo();
};

class CTriangle:public CShape{
private:
public:
int a, b, c;
virtual double Area();
virtual void PrintInfo();
};

double CRectangle::Area() {
return w*h;
}

void CRectangle::PrintInfo() {
cout <<"Rectangle:" <}

double CCircle::Area() {
return 3.14*r*r;
}

void CCircle::PrintInfo() {
cout <<"Circle:" <}

double CTriangle::Area() {
double p = (a + b + c) / 2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}

void CTriangle::PrintInfo() {
cout <<"Triangle:" <}

CShape *pShapes[100];
int MyCompare(const void *s1, const void *s2);

int main() {
int i, n;
CRectangle *pr; CTriangle *pt; CCircle *pc;

cin >> n;
for (int i = 0; i char c;
cin >> c;
switch (c){
case 'R':
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr;
break;
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt;
break;
}
}

qsort(pShapes, n, sizeof(CShape*), MyCompare);
for (int i = 0; i pShapes[i]->PrintInfo();
}
return 0;
}

int MyCompare(const void*s1,const void*s2){
double a1,a2;
CShape **p1;//s1,s2是 void*,不可写“*s1”来取得s1指向的内容
CShape **p2;
//s1,s2是指向 pShapes 数组中的元素,元素类型是CShape*,即 s1,s2是指向指针的指针
p1 = (CShape**)s1;
p2 = (CShape**)s2;
//那么想取得他们所指向的内容只能.*p1
a1 = (*p1)->Area();
a2 = (*p2)->Area();
if (a1 return -1;
}else if (a2 return 1;
}else{
return 0;
}
}

这里写图片描述

这里写图片描述
- 2

class Base {
public:
void fun1() { fun2(); }
virtual void fun2() { cout <<"Base::fun2()" <};
class Derived :public Base {
public:
virtual void fun2() { cout <<"Derived:fun2()" <};
int main() {
Derived d;
Base * pBase = &d;
pBase->fun1();
return 0;
}

结果

这里写图片描述

可以见下一段代码:

class Base {
public:
void fun1() { fun2(); }
virtual void fun2() { cout <<"Base::fun2()" <};
class Derived :public Base {
public:
virtual void fun2() { cout <<"Derived:fun2()" <};
int main() {
Base d;
Base * pBase = &d;
pBase->fun1();
return 0;
}

这里写图片描述

再见一段代码

class Base {
public:
void fun1() { this->fun2(); } //this是基类指针,fun2是虚函数,所以是多态
virtual void fun2() { cout <<"Base::fun2()" <};
class Derived :public Base {
public:
virtual void fun2() { cout <<"Derived:fun2()" <};
int main() {
Derived d;
Base * pBase = &d;
pBase->fun1();
return 0;
}

这里写图片描述

总结:在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时候才决定调用自己的还是派生的。


小quiz

class myclass {
public:
virtual void hello() { cout <<"hello from myclass" < virtual void bye() { cout <<"bye from myclass" <};
class son :public myclass {
public:
void hello() { cout <<"hello from son" < son() { hello(); };
~son() { bye(); };
};
class grandson :public son {
public:
void hello() { cout <<"hello from grandson" < void bye() { cout <<"bye from grandson" < grandson() { cout <<"constructing grandson" < ~grandson() { cout <<"destructing grandson" <};

int main() {
grandson gson;
son *pson;
pson = &gson;
pson->hello(); //多态
return 0;
}

这里写图片描述

解释:从一开始定义grandson类型 gson,就要从顶端开始执行构造函数。好,myclass调用默认构造函数,对于son,则调用hello()函数,注意,此时不是多态,因为是在构造函数里面,所以生成 “hello from son”,接着调用grandson的构造函数,生成“constructing grandson”;接着定义son指针,并且让pson指向gson,所以到pson->hello()的时候,打印出”hello from grandson”,此时是多态哦。最终调用各自析构函数,由底到顶。

多态实现原理

class Base {
public:
int i;
virtual void Print() { cout <<"Base:Print"; }
};
class Derived : public Base {
public:
int n;
virtual void Print() { cout <<"Drived:Print" <};
int main() {
Derived d;
cout <<sizeof(Base) <<"," <<sizeof(Derived) < return 0;
}

这里写图片描述

这里写一下自己的理解,base里面含有int类型,大小是4个字节,因为每一个有虚函数的类(或者由虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都存放着虚函数表的指针,所以多出来的4个字节就是存放虚函数表的地址的。
这里写图片描述


一开始我在想,为什么函数不占字节呢,其实,如果在基类中加入函数也是不占据字节的,不信,请看:

class Base {
public:
int i;
void print2() { cout <<"Base:Print-1"; }
virtual void Print() { cout <<"Base:Print"; }
};
class Derived : public Base {
public:
int n;
virtual void Print() { cout <<"Drived:Print" <};
int main() {
Derived d;
cout <<sizeof(Base) <<"," <<sizeof(Derived) < return 0;
}

这里写图片描述

因此,对于多态的原理,用以下两张图片即可解释:

这里写图片描述

这里写图片描述

即:多态的函数调用语句被编译成一系列根据基类指针所指向的(或者基类引用的)对象中存放的虚函数的地址,在虚函数表中查找虚函数地址,并调用相关指令。

虚析构函数

class CSon {
public: ~CSon() { };
};
class CGrandson :public CSon {
public: ~CGrandson() { };
};
int main() {
CSon *p = new CGrandson();
delete p;
return 0;
}

通过基类的指针删除派生类对象,只会调用基类的析构函数,这样会导致内存未被完全清除


因此,我们希望能够先调用派生类的析构函数,再调用基类析构函数(不可以将虚函数作为构造函数)

  • 1
class son{
public:
~son() { cout<<"bye from son"<};
class grandson : public son{
public:
~grandson(){ cout<<"bye from grandson"<};
int main(){
son *pson;
pson=new grandson;
delete pson;
return 0;
}

这里写图片描述

  • 2
class son {
public:
virtual ~son() { cout <<"bye from son" <};
class grandson : public son {
public:
~grandson() { cout <<"bye from grandson" <};
int main() {
son *pson;
pson = new grandson;
delete pson;
return 0;
}

这里写图片描述

纯虚函数和抽象类

  • 纯虚函数

纯虚函数:没有函数体的虚函数

class A {
private:
int a;
public:
virtual void Print() = 0; //纯虚函数
void fun() { cout <<“fun”; }
};
  • 抽象类

定义:包含纯虚函数的类
1、只能作为基类来派生新类使用
2、不能创建抽象类的对象
3、抽象类可以进行指针和引用–>由抽象类派生出来的对象

A a; // 错, A 是抽象类, 不能创建对象
A * pa; // ok, 可以定义抽象类的指针和引用
pa = new A; //错误, A 是抽象类, 不能创建对象
  • 纯虚函数和抽象类

抽象类中

  1. 成员函数可以调用纯虚函数
  2. 构造/析构函数不能调用纯虚函数

如果一个类从抽象类派生而来,只有当他实现基类中所有纯虚函数,才能成为非抽象类。
实现—诸如添加一个空括号等

class A {
public:
virtual void f() = 0; //纯虚函数
void g() { this->f(); } //ok
A() { } //f( ); // 错误
};
class B : public A {
public:
void f() { cout <<"B: f()" <};
int main() {
B b;
b.g();
return 0;
}

这里写图片描述


推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 加密世界下一个主流叙事领域:L2、跨链桥、GameFi等
    本文介绍了加密世界下一个主流叙事的七个潜力领域,包括L2、跨链桥、GameFi等。L2作为以太坊的二层解决方案,在过去一年取得了巨大成功,跨链桥和互操作性是多链Web3中最重要的因素。去中心化的数据存储领域也具有巨大潜力,未来云存储市场有望达到1500亿美元。DAO和社交代币将成为购买和控制现实世界资产的重要方式,而GameFi作为数字资产在高收入游戏中的应用有望推动数字资产走向主流。衍生品市场也在不断发展壮大。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
author-avatar
进出口产地
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有