遊戲製作前的基石C++學習筆記(一)Exception Handing

前言

這個連載博客的內容呢,主要是關於筆者在C++學習時的一些心得體會和總結,希望能和大家分享。同時C++也是遊戲製作時的“普通話”一樣的語言, 這爲後續相關介紹計算機圖形學和3D遊戲開發無疑提供了一個很好的編程理論基礎和鋪墊。

Exception Handing(異常處理)

一.引入異常處理的原因

這裏先引入一個概念程序的健壯性(Robustness)

計算機科學中,健壯性(英語:Robustness)是指一個計算機系統在執行過程中處理錯誤,以及算法在遭遇輸入、運算等異常時繼續正常運行的能力。 諸如模糊測試之類的形式化方法中,必須通過製造錯誤的或不可預期的輸入來驗證程序的健壯性。 —摘自維基百科

double Handing_score(double score) {
    if (score < 0 || score > 100) {
        cout << "error" << endl; // 即處理異常的語句
    } else {
        // 正常語句塊
    }
}

以上述代碼爲例,當你輸入的分數大於100或小於0時明顯是不符合邏輯的和實際的,而異常正是爲了提醒用戶這方面的代碼塊。通俗的來說異常處理就是處理一些不合理和錯誤情況的過程, 這使得程序在實際運行中更加人性化,比如你在遊戲中試圖走出地圖之外遊戲會有提示,以及其他一些不合理的情況(注:前提是可預料的錯誤!)。


二.實現異常

1’基本的思想:將異常處理與異常檢測分開,異常檢測部分當檢測到異常時就拋出一個異常對象交付給異常處理代碼,通過該異常對象, 獨立開發的異常檢測部分和異常處理部分能夠就程序執行期間所出現的異常情況進行通信。(重要的是異常是一個類的實例對象切記切記

2’基本實現模式

拋出異常的程序段

...                            
throw 表達式
...

捕獲並處理異常的程序段

try
    複合語句(保護段)
catch(異常類型聲明) // 注意異常類型的聲明可以使單個類型名,單個對象聲明或者...(英文省略號(表示與任意的異常類型均可匹配)通常這個放在最後作爲保險處理)
    複合語句
catch (異常類型聲明)
    複合語句
...

3’異常處理機制

a.若有異常則通過throw操作創建一個異常對象並拋出。
b.將有可能拋出異常的程序段嵌在try塊中,控制通過正常的順序執行到到達try塊,然後執行try子塊內的保護段。
c.如果在保護段執行期間並沒有引發異常,那麼跟在try子塊後的catch子句就不執行。程序繼續執行緊跟在try塊中最後一個catch子句後面的語句。
d.catch子句按其在try塊後出現的順序別檢查,類型匹配的catch子句將捕獲並處理異常(或繼續拋出異常)。
e.如果找不到匹配的處理代碼,則自動調用標準庫函數terminate, 其默認功能是調用abort()函數終止程序。

4’ 異常類型的”匹配“機制

a.被拋出異常的類型與catch子句異常聲明中的類型相同。
b.被拋出異常的類型與catch子句異常聲明中的類型的子類型相同。(即公有派生類)

5’異常說明

形式

int f1() throw(logic_error);

throw(異常類型列表) // 放在函數原型中的形參表的後面(即上述的f1()後面)
a.異常類型列表爲空,表示該函數不拋出任何異常。
b.不帶異常說明的函數可以拋出任意類型的異常
c.const成員函數的異常說明放在保留字const之後。
d.基類虛函數的異常列表是派生類中對應虛函數的異常列表的超集。

6’ 異常類的虛函數


下面的代碼是通過調用異常類的虛函數show()在catch句塊中利用基類的指針或引用來實現多態從而使程序簡化

    class offset {
        public:
            offset(int Size) : size(Size) {}
            ~offset() {}
            virtual int Get() { return size;}
            virtual void show() {
                cout << "拋出offset異常" << endl;
                cout << "下標值" << size << "出錯" << endl;
            }
        protected:
            int size;
    }; 
    class Big: public offset {
        public:
            Big(int Size) : offset(Size) {}
            virtual void show() {
                cout << "拋出Big異常" << endl;
                cout << "下標值" << offset::size << "出錯" << endl;
            }
    };
    class Nav : public offset {
        public:
            Nav(int Size) : offset(Size) {}
            virtual void show() {
                cout << "拋出Nav異常" << endl;
                cout << "下標值" << offset::size << "出錯" << endl;
            }
    };
    ...
    catch (people::offset & off) {
        off.show();
    }

7’ 異常類與模板的關係

當在類模板中使用異常時,有下面兩種方法可以建立異常類:
a.爲每個類模板的具體類型或者說爲每個類模板的具體實例建立一個異常。
b.在類模板外面聲明異常類。


三.異常代碼實例

這個例子包含了上述所說的

#include<iostream>
using namespace std;
const int num = 5;
class wrong {}; // 異常類wrong它是在模板類people的外部聲明的
template <typename T>
class people {
  private:
    int *p;
    int size;
  public:
    people(int size = num);
    ~people() { delete[] p; }
    T &operator[] (int off);
    const T& operator[] (int off) const;
    int GetSize() const { return size; }
    class offset {
        public:
            offset(int Size) : size(Size) {}
            ~offset() {}
            virtual int Get() { return size;}
            virtual void show() {
                cout << "拋出offset異常" << endl;
                cout << "下標值" << size << "出錯" << endl;
            }
        protected:
            int size;
    };        // 異常類offset是在模板類people內部聲明的
    class Big: public offset {
        public:
            Big(int Size) : offset(Size) {}
            virtual void show() {
                cout << "拋出Big異常" << endl;
                cout << "下標值" << offset::size << "出錯" << endl;
            }
    };     // 異常類Big是異常類offset的子類是在模板類people內部聲明的
    class Nav : public offset {
        public:
            Nav(int Size) : offset(Size) {}
            virtual void show() {
                cout << "拋出Nav異常" << endl;
                cout << "下標值" << offset::size << "出錯" << endl;
            }
    };     // 異常類Nav是異常類offset的另一個子類是在模板類people內部聲明的
};
template<typename T>
people<T>::people(int Size) : size(Size) {
    cout << "調用people的構造函數" << endl;
    if (Size > 10000) {
        throw Big(Size);
    }
    if (Size < 1) {
        throw Nav(Size);
    }
    p = new T[size];
    for (int i = 0; i < size; i++) {
        p[i] = 0;
    }
}
template<typename T>
T& people<T>::operator[](int off) {
    if (off >= 0 && off < GetSize()) {
        return p[off];
    }
    throw wrong();
    return p[0];
}
template<typename T>
const T& people<T>::operator[](int off) const {
    if (off >= 0 && off < GetSize()) {
        return p[off];
    }
    throw wrong();
    return p[0];
}
int main() {
    try {
        people<int> student(100000);
        for (int i = 9999; i < 10001; i++) {
            student[i] = i;
            cout << "one[" << i << "]賦值完畢" << endl;
        }
    }
    catch (wrong) {
        cout << "超過數組長度, 不能繼續執行賦值操作" << endl;
    }
    catch (people<int>::offset & off) {
        off.show(); // 異常類中的虛函數可以通過父類的指針或引用實現多態
    }
    catch (...) {
        cout << "程序出現異常" << endl; // 捕獲其他的異常
    }
    return 0;
}

下面是程序運行結果
程序運行結果


四.標準庫異常類

這裏寫圖片描述


五.auto_ptr(智能指針)

1.在程序中內存泄露是很令人頭疼的一個問題,當我們在C++中new 一個東西時必須要有一個delete運算符與之匹配, 通常情況下我們可能會謹記這個規則,然而當我們引入異常後,當異常被拋出後,程序的正常流程將會被改變此時便會引起不易察覺的內存泄露的錯誤。

實例:

#include<iostream>
using namespace std;
class Check {
    public:
    Check() {cout << "對象創建成功" << endl;}
    ~Check() {cout << "對象銷燬成功" << endl;}
};
class Exception {};
bool quit = false;
void Out() {
    if (quit == true)
        throw Exception();
}
void func() {
    Check *p = new Check;
    quit = true; // 刪除該語句程序便不會拋出異常,進而不會發生內存泄露
    Out();
    delete p;
}
int main() {
    try {
        func();   
    }
    catch(Exception) {
            cout << "捕獲到Exception異常" << endl;
    }
    return 0;
}

運行效果
上述代碼中看起來我們new一個指針p和delete指針p是對應的,然而在程序執行過程中因爲執行了Out函數拋出了一個異常,使得程序實際運行中並沒有執行delete語句。

2.智能指針即auto_ptr類:聲明如下

template<typename T>
class auto_ptr {
    public:
        typedef T element_type;
        explicit auto_ptr(T *p = 0) throw();
        auto_ptr(const auto_ptr<T>& rhs) throw();
        auto_ptr<T>& operator=(auto_ptr<T>& rhs) throw();
        ~auto_ptr();
        T& operator* () const throw();
        T* operator->() const throw();
        T* get() const throw();
        T* release() const throw();
    private:
        bool _Owns;
        T *_Ptr; 
}

定義如下auto_ptr<string> p(new string)

該行語句定義了一個auto_ptr類的對象p, 尖括號中的string用來初始化它的指針成員(T *_Ptr)的類型, 小括號中的“new string”調用構造函數explicit auto_ptr(T *p = 0) throw();小括號中的T是模板參數,前面已經用string具體化了T,所以auto_ptr類對象p的指針成員是string類型,一個指向string型字符串的指針,指針名是_Ptr, 如果沒有初始化的話就是defult value(默認值)NULL。_Ptr保存的是new string操作的返回值即在堆中創建的string型字符串的地址。這樣auto_ptr類的對象p就通過構造函數使它的指針成員_Ptr指向了一塊堆中空間。
單參數的構造函數前面的explicit是用來防止隱式類型轉換的,調用這種構造函數必須加小括號,如
auto_ptr<string> p (new string)
而不能auto_ptr<string> p = new string 這樣禁止了亂構造智能指針
由上述介紹定義一個智能指針就等於創建了一個auto_ptr類對象,auto_ptr類對象與普通指針的區別:當auto_ptr類對象因生命期結束而被撤銷時,該對象的析構函數會對該對象中所保存的對象指針進行delete操作,從而刪除auto_ptr類對象所指向的目標對象,避免被我們遺忘。
用智能指針對上述異常代碼進行改寫:

#include<iostream>
#include <memory>
using namespace std;
class Check {
public:
    Check() { cout << "對象創建成功" << endl; }
    ~Check() { cout << "對象銷燬成功" << endl; }
};
class Exception {};
bool quit = false;
void Out() {
    if (quit == true)
        throw Exception();
}
void func() {
    auto_ptr<Check> p(new Check); // 替換掉Check *p = new Check;
    quit = true;
    Out();
    //不用自己寫delete p;
}
int main() {
    try {
        func();
    }
    catch (Exception) {
        cout << "捕獲到Exception異常" << endl;
    }
    return 0;
}

程序運行結果
這裏寫圖片描述

注意事項:

a.智能指針在頭文件中不要忘記include
b.智能指針不能對數組使用,因爲它的delelte並不是delete [],它只能刪除數組的第一個元素
c.智能指針不能作爲STL容器中的元素這點記住就好(C++標準明確禁止可能會出現不可預料的錯誤)


到這裏算是大致講完了異常處理,希望大家能有所收穫,這是第一次寫博客難免會有些問題和毛病,所以博主要再接再厲爲大家分享更多的高質量學習心得~
座右銘:Become a MVP(Most Value Programmer)!~
轉載請註明來自:http://blog.csdn.net/a1054513777如若轉載,請保留原文地址。謝謝合作。

發佈了25 篇原創文章 · 獲贊 15 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章