9-C++遠征之多態篇-學習筆記

C++遠征之多態篇

面向對象三大特徵:封裝,繼承,多態ios

多態: 發出一條命令時,不一樣的對象接收到一樣的命令作出的動做不一樣c++

多態篇會學習到的目錄:數組

  1. 普通虛函數 & 虛析構函數
  2. 純虛函數:抽象類 & 接口類
  3. RTTI (運行時類型識別)
  4. 異常處理
  5. 概念區分: 隱藏 & 覆蓋 | 早綁定與晚綁定
  6. 虛函數表(核心部分)

多態的內容不少,概念也聽起來有點變態ide

多態變態

不過這也是最精彩的部分了。函數

c++ 虛函數

什麼是多態?學習

多態是指相同對象收到不一樣消息或不一樣對象收到相同消息時產生不一樣的動做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;
}

當咱們傳入一個參數,兩個參數會調用兩個不一樣的同名函數。

計算機在編譯階段就會自動根據參數使用不一樣的參數來肯定使用哪一個函數。

這裏程序在運行以前也就是編譯階段,就決定了運行哪一個函數。很早的就決定運行哪一個了,這種狀況就叫作早綁定,或靜態多態。

動態多態(晚綁定): 不一樣對象收到相同消息

mark

圓形和矩形都有本身的計算面積的方法,兩種方法確定是不一樣的。

這就是對不一樣的對象下達相同的指令,卻作着不一樣的操做。

動態多態前提:必須以封裝(數據封裝到類中)與繼承(繼承關係)爲基礎。

動態多態,起碼有兩個類,一個是子類,一個是父類。使用三個類時表現的更爲明顯。

代碼例子:

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關鍵字使得成員函數變成虛函數。

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: 使用指向子類對象的父類指針是否能直接調用到子類方法。

mark

能夠看到問題1,只銷毀了父類對象,子類對象沒有被一併銷燬。問題2,父類指針調用的是父類中的方法,子類方法並無被調用到。

在給shape的clacArea函數加上virtual後,父類指針可直接調用到子類的成員函數。

Shape.h中的析構函數,與子類重名的函數都加上virtual

class Shape
{
public:
    Shape();
    virtual ~Shape();
    virtual double calcArea();
};

mark

推薦爲子類也加上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。父類指針能夠一塊兒銷燬掉子類的對象。

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;
}

mark

給Shape的析構函數變成一個虛析構函數。推薦你們把子類的析構函數也加上virtual。由於子類也有可能成爲其餘類的父類。

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() {
    }

練習題

  • 虛函數特性能夠被繼承,當子類中定義的函數與父類中虛函數的聲明相同時,該函數也是虛函數。
  • 虛函數使用virtual關鍵字定義,但使用virtual關鍵字時,並不是所有是虛函數(能夠是虛繼承)
  • 虛析構函數是爲了不使用父類指針釋放子類對象時形成內存泄露。

虛函數與虛析構函數原理

如何實現虛函數和虛析構函數: 虛函數的實現原理

涉及到函數指針,介紹一下函數指針

指針指向對象 - - - 對象指針
指針指向函數 - - - 函數指針

函數的本質就是一段二進制的代碼,指針指向代碼的開頭,而後一行一行執行到函數結尾

mark

指針指向代碼內存的首地址,函數入口地址。

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;
}

mark

當咱們實例化一個Shape對象時,shape中除了數據成員m_iEdge,還有另一個成員(虛函數表指針;也是一個指針佔有四個內存單元,存放地址,指向一個虛函數表)。該表會與Shape類的定義同時出現,在計算機中虛函數表佔有必定內存空間。

假設虛函數表的起始地址: 0xCCFF。那麼虛函數表指針的值就是0xCCFF。父類的虛函數表只有一個,經過父類實例化出的多個對象,他們的虛函數表指針都只有一個: 0xCCFF。

父類的虛函數表中定義了一個函數指針calcArea_ptr-> 指向calcArea()的入口地址。

mark

Circle中並無定義虛函數,可是他卻從父類中繼承了虛函數。因此咱們在實例化Circle也會產生一個虛函數表指針,它是Circle本身的虛函數表。

在Circle中計算面積的方法首地址與父類的一致,這使得在Circle中訪問父類的計算面積函數也能經過虛函數表指針找到本身的虛函數表。在本身的虛函數表中找到的計算面積函數指針也是指向父類的的計算面積函數的。

若是咱們在Circle中定義了計算面積的函數:

class Circle: public Shape
{
public:
    Circle(double r);
    virtual double calcArea();
private:
    double m_dR;
}

Shape沒有發生變化。

mark

對於Circle來講則有變化:

mark

此時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;
}

mark

若是咱們在父類中定義了虛析構函數,那麼咱們在父類的虛函數表中就會有一個父類的析構函數的函數指針。

mark

那麼子類的虛函數表中也會有一個函數指針,指向子類的析構函數。

這個時候使用父類對象指向子類對象, 就會執行子類的析構函數,子類的執行完以後,系統會自動執行父類的析構函數。

虛析構函數與上面虛函數是同理可得的: 就是子類中有同名函數(同爲析構函數), 那麼這個虛函數表中指針將指向子類的函數。而由於子類析構函數執行會觸發父類自動執行,因此實現了銷燬父類指針,釋放子類和父類的對象。

  • 好比此時咱們刪去子類的析構函數。那麼將只執行父類的析構函數。

證實虛函數表指針的存在(代碼示例)

虛函數表指針要求

咱們須要知道的一些概念:

  • 對象的大小: 在類實例化的對象當中數據成員所佔據的大小,不包括成員函數。
  • Shape沒有數據成員(理論上不佔內存); Circle有一個int型的數據成員,應該佔四個。
  • 對象的地址:經過類實例化的對象,它佔據的內存單元的首地址
  • 對象成員的地址:當用一個類實例化一個對象以後,這個對象中可能與一個或多個數據成員,每個數據成員所佔據的地址就是這個對象的成員地址;對象的數據成員因爲數據類型不一樣,佔據的內存大小也不一樣,地址也是不一樣的。
  • 虛函數表指針:在具備虛函數的狀況下實例化對象時,這個對象的第一個內存存儲的是一個指針,即虛函數表的指針,佔四個內存單元,所以咱們能夠經過計算對象的大小來證實指針的存在。

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;
}

運行結果:

mark

4是由於int佔四個字節。如何解釋1?

  • 在沒有一個數據成員的狀況下,C++會給該對象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;
}

運行結果:

mark

虛函數示例2

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;
}

運行結果:

mark

當咱們使Shape擁有一個虛函數時。

  • 指向Shape的指針會指向一個虛擬函數表。由於是個指針,因此佔四個內存空間。

Circle由於繼承自Shape也會擁有一個虛函數表,加上本身本來的數據成員,對象大小爲8。

Shape.h改成以下:

class Shape
{
public:
    Shape();
    double calcArea();
    virtual ~Shape(); // 虛析構函數也有虛函數表。
};

運行結果:

mark

虛函數表指針位於內存中的前四個單元。

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;
}

運行結果:

mark

Circle中前四個內存單元是虛函數表指針地址。後四個是數據成員100。

int *q = (int *)&circle;
q++;
cout << (unsigned int)(*q) << endl;

mark

輸出結果爲100.說明:

  • Circle指針8位。前四位是指針指向虛地址表首地址。後四位是裏面存着的內容。
  • 父類和子類擁有不一樣的屬於本身的虛函數表。

練習;

  • 在C++中多態的實現是經過虛函數表實現的
  • 每一個類只有一份虛函數表,全部該類的對象共用同一張虛函數表
  • 兩張虛函數表中的函數指針可能指向同一個函數。(當子類沒有父類的同名函數)

鞏固練習:

定義一個動物(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;
}

運行結果:

mark

  • 由於父類使用虛析構函數。因此釋放內存。
  • 由於子類中有父類的同名函數,因此在子類的虛函數表中本來指向父類方法的指針被覆蓋爲子類本身的函數指針。
  • 實現了父類對象調用子類方法。

純虛函數 & 抽象類

例子:

class Shape
{
public:
    virtual double calcArea()//虛函數
    {return 0;}
    virtual double calcPerimeter() = 0;//純虛函數
}

純虛函數:

  • 沒有函數體
  • 直接等於0

普通虛函數有值,純虛函數直接爲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;
};
  • 抽象類的子類也有多是抽象類。抽象類的子類只有把抽象類當中的全部純虛函數都作了實現,子類才能夠實例化對象。
  • 上面代碼中work只把子類的兩個實現了一個。只有dustman
    才能實例化對象。

抽象類代碼示例

抽象類代碼

若是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來計算周長。

練習

  • 只有函數聲明沒有函數定義,直接等於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;
}

運行結果:

mark

由於狗實現了動物類的全部純虛構函數,因此它能夠被實例化。由於父類的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;
}

mark

看出飛機能夠做爲參數傳入flymatch;這限制了傳入參數的對象類型,能夠在函數體中調用接口類的方法。

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

    flyMatch(&p1,&p2);

    system("pause");
    return 0;
}

mark

能夠看到繼承飛機的戰鬥機也是能夠參加飛行比賽的,由於它也有飛行能力。

改變代碼以下:

讓戰鬥機繼承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;
}

mark

同時繼承兩個,既能夠當作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;
}

輸出:

mark

RTTI (運行時類型識別)

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注意事項:

  • 只能應用於指針和引用的轉換
  • 要轉換的類型中必須包含虛函數
  • 轉換成功返回子類的地址,失敗返回NULL

typeid注意事項:

  • type_id返回一個type_info對象的引用
  • 若是想經過基類的指針得到派生類的數據類型,基類必須帶有虛函數
  • 只能獲取對象的實際類型。(不能判斷當前指針是基類仍是子類)

mark

name() & 運算符重載等號,使得咱們能夠直接用==進行比對

RTTI代碼示例

要求

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;
}

運行結果:

mark

  • 傳入的是bird,因此執行了bird分支下的覓食。
  • 當傳入是plane時,則會執行carry。
int main()
{
    Plane p;
    doSomething(&p);

    system("pause");
    return 0;
}

mark

代碼說明typeid, dynamic_cast的注意事項

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;
}

mark

能夠看到直接對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;

只能應用於指針和引用的轉換,且必須轉換的兩個類中含有虛函數。

練習題

  • 繼承關係不是RTTI的充分條件,只是必要條件,因此存在繼承關係的類不必定能夠用RTTI技術
  • RTTI的含義是運行時類型識別
  • RTTI技術能夠經過父類指針識別其所指向對象的真實數據類型
  • 運行時類型別必須創建在虛函數的基礎上,不然無需RTTI技術

鞏固練習

定義一個可以移動(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;
}

運行結果:

mark

異常處理

異常:程序運行期出現的錯誤。

異常處理:對有可能發生異常的地方作預見性的安排

如常見提示: 網線,內存不足。

異常處理的關鍵字:

  • try...catch...

嘗試運行正常的邏輯,捕獲以後進行處理。

throw拋出異常

思想:

主邏輯(try)與異常處理邏輯(catch)分離

mark

三個函數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;
}

常見的異常:

  • 數組下標越界
  • 除數爲0
  • 內存不足

異常處理與多態的關係:

mark

定義一個接口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類型捕獲。

mark

catch (double &e)
    {
        cout << e << endl;
    }

mark

能夠打印出拋出來的異常值:如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;
}

運行結果:

mark

能夠看到成功的捕獲到了下標越界異常。

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;
}

經過(...)能夠捕獲到全部異常。

練習題

  • 在C++中異常處理一般使用try...catch...語法結構。
  • 一個try語句能夠對應一個或多個catch語句,但不能沒有catch語句
  • C++中使用throw拋出異常,經過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;
}

運行結果:

mark

相關文章
相關標籤/搜索