IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++面向对象程序设计(六) -> 正文阅读

[C++知识库]C++面向对象程序设计(六)

C++面向对象程序设计(六)

1.虚函数和多态的基本概念

虚函数:在类的定义中,前面有virtual关键字的成员函数就是虚函数。

class base{
  virtual int get();
};
int base::get(){}

virtual关键字只用在类定义里的函数声明中,写函数体时不用。

多态的表现形式一

  1. 派生类的指针可以赋值给基类指针

  2. 通过基类指针调用基类和派生类中的同名虚函数时

    1. 若该指针指向一个基类的对象,那么被调用是基类的虚函数
    2. 若该指针指向一个派生类的对象,那么被调用的事派生类的虚函数,

    这种机制就叫做多态

class CBase{
  public:
  virtual void SomeVirtualFunction(){}
};
class CDerived:public CBase{
  public:
  virtual void SomeVirtualFunction(){}
};
int main(){
  CDerived ODerived;
  CBase *p=&ODerived;
  p->SomeVirtualFunction();//调用哪个虚函数取决于p指向哪种类型的对象
  return 0;
}

多态的表现形式二

  1. 派生类的对象可以赋值给基类引用

  2. 通过基类引用调用基类和派生类中的同名虚函数时

    1. 若该引用引用的是一个基类的对象,那么被调用是基类的虚函数
    2. 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。

    这种机制也叫做多态

class CBase{
  public:
  virtual void SomeVirtualFunction(){}
};
class CDerived:public CBase{
  public:
  virtual void SomeVirtualFunction(){}
};
int main(){
  CDerived ODerived;
  CBase & r=ODerived;
  r.SomeVIrtualFunction();//调用哪个虚函数取决于r引用哪种类型的对象
  return 0;
}

多态的示例

class A{
  public:
  virtual void Print(){
    cout<<"A::Print"<<endl;
  }
};
class B:public A{
  public:
  virtual void Print(){
    cout<<"B::Print"<<endl;
  }
};
class D:public A{
  public:
  virtual void Print(){
    cout<<"D::Print"<<endl;
  }
};
class E:public B{
  virtual void Print(){
    cout<<"E::Print"<<endl;
  }
};
int main(){
  A a;B b;E e;D d;
  A * pa=&a; B * pb=&b;
  D * pd=&d; E * pe=&e;
  pa->Pirnt();//a.Print()
  pa=pb;
  pa->Pinrt();//b.Print()
  pa=pd;
  pd->Print();//d.Print()
  pa=pe;
  pa->Print();//e.Print()
  return 0;
}

多态的作用

在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少。

2.使用多态的游戏程序实例

《魔法门之英雄无敌》

游戏中有多种怪物,每种怪物都有一个类与之对应,每个怪物就是一个对象。

CSoldier CDragon CPhonex CAngel

怪物能够互相攻击,攻击敌人和被攻击时都有相应的动作,动作是通过对象的成员函数实现的。

游戏版本升级时,要增加新的怪物–雷鸟

如何编程才能使升级的代码改动和增加量较小?

新增类:DThunderBird

基本思路:

为每个怪物类编写Attack,FightBack和Hurted成员函数

Attack函数表现攻击动作,攻击某个怪物,并调用被攻击怪物的Hurted函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的FightBcak成员函数,遭受攻击怪物反击。

Hurted函数减少自身生命值,并表现受伤动作。

FightBack成员函数表现反击动作,并调用反击对象的Hurted成员函数,使被反击对象受伤。

设置基类CCreature,并使CWolf等其他类都由CCreature派生而来。

  • 非多态的实现方法
class CCreature{
  protected:
  int nPower;
  int nLifeValue;
};
class CDragon:public CCreature{
  public:
  void Attack(CWolf * pWolf){
    pWolf->Hurted(nPoweer);
    pWolf->FightBack(this);
  }
  void Attack(CGhost * pGhost){
    pGhost->Hurted(nPower);
    pGhost->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函数,以及n个FightBack函数,对于其他类也是如此

如果游戏版本升级,增加了新的怪物雷鸟,CThunderBird,则程序改动较大

那么所有的类都需要增加两个成员函数

? void Attack(CThunderBird * pThunderBird);

? void FightBack(CThunderBid * pThunderBird);

在怪物种类多的时候,工作量比较大。

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

基类只有一个Attack成员函数,也只有一个FightBack成员函数,所有CCreature的派生类也是这样。

class CDragon:public CCreature{
  public:
  virtual void Attack(CCreature *pCreature);
  virtual void Hurted(int nPower);
  virtual void FightBack(CCreature * pCreature);
};
void CDragon::Attack(CCreature * p){
  p->Hurted(m_nPower);
  p->FightBack(this);
}
void CDragon::Hurted(int nPower){
  m_nLifeValue-=nPower;
}
void CDragon::FightBack(CCreature *p){
  p->Hurted(m_nPower/2);
}

多态实现方法的优势

如果游戏版本升级,增加了新的怪物雷鸟CThunderBird

只需要编写新类CThunderBird,不需要在已有的类里专门为新怪物增加:

void Attack(CThunderBird * pThunderBird);

void FightBack(CThunderBird * pThunderBird);

成员函数,已有的类可以原封不动。

void CDragon::Attack(CCreature *p){
  p->Hurted(m_nPower);
  p->FightBack(this);
}
CDragon Dragon;CWolf Wolf;CGhost Ghost;
CThunderBird Bird;
Dragon.Attack(&Wolf);
Dragon.Attack(&Ghost);
Drragon.Attack(&Bird);

根据多态的规则,上面的(1)(2)(3)进入到CDragon::Attack函数后,能分别调用:

CWolf::Hurted

CGhost::Hurted

CBird::Hurted

3.多态实例-几何形体处理程序

几何形体处理程序:输入若干个几何形体的参数,要求按面积排序输出。输出时指明形状。

input

第一行是几何形体数目n,下面有n行,每行以一个字母c开头

若c是‘R’,则代表一个矩形,本行后跟着两个整数,分别是矩形的宽和高;

若c是‘C’,则代表一个圆,本行后面跟着一个整数代表其半径;

若c是‘T’,则代表一个三角形,本行后面跟着三个整数,代表三条边的长度

output

按面积从小到大依次输出每个几何形体的种类及面积。每行一个几何形体,输出格式为:

形体面积:面积

#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;
class CShape{
  public:
  virtual double Area()=0;//纯虚函数
  virtual void PrintInfo()=0;
};
class CRectangle:public CShape{
  public:
  int w,h;
  virtual double Area();
  virtual void PrintInfo();
};
class CCircle:public CShape{
  public:
  int r;
  virtual double Area();
  virtual void PrintInfo();
};
class CTriangle:public CShape{
  public:
  int a,b,c;
  virtual double Area();
  virtual double PrintInfo();
};
double CRectangle::Area(){
  return w*h;
}
void CRectangle::PrintInfo(){
  cout<<"Rectangle:"<<Area()<<endl;
}
double CCricle::Area(){
  return 3.14*r*r;
}
void CCircle::PrintInfo(){
  cout<<"Circle:"<<Area()<<endl;
}
double CTriangle::Area(){
  double p=(a+b+c)/2.0;
  return sqrt(p*(p-a)*(p-b)*(p-c));
}
void CTriangle::PinrtInfo(){
  cout<<"Triangle:"<<Area()<<endl;
}
CShape * pShapes[100];
int MyCompare(const void *s1,const void *s2);
int main(){
  int i;int n;
  CRectangle *pr;CCircle *pc;CTriangle *pt;
  cin>>n;
  for(int i=0;i<n;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,sizepof(CShape *),MyCompare);
  for(i=0;i<n;i++)
    pShapes[i]->PrintInfo();
  return 0;
}
int MyCompare(const void *s1,const void *s2){
  double a1,a2;
  CShape **p1;//s1,s2是void *,不可写*s1
  CShape **p2;
  p1=(CShape **)s1;//s1,s2指向pShapes数组中的元素,数组元素的类型是CShape *
  p2=(CShape **)s2;//故p1,p2都是指向指针的指针,类型为CShape*
  a1=(*p1)->Area();//*p1的类型是CShape*,是基类指针,故此句为多态
  a2=(*p2)->Area();
  if(a1<a2)
    return -1;
  else if(a2<a1)
    return 1;
  else
    return 0;
}

如果添加新的几何形体,比如五边形,则只需要从CShape派生出CPentagon,以及在main中的switch语句中增加一个case,其余部分不变。

用积累指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能各个派生类对象做各种操作,是很常用的做法

多态的另一个栗子

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

在非构造函数,非析构函数的成员函数中调用虚函数,是多态。

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

class myclass{
  public:
  virtual void hello(){cout<<"hello from myclass"<<endl;}
  virtual void bye(){cout<<"bye from myclass"<<endl;}
};
class son:public myclass{
  void hello(){cout<<"hello from son"<<endl;}
  son(){hello();}
  ~son(){bye();}
};
class grandson:public son{
  public:
  void hello(){cout<<"hello from grandson"<<endl;}
  void bye(){cout<<"bye from grandson"<<endl;}
  grandson(){cout<<"constructing grandson"<<endl;}
  ~grandson(){cout<<"destructing grandson"<<endl;}
};
int main(){
  grandson gson;
  son*pson;
  pson=&gson;
  pson->hello();//多态
  return 0;
}
//结果
hello from son
constructring grandson
hello from grandson 
destructing grandson
bye from myclass

派生类中和基类中虚函数同名同参数表的函数,不加virtual也会自动成为虚函数。

  • 虚函数的访问权限
class Base{
  private:
  virtual void fun2(){cout<<"Base::fun2()"<<endl;}
};
class Derived:public Base{
  public:
  virtual void fun2(){cout<<"Derived::fun2()"<<endl;}
};
Derived d;
Base *pBase=&d;
pBae->fun2();//编译出错

编译出错是因为fun2()是Base的私有成员。即使运行到此时实际上调用的应该是Derived的公有成员fun2()也不行,因为语法检查是不考虑运行结果的。

如果将Base中的private换成public,即使Derived中的fun2()是private,编译依然能够通过,也能正确戴哦用Derived::fun2()。

4.虚函数的实现原理

“多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用基类还是派生类的函数,运行时才确定,这叫“动态联编”。

class Base{
  public:
  int i;
  virtual void Print(){cout<<"Base::Print";}
};
class Derived:public Base{
  public:
  int n;
  virtual void Print(){cout<<"Derived:Print"<<endl;}
};
int main(){
  Derived d;
  cout<<sizeof(Base)<<","<<sizeof(Derived);
  return 0;
}
//运行输出结果:8,12

多态实现的关键—虚函数表

每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。多出来的4个字节就是用来放虚函数表的地址的。

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

#include <iostream>
using namespace std;
class A{
  public:
  virtual void Func(){cout<<"A::Func"<<endl;}
};
class B:public A{
  public:
  virtual void Func(){cout<<"B::Func"<<endl;}
};
int main(){
  A a;
  A *pa=new B();
  pa->Func();//64位程序指针为八字节
  long long *p1=(long long *)&a;
  long long *p2=(long long *)pa;
  *p2=*p1;
  pa->Func();
  return 0;
}
//B::func
//A::func

5.虚析构函数、纯虚函数

虚析构函数

通过基类的指针删除派生类的对象时,通常情况下只调用基类的析构函数

但是,删除一个派生类的对象时,应该先调用帕苏珩类的析构函数,然后再调用基类的析构函数。

解决办法:把基类的析构函数声明为virtual

派生类的析构函数可以virtual不进行声明

通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数。

一般来说,一个类如果定义类虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义为虚函数。

注意:不允许以虚函数作为构造函数

class son{
  public:
  ~son(){cout<<"bye from son"<<endl;}
};
class grandson:public son{
  public:
  ~grandson(){cout<<"bye from grandson"<<endl;}
};
int main(){
  son *pson;
  pson=new grandson();
  delete pson;
  return 0;
}
//输出 bye from son			(没有执行grandson::~grandson)

class son{
  public:
  virtual ~son(){cout<<"bye from son"<<endl;}
};
class grandson:public son{
  public:
  ~grandson(){cout<<"bye from grandson"<<endl;}
};
int main(){
  son *pson;
  pson=new grandson();
  delete pson;
  return 0;
}
//输出 bye from grandson	
//bye fron son
  • 纯虚函数和抽象类

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

class A{
  private:
  int a;
  public:
  virtual void Print()=0;//纯虚函数
  void fun(){cout<<"fun";}
};

包含纯虚函数的类叫做抽象类

抽象类只能作为基类来派生新类使用,不能创建抽象类的对象

抽象类的指针和引用可以指向由抽象类派生出来的类的对象

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

在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或 析构函数内部不能调用纯虚函数

如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函数,它才能成为非抽象类

class A{
  public:
  virtual void f()=0;
  void g(){this->f();}//ok
  A(){			//f() 错误
  }
};
class B:public A{
  void f(){cout<<"B:f()"<<endl;}
};
int main(){
  B b;
  b.g();				//输出 	B:f()
  return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-19 07:48:45  更:2021-09-19 07:48:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 12:34:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计