面向對象三大特徵:封裝,繼承,多態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; }
運行結果: