慕课网-C++远征之多态篇(中)-学习笔记

c++远征之多态篇

纯虚函数 & 抽象类

例子:

class Shape
{
public:
    virtual double calcArea()//虚函数
    {return 0;}
    virtual double calcPerimeter() = 0;//纯虚函数
}

纯虚函数:

  • 没有函数体
  • 直接等于0
普通虚函数有值,纯虚函数直接为0
  • 在虚函数表中直接写为0,

  • 包含纯虚函数的类,就是抽象类。上面含有纯虚函数的shape类就是一个抽象类。

  • 纯虚函数无法调用,所以抽象类无法实例化对象

class Person
{
public:
    Person(string name);
    virtual void work() =0;
    virtual void printInfo() =0;
};
class Worker: public Person
{
public:
    Worker(string name)
    virtual void work() = 0;
    virtual void printInfo() { cout << m_strName <<endl;}
private:
    string m_strName;
};
class Dustman: public Worker
{
public:
    Worker(string name)
    virtual void work() {cout << "扫地"};
    virtual void printInfo() { cout << m_strName <<endl;}
private:
    string m_strName;
};
  • 抽象类的子类也有可能是抽象类。抽象类的子类只有把抽象类当中的所有纯虚函数都做了实现,子类才可以实例化对象。
  • 上面代码中work只把子类的两个实现了一个。只有dustman
    才能实例化对象。

抽象类代码示例

抽象类代码

如果Worker没有实现work。则不可以实例化work。
当Worker的子类dustman实现了work。就可以实例化dustman。

代码:

#ifndef PERSON_H//假如没有定义
#define PERSON_H//定义

#include <string>
using namespace std;

class Person
{
public:
    Person(string name);
    virtual ~Person() {};
    virtual void work() =0;
private:
    string m_strName;
};

#endif //结束符

//
#include "Person.h"


Person::Person(string name)
{
    m_strName = name;

}
//
#include <string>
using namespace std;
#include "Person.h"
class Worker:public Person
{
public:
    Worker(string name,int  age);
    //virtual void work();
    virtual ~Worker() {};
private:
    int  m_iAge;

};

//
#include "Worker.h"
#include <iostream>
using namespace std;
Worker::Worker(string name,int age):Person(name)
{
    m_iAge = age;
}

//void Worker::work()
//{
//  cout << "work()" << endl;
//}

#ifndef DUSTMAN_H
#define DUSTMAN_H
#include "Worker.h"
class Dustman :public Worker
{
public:
    Dustman(string name, int age);
    virtual void work();
};
#endif 

#include "Dustman.h"
#include <iostream>
using namespace std;

Dustman::Dustman(string name, int age) :Worker(name, age)
{

}
void Dustman::work() {
    cout << "扫地" << endl;
}

//main.cpp
//
#include <iostream>
#include "Person.h"
#include <stdlib.h>
#include "Dustman.h"
int main()
{
    //Person person("张三");//“Person”: 不能实例化抽象类
    //Worker worker("zhangsan", 17);// “Worker”: 不能实例化抽象类
    Dustman dustman("zhangsan", 20);

    
    system("pause");
    return 0;
}

一个抽象类之所以叫抽象类,是因为它里面有一个或以上的纯虚函数,纯虚函数的写法是virtual 函数返回类型 函数名()=0 ,纯虚函数里面不用写任何代码。
当我们定义了一个纯虚函数,他同样会在虚函数表中出现,如图calcPerimeter ptr就是纯虚函数的指针,他的只是0(意思就是他没有指向代码区,不会实现任何方法)。他这样的目的是为了让子类在继承他的时候,再实现他的方法。

例如circle继承了shape,circle为了可以计算周长,定义了一个叫calcPerimeter的方法,因此把他父类shape的纯虚函数calcPerimeter覆盖了,这样就可以通过子类circle来计算周长。

练习
  • 只有函数声明没有函数定义,直接等于0的虚函数是纯虚函数。
  • 含有纯虚函数的类叫做抽象类。
  • 不可以使用含有纯虚函数的类实例化对象。
  • 抽象类的子类也可以是抽象类。
单元练习

定义一个动物(animal)类,要求含有虚函数eat和纯虚函数move以及数据成员m_strName,并定义构造函数和虚析构函数
定义一个狗(Dog)类,要求公有继承动物类,定义构造函数和虚析构函数,并实现自己的eat和move函数

通过动物类实例化狗类,调用狗类当中的成员函数

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义动物类:Animal
 * 虚函数:eat()
 * 纯虚函数:move()
 * 数据成员:m_strName
 */
class Animal
{
public:
    // 默认构造函数
    Animal(){};
    // 含参构造函数
    Animal(string name){m_strName = name; cout << "Animal" << endl;}
    // 虚析构函数
    virtual ~Animal(){cout << "~Animal" << endl;}
    // 虚成员函数
    virtual void eat(){cout << "Animal--" << m_strName << "-- eat" << endl;}
    // 纯虚函数
    virtual void move() = 0;
public:
    // 数据成员
    string m_strName;
};

/**
 * 定义狗类:Dog
 * 公有继承动物类
 * 虚成员函数:eat()、move()
 */
class Dog: public Animal
{
public:
    // 默认构造函数
    Dog(){};
    // 含参构造函数
    Dog(string name){m_strName = name; cout << "Dog" << endl;}
    // 虚析构函数
    virtual ~Dog(){cout << "~Dog" << endl;}
    // 虚成员函数eat()
    virtual void eat(){cout << "Dog--" << m_strName << " -- eat" << endl;}
    // 虚成员函数move()
    virtual void move(){cout << "Dog--" << m_strName << " -- move" << endl;}
public:
    // 数据成员
    string m_strName;
};

int main(void)
{
    // 通过动物类实例化狗类
    Animal *p = new Dog("狗类");
    // 调用成员函数
    p ->eat();
    p ->move();
    // 释放内存
    delete p;
    p = NULL;
    
    return 0;
}

运行结果:

运行结果

因为狗实现了动物类的所有纯虚构函数,所以它可以被实例化。因为父类的eat和move都是虚函数。所以子类的纯虚函数表覆盖了父类的方法。因为是虚析构函数所以父类指针销毁。子类的也一起没了。

如果不是分.h和.cpp文件写,记得在默认构造函数加上{}

接口类

  • 含有纯虚函数的类叫抽象类;仅仅含有纯虚函数的类叫接口类
  • 接口类没有数据成员,所有成员函数都是纯虚函数
class Shape
{
public:
    virtual double calcArea() = 0;//计算面积
    virtual double calcPerimeter() = 0;//计算周长
}

上述shape类是一个接口类。

接口类表达的是一种能力或协议:

class Flyable
{
public:
    virtual void takeoff() = 0;//起飞
    virtual void land() = 0; //降落

}

飞行能力.

class Bird:public Flyable
{
public:
    virtual void takeoff(){}
    virtual void land(){}
private:
    //...
}

鸟要实例化就得实现起飞和降落。

如果我们在使用的时候有这样一个函数。函数需要传入的指针是能飞的。鸟继承自父类flyable。is-a关系。

飞行比赛

class flyMatch(Flyable *a,Flyable *b)
{
    //...
    a->takeoff();
    b->takeoff();
    a->land;
    b->land;

}

如果你要参加飞行比赛就得会飞。你要会飞就得实现这两个函数。相当于一种协议。

同样道理射击能力

class CanShot
{
public:
    virtual void aim() = 0;//瞄准
    virtual void reload() =0;//装弹
}

飞机多继承飞行能力和射击之后。变成战斗机。
它要实例化就得实现下面四个函数。

class Plane:public Flyable,public CanShot
{
    virtual void takeoff(){}
    virtual void land(){}
    virtual void aim(){}
    virtual void reload(){}
}
//传入两个plane。plane is a canshot
void fight(CanShot *a,CanShot *b)
{
    a -> aim();
    b -> aim();
    a -> reload();
    b -> reload();
}

复杂情况:

class Plane:public Flyable
{
    //...
    virtual void takeoff(){}
    virtual void land(){}
}

class FighterJet:public Plane,public CanShot
{
    virtual void aim(){}
    virtual void reload(){}
}

这种继承情况下plane不是一个接口类。而canshot是一个接口类

void airBattle(FighterJet *a,FighterJet *b)
{
    //调用flyable中约定的函数
    //调用canshot中约定的函数
}

接口类代码示例

要求
#ifndef FLYABLE_H
#define FLYABLE_H

class Flyable
{
public:
    virtual void takeoff() = 0;//起飞
    virtual void land() = 0; //降落
};
#endif

//
#ifndef PLANE_H
#define PLANE_H
#include "Flyable.h"
#include <string>
using namespace std;
class Plane :public Flyable
{
public:
    Plane(string code);
    virtual void takeoff();
    virtual void land();
    void printCode();
private:
    string m_strCode;
};
#endif

//
#include "Plane.h"
#include <iostream>
using namespace std;
Plane::Plane(string code)
{
    m_strCode = code;
}
void Plane::takeoff()
{
    cout << "plane - takeoff" << endl;

}
void Plane::land()
{
    cout << "plane - land" << endl;

}
void Plane::printCode()
{
    cout << m_strCode << endl;
}

//
//
#ifndef FIGHTERPLANE_H
#define FIGHTERPLANE_H

#include "Plane.h"
class FighterPlane:public Plane
{
public:
    FighterPlane(string code);
    virtual void takeoff();
    //因为plane已经实现过了,所以它可实现也可也不
    virtual void land();
};
#endif 

//
#include <iostream>
#include "FighterPlane.h"
using namespace std;

FighterPlane::FighterPlane(string code) :Plane(code)
{

}
void FighterPlane::takeoff()
{
    cout << "FighterPlane -- takeoff" <<endl;

}
void FighterPlane::land()
{
    cout << "FighterPlane -- land" << endl;

}

//
//
#include <iostream>
using namespace std;
#include <stdlib.h>
#include "FighterPlane.h"

void flyMatch(Flyable *f1,Flyable *f2)
{
    f1->takeoff();
    f1->land();
    f2->takeoff();
    f2->land();

}
int main(void)
{
    Plane p1("001");
    Plane p2("002");
    p1.printCode();
    p2.printCode();

    flyMatch(&p1,&p2);

    system("pause");
    return 0;
}

运行结果

看出飞机可以作为参数传入flymatch。限制了传入参数的对象类型。可以在函数体中调用接口类的方法。

int main(void)
{
    FighterPlane p1("001");
    FighterPlane p2("002");
    p1.printCode();
    p2.printCode();

    flyMatch(&p1,&p2);

    system("pause");
    return 0;
}
战斗机运行结果

让战斗机继承flyable和plane。飞机不再继承flyable。

此时它就既可以当做flyable传入,也可以传入plane。

#include <iostream>
using namespace std;
#include <stdlib.h>
#include "FighterPlane.h"

void flyMatch(Plane *f1,Plane *f2)
{
    f1->printCode();
    f2->printCode();

}

int main(void)
{
    FighterPlane p1("001");
    FighterPlane p2("002");

    flyMatch(&p1,&p2);

    system("pause");
    return 0;
}

//
class FighterPlane:public Plane,public Flyable
{
}
class Plane
{

}

可以看出同时继承两个。既可以当做flyable传入,也可以传入plane。

  • 接口类中仅有纯虚函数,不能含有其他函数,也不能含有数据成员。
  • 可以使用接口类指针指向其子类对象,并调用子类对象中实现的接口类中的纯虚函数。
  • 一个类可以继承一个接口类,也可以继承多个接口类。
  • 一个类继承接口类的同时也可以继承非接口类。

巩固练习

定义一个能够射击(CanShut)类,要求含有纯虚函数aim和reload
定义一个枪(Gun)类,继承CanShut类,并实现函数aim和reload。
定义函数Hunting(CanShut *s),调用s指向对象的函数。

在函数中传入Gun的对象,查看结果

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义射击类:CanShut
 * 定义纯虚函数:aim、reload
 */
class CanShut
{
public:
    virtual void aim() =0;
    virtual void reload() =0;
};

/**
 * 定义枪类:Gun
 * 公有继承射击类
 * 实现成员函数:aim、reload
 */
class Gun : public CanShut
{
public:
    virtual void aim()
    {
         cout << "Gun -- aim" << endl;
    }
    virtual void reload()
    {
         cout << "Gun -- reload" << endl;
    }
};

/**
 * 定义含参函数射击:hunting
 * 调用参数的aim与reload函数
 */
void hunting(CanShut *s)
{
    s->aim();
    s->reload();
}

int main(void)
{
    // 实例化枪对象
    CanShut *p = new Gun();
    // 调用含参函数hunting,将对象枪传入函数中
    hunting(p);//因为已经是个指针。所以直接传入指针本身。
    //如果是对象。那要加上&取地址符
    // 释放内存
    delete p;
    p = NULL;

    return 0;
}

输出:

  • aim
  • Gun
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值