游戏制作前的基石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.youkuaiyun.com/a1054513777如若转载,请保留原文地址。谢谢合作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值