面向对象三大特征:封装,继承,多态ios
多态: 发出一条命令时,不一样的对象接收到一样的命令作出的动做不一样c++
多态篇会学习到的目录:数组
多态的内容不少,概念也听起来有点变态ide
不过这也是最精彩的部分了。函数
什么是多态?学习
多态是指相同对象收到不一样消息或不一样对象收到相同消息时产生不一样的动做this
静态多态 & 动态多态spa
例子:3d
矩形类有两个同名的计算面积的函数,参数不一样,这是两个互为重载的函数。指针
class Rect { public: int calcArea(int width); int calcArea(int width,int height);//互为重载 } int main(void) { Rect rect; rect.calcArea(10); rect.calcArea(10,20); return 0; }
当咱们传入一个参数,两个参数会调用两个不一样的同名函数。
计算机在编译阶段就会自动根据参数使用不一样的参数来肯定使用哪一个函数。
这里程序在运行以前也就是编译阶段,就决定了运行哪一个函数。很早的就决定运行哪一个了,这种状况就叫作早绑定,或静态多态。
圆形和矩形都有本身的计算面积的方法,两种方法确定是不一样的。
这就是对不一样的对象下达相同的指令,却作着不一样的操做。
动态多态前提:必须以封装(数据封装到类中)与继承(继承关系)为基础。
动态多态,起码有两个类,一个是子类,一个是父类。使用三个类时表现的更为明显。
代码例子:
Shape类
class Shape { public: double calcArea() { cout << "calcArea" <<endl; return 0; } }
继承Shape的Circle类
class Circle:public Shape { public: Circle(double r); double calcArea(); private: double m_dR; } //Circle计算面积实现 double Circle::calcArea() { return 3.14 * m_dR * m_dR; } //矩形 class Rect::public Shape { public: Rect(double width,double height); double calcArea(); private: double m_dWidth; double m_dHeight; } //矩形计算面积实现 double Rect::calcArea() { return m_dWidth * m_dHeight; }
main函数中的使用:
int main() { Shape *shape1 = new Circle(4.0); Shape *shape2 = new Rect(3.0,5.0); shape1 -> calcArea(); shape2 -> calcArea(); return 0; }
上述代码的效果将不是咱们预期的多态,会调用两次父类的clacArea();
使用父类指针指向子类对象,子类与父类有同名函数,加virtual成为虚函数,则调用相同的函数名的时候调用的是子类的函数。 不添加的时候,使用父类指针指向的是父类自身的calc。
使用virtual关键字使得成员函数变成虚函数。
class Shape { public: virtual double calcArea() //虚函数 { cout << "calcArea" <<endl; return 0; } }
在父类中将想要实现多态的函数添加virtual关键字,使其变为虚函数。
加上virtual后,父类指针指向子类对象。子类与父类有同名函数,父类指针调用到的是子类方法。
若是是用父类指针,不加virtual关键字的话就会调用父类。 若是是用子类指针,则调用子类,由于父类继承过来的同名函数形成了隐藏,只能经过.Father::
访问
附录代码 2-2-VirtualFunction:
Shape.h
#ifndef SHAPE_H #define SHAPE_H #include <iostream> using namespace std; class Shape { public: Shape(); ~Shape(); double calcArea(); }; #endif
Shape.cpp
#include "Shape.h" Shape::Shape() { cout << "Shape()" << endl; } Shape::~Shape() { cout << "~Shape()" << endl; } double Shape::calcArea() { cout << "Shape - > calcArea()" << endl; return 0; }
Circle.h
#ifndef CIRCLE_H #define CIRCLE_H #include "Shape.h" class Circle:public Shape { public: Circle(double r); ~Circle(); double calcArea(); // 同名且参数返回值一致 protected: double m_dR; }; #endif
Circle.cpp
#include "Circle.h" Circle::Circle(double r) { cout << "Circle()" << endl; m_dR = r; } Circle::~Circle() { cout << "~Circle()" << endl; } double Circle::calcArea() { cout << "Circle-->calcArea()" << endl; return 3.14 * m_dR * m_dR; }
Rect.h
#ifndef RECT_H #define RECT_H #include "Shape.h" class Rect : public Shape { public: Rect(double width,double height); ~Rect(); double calcArea(); protected: double m_dwidth; double m_dHeight; }; #endif // RECT_H
Rect.cpp
#include "Rect.h" Rect::Rect(double m_dwidth, double m_dHeight) { cout << "Rect()" << endl; this->m_dHeight = m_dHeight; this->m_dwidth = m_dwidth; } Rect::~Rect() { cout << "~Rect()" << endl; } double Rect::calcArea() { cout << "Rect::calcArea()"<< endl; return m_dwidth * m_dHeight; }
main.cpp
#include <iostream> #include "Circle.h" #include "Rect.h" #include <stdlib.h> using namespace std; int main() { // 定义两个父类指针,指向子类对象 Shape *shape1 = new Circle(3.0); Shape *shape2 = new Rect(3.0, 4.0); shape1->calcArea(); shape2->calcArea(); //当基类不添加virtual时。打印两遍基类的。 delete shape1; shape1 = NULL; delete shape2; shape2 = NULL; system("pause"); return 0; }
上述代码问题1: 销毁父类指针是否能够连带销毁子类对象。
问题2: 使用指向子类对象的父类指针是否能直接调用到子类方法。
能够看到问题1,只销毁了父类对象,子类对象没有被一并销毁。问题2,父类指针调用的是父类中的方法,子类方法并无被调用到。
在给shape的clacArea函数加上virtual后,父类指针可直接调用到子类的成员函数。
Shape.h中的析构函数,与子类重名的函数都加上virtual
class Shape { public: Shape(); virtual ~Shape(); virtual double calcArea(); };
推荐为子类也加上virtual。这里是两个同级(儿子级)对象实例化,因此实例化出两个爸爸是正常的。 若是是一个儿子有两个爸爸,两个爸爸有同名数据成员,应该虚继承(即用子类本身的,两爸爸的没法抉择,谁的就都不要了)。
动态多态的内存泄漏
class Shape { public: Shape(); virtual double calcArea(); }
与以前的相比多定义了一个指针数据成员,圆心坐标。
class Circle: public Shape { public: Circle(int x,int y,double r); ~Circle(); virtual double calcArea(); private: double m_dR; Coordinate *m_pCenter;//圆心坐标 }
构造函数中实例化Coordinate,析构函数中释放掉。
Circle::Circle(int x,int y,double r) { m_pCenter = new Coordinate(x,y); m_dR = r; } Circle::~Circle() { delete m_pCenter; m_pCenter = NULL; }
上述代码咱们能够实现堆中内存的释放。
可是在多态中:
int main(void) { Shape *shape1 = new Circle(3,5,4.0) shape1 -> calcArea(); delete shape1; shape1 = NULL; return 0; }
delete后面跟着父类的指针。只会执行父类的析构函数。那么就没法执行Circle的析构函数,就会形成内存泄露。
以前虽然没有执行子类的析构函数,可是由于子类没有new 申请内存,因此没有泄露。
上述代码在Shape的析构函数未添加virtual时将只会释放Shape对象。而实例化出的Circle对象不会被销毁。
父类指向的子类对象,会先执行子类的析构函数,而后执行父类析构函数。
virtual -> 析构函数
只须要在基类的析构函数添加virtual。父类指针能够一块儿销毁掉子类的对象。
class Animal { public: virtual static int getCount()//由于被修饰过的静态函数是类的,不属于任何一个对象。 }
会忽略inline。使他变成一个纯粹的虚函数。
class Animal { public: inline virtual int eat() { } }
2-5-VirtualDestructorFunction
Shape.h:
#ifndef SHAPE_H #define SHAPE_H #include <iostream> using namespace std; class Shape { public: Shape(); virtual ~Shape(); virtual double calcArea(); }; #endif
Shape.cpp
#include "Shape.h" Shape::Shape() { cout << "Shape()" << endl; } Shape::~Shape() { cout << "~Shape()" << endl; } double Shape::calcArea() { cout << "Shape - > calcArea()" << endl; return 0; }
Rect.h:
#ifndef RECT_H #define RECT_H #include "Shape.h" class Rect : public Shape { public: Rect(double width,double height); ~Rect(); double calcArea(); protected: double m_dwidth; double m_dHeight; }; #endif // RECT_H
Rect.cpp:
#include "Rect.h" Rect::Rect(double m_dwidth, double m_dHeight) { cout << "Rect()" << endl; this->m_dHeight = m_dHeight; this->m_dwidth = m_dwidth; } Rect::~Rect() { cout << "~Rect()" << endl; } double Rect::calcArea() { cout << "Rect::calcArea()"<< endl; return m_dwidth * m_dHeight; }
Circle.h 添加坐标类数据成员指针:
#ifndef CIRCLE_H #define CIRCLE_H #include "Shape.h" #include "Coordinate.h" class Circle:public Shape { public: Circle(double r); ~Circle(); double calcArea(); protected: double m_dR; Coordinate *m_pCenter; }; #endif
Circle.cpp 实例化坐标对象,析构中释放:
#include "Circle.h" Circle::Circle(double r) { cout << "Circle()" << endl; m_dR = r; m_pCenter = new Coordinate(3, 5); } Circle::~Circle() { cout << "~Circle()" << endl; delete m_pCenter; m_pCenter = NULL; } double Circle::calcArea() { cout << "Circle-->calcArea()" << endl; return 3.14 * m_dR * m_dR; }
Coordinate.h
#ifndef COORDINATE_H #define COORDINATE_H #include <iostream> using namespace std; class Coordinate { public: Coordinate(int x, int y); ~Coordinate(); private: int m_iX; int m_iY; }; #endif
Coordinate.cpp
#include "Coordinate.h" #include <iostream> using namespace std; Coordinate::Coordinate(int x, int y) { cout << "Coordinate()" << endl; m_iX = x; m_iY = y; } Coordinate::~Coordinate() { cout << "~Coordinate()" << endl; }
main.cpp:
#include <iostream> #include "Circle.h" #include "Rect.h" #include <stdlib.h> using namespace std; int main() { Shape *shape2 = new Rect(3.0, 4.0); Shape *shape1 = new Circle(3.0); shape1->calcArea(); shape2->calcArea(); //当基类不添加virtual时。打印两遍基类的。 delete shape1; shape1 = NULL; delete shape2; shape2 = NULL; system("pause"); return 0; }
给Shape的析构函数变成一个虚析构函数。推荐你们把子类的析构函数也加上virtual。由于子类也有可能成为其余类的父类。
virtual Shape(); // 报错: “inline”是构造函数的惟一合法存储类
virtual void test() { // 报错: error C2575: “test”: 只有成员函数和基能够是虚拟的 }
class Shape { public: Shape(); virtual static void test(); //error C2216: “virtual”不能和“static”一块儿使用 virtual ~Shape(); virtual double calcArea(); };
inline会失效:
virtual inline void test() { }
如何实现虚函数和虚析构函数: 虚函数的实现原理
涉及到函数指针,介绍一下函数指针
指针指向对象 - - - 对象指针
指针指向函数 - - - 函数指针
函数的本质就是一段二进制的代码,指针指向代码的开头,而后一行一行执行到函数结尾
指针指向代码内存的首地址,函数入口地址。
class Shape { public: virtual double calcArea() //虚函数 { return 0; } protected: int m_iEdge; }
//子类 class Circle: public Shape { public: Circle(double r); //Circle使用的也是Shape的虚函数 private: double m_dR; }
当咱们实例化一个Shape对象时,shape中除了数据成员m_iEdge,还有另一个成员(虚函数表指针;也是一个指针占有四个内存单元,存放地址,指向一个虚函数表)。该表会与Shape类的定义同时出现,在计算机中虚函数表占有必定内存空间。
假设虚函数表的起始地址: 0xCCFF。那么虚函数表指针
的值就是0xCCFF。父类的虚函数表只有一个,经过父类实例化出的多个对象,他们的虚函数表指针都只有一个: 0xCCFF。
父类的虚函数表中定义了一个函数指针calcArea_ptr
-> 指向calcArea()的入口地址。
Circle中并无定义虚函数,可是他却从父类中继承了虚函数。因此咱们在实例化Circle也会产生一个虚函数表指针,它是Circle本身的虚函数表。
在Circle中计算面积的方法首地址与父类的一致,这使得在Circle中访问父类的计算面积函数也能经过虚函数表指针找到本身的虚函数表。在本身的虚函数表中找到的计算面积函数指针也是指向父类的的计算面积函数的。
若是咱们在Circle中定义了计算面积的函数:
class Circle: public Shape { public: Circle(double r); virtual double calcArea(); private: double m_dR; }
Shape没有发生变化。
对于Circle来讲则有变化:
此时Circle中关于计算面积的函数指针指向本身的计算面积方法的首地址。
当Shape指针指向Circle对象,会经过Circle中的虚函数表指针,找到Circle本身的虚函数表,指向Circle本身的计算面积函数。
父类和子类出现同名函数,称之为函数隐藏。
上面这种状况称之为函数的覆盖。
特色:在父类中经过virtual修饰析构函数。经过父类指针指向子类对象,那么释放父类指针,能够同时释放子类对象。
理论前提:
执行完子类的析构函数就会执行父类的析构函数。
只要咱们能够实现执行子类的析构函数,就能够实现一次性释放两个。
Shape.h
class Shape { public: virtual double calcArea() //虚函数 { return 0; } virtual ~Shape(){} //虚析构函数 protected: int m_iEdge; }
Circle.h
class Circle: public Shape { public: Circle(double r); virtual double calcArea(); virtual ~Circle(); //不写计算机也会自行定义。 private: double m_dR; }
main.cpp:
int main() { Shape *shape = new Circle(10.0); delete shape; shape = NULL; return 0; }
若是咱们在父类中定义了虚析构函数,那么咱们在父类的虚函数表中就会有一个父类的析构函数的函数指针。
那么子类的虚函数表中也会有一个函数指针,指向子类的析构函数。
这个时候使用父类对象指向子类对象, 就会执行子类的析构函数,子类的执行完以后,系统会自动执行父类的析构函数。
虚析构函数与上面虚函数是同理可得的: 就是子类中有同名函数(同为析构函数), 那么这个虚函数表中指针将指向子类的函数。而由于子类析构函数执行会触发父类自动执行,因此实现了销毁父类指针,释放子类和父类的对象。
咱们须要知道的一些概念:
2-8-VirtualTablePointer
Shape.h
#ifndef SHAPE_H #define SHAPE_H #include <iostream> using namespace std; class Shape { public: Shape(); ~Shape(); double calcArea(); //virtual ~Shape(); //virtual double calcArea(); }; #endif
Shape.cpp
#include "Shape.h" Shape::Shape() { //cout << "Shape()" << endl; } Shape::~Shape() { //cout << "~Shape()" << endl; } double Shape::calcArea() { cout << "Shape - > calcArea()" << endl; return 0; }
Circle.h
#ifndef CIRCLE_H #define CIRCLE_H #include "Shape.h" class Circle:public Shape { public: Circle(int r); ~Circle(); protected: int m_iR; }; #endif
Circle.cpp
#include "Circle.h" Circle::Circle(int r) { m_iR = r; } Circle::~Circle() { }
main.cpp:
#include <iostream> #include "Circle.h" #include <stdlib.h> using namespace std; int main() { Shape shape; cout << sizeof(shape) << endl; // Shape对象没有任何的数据成员。理论应该为0. Circle circle(100); cout << sizeof(circle) << endl; // Circle 有一个int数据成员 理论为4. system("pause"); return 0; }
运行结果:
4是由于int占四个字节。如何解释1?
main.cpp:
#include <iostream> #include "Circle.h" #include <stdlib.h> using namespace std; int main() { Shape shape; int *p = (int *)&shape; // 强制类型转换 cout << p << endl; Circle circle(100); int *q = (int *)&circle; cout << q << endl; // Shape和Circle对象在内存中地址不一样。 cout << (unsigned int)(*q) << endl; // 打印出里面数据成员的值 system("pause"); return 0; }
运行结果:
2-9-VirtualFunction2
Shape.h (其中calcArea被加上了virtual关键字)
class Shape { public: Shape(); ~Shape(); // virtual ~Shape(); virtual double calcArea(); };
此时实例化Shape对象就应该有一个虚函数表指针了,对象大小从1会变成5。
main.cpp
int main() { Shape shape; cout << sizeof(shape) << endl; Circle circle(100); cout << sizeof(circle) << endl; //打印出里面存着的值 system("pause"); return 0; }
运行结果:
当咱们使Shape拥有一个虚函数时。
Circle由于继承自Shape也会拥有一个虚函数表,加上本身本来的数据成员,对象大小为8。
Shape.h改成以下:
class Shape { public: Shape(); double calcArea(); virtual ~Shape(); // 虚析构函数也有虚函数表。 };
运行结果:
虚函数表指针位于内存中的前四个单元。
int main() { Shape shape; int *p = (int *)&shape; cout << (unsigned int)(*p) << endl; // 虚函数表地址 Circle circle(100); int *q = (int *)&circle; cout << (unsigned int)(*q) << endl; // 打印出的仍是虚函数表地址 system("pause"); return 0; }
运行结果:
Circle中前四个内存单元是虚函数表指针地址。后四个是数据成员100。
int *q = (int *)&circle; q++; cout << (unsigned int)(*q) << endl;
输出结果为100.说明:
练习;
定义一个动物(animal)类,要求含有虚函数eat和move,并定义构造函数和虚析构函数
定义一个狗(Dog)类,要求共有继承动物类,定义构造函数和虚析构函数,并实现本身的eat和move函数
使用父类对象实例化子类,调用子类成员函数
#include <iostream> #include <stdlib.h> #include <string> using namespace std; /** * 定义动物类:Animal * 成员函数:eat()、move() */ class Animal { public: // 构造函数 Animal(){cout << "Animal" << endl;} // 析构函数 virtual ~Animal(){cout << "~Animal" << endl;} // 成员函数eat() virtual void eat(){cout << "Animal -- eat" << endl;} // 成员函数move() virtual void move(){cout << "Animal -- move" << endl;} }; /** * 定义狗类:Dog * 此类公有继承动物类 * 成员函数:父类中的成员函数 */ class Dog : public Animal { public: // 构造函数 Dog(){cout << "Dog" << endl;} // 析构函数 virtual ~Dog(){cout << "~Dog" << endl;} // 成员函数eat() virtual void eat(){cout << "Dog -- eat" << endl;} // 成员函数move() virtual void move(){cout << "Dog -- move" << endl;} }; int main(void) { // 经过父类对象实例化狗类 Animal *a = new Dog(); // 调用成员函数 a ->eat(); a ->move(); // 释放内存 delete a; a = NULL; return 0; }
运行结果:
例子:
class Shape { public: virtual double calcArea()//虚函数 {return 0;} virtual double calcPerimeter() = 0;//纯虚函数 }
纯虚函数:
当咱们定义了一个纯虚函数,他一样会在虚函数表中出现,如图calcPerimeter ptr就是纯虚函数的指针,他的值是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; };
若是Worker没有实现work。则不能够实例化work。
当Worker的子类dustman实现了work。就能够实例化dustman。
代码:
3-2-AbstractClass
Person.h
#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 //结束符
Person.cpp
#include "Person.h" Person::Person(string name) { m_strName = name; // 不实现纯虚函数 }
Worker.h
#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; };
Worker.cpp
#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; //}
Dustman.h
#ifndef DUSTMAN_H #define DUSTMAN_H #include "Worker.h" class Dustman :public Worker { public: Dustman(string name, int age); virtual void work(); }; #endif
Dustman.cpp
#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 "Worker.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; // 纯虚函数里面不用写任何代码 virtual void work() =0; // 纯虚函数
类包含了纯虚函数就会没法实例化,抽象函数咱们自己就不须要它实例化。
例如Circle继承了shape,Circle为了能够计算周长,定义了一个叫calcPerimeter的方法,所以把他父类Shape的纯虚函数calcPerimeter覆盖了,这样就能够成功实例化经过子类Circle来计算周长。
定义一个动物(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中约定的函数 }
3-6-InterfaceClass
Flyable.h
#ifndef FLYABLE_H #define FLYABLE_H class Flyable { public: virtual void takeoff() = 0;//起飞 virtual void land() = 0; //降落 }; #endif
Plane.h
#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
Plane.cpp
#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; }
FighterPlane.h
#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
FighterPlane.cpp
#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; }
main.cpp:
#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。
class FighterPlane :public Plane,public Flyable {}; class Plane //并把飞机中的纯虚函数 声明 & 定义 去掉
此时它就既能够当作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; }
同时继承两个,既能够当作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; }
输出:
Run-Time Type Identification
介绍知识点: typeid
dynamic_cast
例子:
class Flyable { public: virtual void takeoff() = 0;//起飞 virtual void land() = 0; //降落 }; class Bird:public Flyable { public: void foraging(){} // 觅食 virtual void takeoff(){} virtual void land(){} private: //... }; class Plane:public Flyable { public: void carry(){} // 运输 virtual void takeoff(){} virtual void land(){} };
使用时:
void doSomething(Flyable *obj) { obj ->takeoff(); //若是是bird,则觅食 //若是是plane,则运输 obj -> land(); }
若是对指针能进行判断,而后根据传入指针不一样调用不一样方法。实现小鸟觅食,plane运输。
void doSomething(Flyable *obj) { obj ->takeoff(); cout << typeid(*obj).name() <<endl; // 打印出对象类型 if(typeid(*obj) == typeid(Bird)) { Bird *bird = dynamic_cast<Bird *>(obj); // 尖括号里填写目标类型 //尖括号内是咱们想要转化成的类型。 bird -> foraging(); } if(typeid(*obj) == typeid(Plane)) { Plane *plane = dynamic_cast<Plane *>(obj); // 尖括号里填写目标类型 //尖括号内是咱们想要转化成的类型。 plane -> carry(); } obj -> land(); }
总结:
dynamic_cast注意事项:
typeid注意事项:
type_id
返回一个type_info
对象的引用name()
& 运算符重载等号,使得咱们能够直接用==
进行比对
4-2-RTTICode
Flyable.h
#ifndef FLYABLE_H #define FLYABLE_H class Flyable { public: virtual void takeoff() = 0;//起飞 virtual void land() = 0; //降落 }; #endif
Plane.h
#ifndef PLANE_H #define PLANE_H #include <string> #include "Flyable.h" using namespace std; class Plane :public Flyable { public: void carry(); virtual void takeoff(); virtual void land(); }; #endif
Plane.cpp
#include <iostream> #include "Plane.h" using namespace std; void Plane::carry() { cout << "Plane::carry()" << endl; } void Plane::takeoff() { cout << "Plane::takeoff()" << endl; } void Plane::land() { cout << "Plane::land()" << endl; }
Bird.h
#ifndef BIRD_H #define BIRD_H #include "Flyable.h" #include <string> using namespace std; class Bird :public Flyable { public: void foraging(); virtual void takeoff(); virtual void land(); }; #endif // !BIRD_H
Bird.cpp
#include <iostream> #include "Bird.h" using namespace std; void Bird::foraging() { cout << "Bird::foraging()" << endl; } void Bird::takeoff() { cout << " Bird::takeoff()" << endl; } void Bird::land() { cout << " Bird::land()" << endl; }
main.cpp:
#include <iostream> #include "Bird.h" #include "Plane.h" using namespace std; #include <stdlib.h> void doSomething(Flyable *obj) { cout << typeid(*obj).name() << endl; obj->takeoff(); if (typeid(*obj) == typeid(Bird)) { Bird *bird = dynamic_cast<Bird *>(obj); bird->foraging(); } if (typeid(*obj) == typeid(Plane)) { Plane *plane = dynamic_cast<Plane *>(obj); plane->carry(); } obj->land(); } int main() { Bird b; doSomething(&b); system("pause"); return 0; }
运行结果:
int main() { Plane p; doSomething(&p); system("pause"); return 0; }
int main() { int i =0; cout << typeid(i).name() << endl; }
输出为int,打印出数据类型。能够看到数据类型,基本数据类型的也能够查看到。
int main() { Flyable *p = new Bird(); cout << typeid(p).name() << endl; cout << typeid(*p).name() << endl; system("pause"); return 0; }
能够看到直接对p进行typeid,打印出的是指针的类型。
*p
则是p指向的对象的类型。
看dynamic_cast的使用限制:
将Flyable.h的两个纯虚函数改成普通的。
#ifndef FLYABLE_H #define FLYABLE_H class Flyable { public: void takeoff(){}//起飞 void land() {}//降落 }; #endif
经过大括号来实现。
将Bird.h中两个虚函数去掉。
#ifndef BIRD_H #define BIRD_H #include "Flyable.h" #include <string> using namespace std; class Bird :public Flyable { public: void foraging(); void takeoff(); void land(); }; #endif // !BIRD_H
Bird和flyable此时变成普通的继承。
int main() { Flyable *p = new Bird(); Bird *b = dynamic_cast<Bird *>p; // 会报错: “dynamic_cast”:“Flyable”不是多态类型 system("pause"); return 0; }
对于dynamic_cast的使用,要求转换类型仍是被转类型都要有虚函数。
int main() { Flyable p; Bird b = dynamic_cast<Bird>p; //“dynamic_cast”:“Flyable”不是多态类型 system("pause"); return 0;
只能应用于指针和引用的转换,且必须转换的两个类中含有虚函数。
定义一个可以移动(Movable)类,要求含有纯虚函数move
定义一个公交车(Bus)类,继承Movable类,并实现函数move,定义函数carry
定义一个坦克(Tank)类,继承Movable类,并实现函数move,定义函数shot。
定义函数doSomething(Movable *obj)
,根据s指向对象的类型调用相应的函数。
实例化公交车类和坦克类,将对象传入到doSomething函数中,调用相应函数
#include <iostream> #include <stdlib.h> #include <string> #include <typeinfo> using namespace std; /** * 定义移动类:Movable * 纯虚函数:move */ class Movable { public: virtual void move() = 0; }; /** * 定义公交车类:Bus * 公有继承移动类 * 特有方法carry */ class Bus : public Movable { public: virtual void move() { cout << "Bus -- move" << endl; } void carry() { cout << "Bus -- carry" << endl; } }; /** * 定义坦克类:Tank * 公有继承移动类 * 特有方法fire */ class Tank :public Movable { public: virtual void move() { cout << "Tank -- move" << endl; } void fire() { cout << "Tank -- fire" << endl; } }; /** * 定义函数doSomething含参数 * 使用dynamic_cast转换类型 */ void doSomething(Movable *obj) { obj->move(); if(typeid(*obj) == typeid(Bus)) { Bus *bus = dynamic_cast<Bus *>(obj); bus->carry(); } if(typeid(*obj) == typeid(Tank)) { Tank *tank = dynamic_cast<Tank *>(obj); tank->fire(); } } int main(void) { Bus b; Tank t; doSomething(&b); doSomething(&t); return 0; }
运行结果:
异常:程序运行期出现的错误。
异常处理:对有可能发生异常的地方作预见性的安排
如常见提示: 网线,内存不足。
异常处理的关键字:
try...catch...
尝试运行正常的逻辑,捕获以后进行处理。
throw抛出异常
思想:
主逻辑(try)与异常处理逻辑(catch)分离
三个函数f1,f2,f3。用f2调用f1,f3调用f2。
当f1出现异常会往上抛,若是f2能够处理就能够处理完成,
若是不能处理,会继续进行异常的传播直到f3捕获并处理。
若是没人处理就会抛给系统处理。
void fun1() { throw 1; // 抛出数字1 } int main(){ try { fun1(); // 若是正常运行,catch里的不会被执行 }catch(int) //throw的是1,因此用int类型捕获 { //..... } return 0; }
try{ fun1(); } catch(int) {} catch(double) {} catch(...) //括号里三个点,捕获全部的异常 {}
一个try能够有多个catch,不一样异常作不一样处理。
下面咱们来作捕获值:
char getChar(const string& aStr,const int aIndex) { if (aIndex > aStr.size()) { throw string("ivalid index!"); } return aStr[aIndex]; //根据字符串和下标拿到对应下标位置的字符 }
string str("hello world"); char ch; try{ ch = getChar(str,100); //这句抛异常,下句不会运行 cout << ch << endl; }catch(string& aval){ cout << aval << endl; }
常见的异常:
异常处理与多态的关系:
定义一个接口exception,多个子类来继承该类; 那么咱们能够经过父类对象捕获不一样子类对象的异常。
void fun1() { throw new SizeErr(); } void fun2() { throw new MemoryErr(); } try{ fun1(); }catch(Exception &e) { e.xxx(); } try{ fun2(); }catch(Exception &e) { e.xxx(); }
经过父类的引用,调用相应的子类处理函数。
5-2-ErrorDeal
Exception.h
#ifndef EXCEPTION_H #define EXCEPTION_H class Exception { public: virtual void printException(); virtual ~Exception() {} }; #endif
Exception.cpp
#include "Exception.h" #include <iostream> using namespace std; void Exception::printException() { cout << " Exception::printException()" << endl; }
IndexException.h
#ifndef INDEX_EXCEPTION_H #define INDEX_EXCEPTION_H #include "Exception.h" class IndexException:public Exception { public: virtual void printException(); }; #endif
IndexException.cpp
#include "IndexException.h" #include <iostream> using namespace std; void IndexException::printException() { cout << "提示:下标越界" << endl; }
main.cpp
#include <iostream> #include <stdlib.h> #include "IndexException.h" using namespace std; void test() { throw 0.1; } int main(void) { try { test(); } catch (double) { cout << "exception" << endl; } system("pause"); return 0; }
throw 1.0, double类型捕获。
catch (double &e) { cout << e << endl; }
能够打印出抛出来的异常值:如0.1
main.cpp
#include <iostream> #include <stdlib.h> #include "IndexException.h" using namespace std; void test() { throw IndexException(); } int main(void) { try { test(); } catch (IndexException &e) { e.printException(); } system("pause"); return 0; }
运行结果:
能够看到成功的捕获到了下标越界异常。
int main(void) { try { test(); } catch (Exception &e) { e.printException(); } system("pause"); return 0; }
依然打印出数组的提示,父类的引用可使用到子类的处理函数。
int main(void) { try { test(); } catch (...) { cout << "error" << endl; } system("pause"); return 0; }
经过(...)能够捕获到全部异常。
try...catch...
语法结构。函数division的两个参数为dividend(被除数)和divisor(除数)
要求用户输入除数和被除数,并做为参数传递给division函数
若是除数为0,则抛出异常,并被捕获,将异常的内容显示到屏幕上
#include <iostream> #include <string> #include <stdlib.h> using namespace std; /** * 定义函数division * 参数整型dividend、整型divisor */ int division(int dividend, int divisor) { if(0 == divisor) { // 抛出异常,字符串“除数不能为0” throw string("除数不能为0"); } else { return dividend / divisor; } } int main(void) { int d1 = 0; int d2 = 0; int r = 0; cin >> d1; cin >> d2; // 使用try...catch...捕获异常 try{ r = division(d1,d2); cout << r << endl; }catch(string &str){ cout << str <<endl; } return 0; }
运行结果: