博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
慕课网-C++远征之多态篇(中)-学习笔记
阅读量:5890 次
发布时间:2019-06-19

本文共 8340 字,大约阅读时间需要 27 分钟。

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 <
  • 抽象类的子类也有可能是抽象类。抽象类的子类只有把抽象类当中的所有纯虚函数都做了实现,子类才可以实例化对象。
  • 上面代码中work只把子类的两个实现了一个。只有dustman
    才能实例化对象。

抽象类代码示例

抽象类代码

如果Worker没有实现work。则不可以实例化work。

当Worker的子类dustman实现了work。就可以实例化dustman。

代码:

#ifndef PERSON_H//假如没有定义#define PERSON_H//定义#include 
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
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
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
using namespace std;Dustman::Dustman(string name, int age) :Worker(name, age){}void Dustman::work() { cout << "扫地" << endl;}//main.cpp//#include
#include "Person.h"#include
#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 
#include
#include
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 canshotvoid 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_Hclass Flyable{public:    virtual void takeoff() = 0;//起飞    virtual void land() = 0; //降落};#endif//#ifndef PLANE_H#define PLANE_H#include "Flyable.h"#include 
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
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
#include "FighterPlane.h"using namespace std;FighterPlane::FighterPlane(string code) :Plane(code){}void FighterPlane::takeoff(){ cout << "FighterPlane -- takeoff" <
using namespace std;#include
#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 
using namespace std;#include
#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 
#include
#include
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

转载地址:http://yzwsx.baihongyu.com/

你可能感兴趣的文章
Web的项目管理工具Redmine(对比选择最佳开源项目)- Codendi,dotProject,Launchpad,Project.net,Redmine...
查看>>
Linux基础(十)--bash脚本简介
查看>>
Cocos2d-x Android开发环境的配置
查看>>
NTP
查看>>
扩展根目录空间
查看>>
簡單收所有經過網卡的包 promiscuous mode GNU Linux
查看>>
高性能高并发--分布式,集群
查看>>
git获取远程分支
查看>>
文字与格式字符串不匹配
查看>>
我的友情链接
查看>>
window2008 域控更改密码策略
查看>>
redis问题
查看>>
微信服务号接入提示token认证失败
查看>>
CentOS 6.4部署LAMP(多站点环境)
查看>>
javaweb开发之jsp
查看>>
2013年总结(2)-财务收入与支出
查看>>
React创建组件的三种方式
查看>>
Nginx 之 Location基础理解及实战
查看>>
企业级nginx服务优化合集
查看>>
maven配置cargo实现远程部署项目到tomcat
查看>>