栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > C/C++/C#

C++异常处理

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++异常处理

文章目录
  • 异常处理基础
  • 定义自己的异常类
  • 在函数中抛出异常


异常处理基础

首先用一个实例进行说明,假定牛奶在我们的文化中是一种重要食物,几乎永远用不完。但是,仍然希望程序处理“用完牛奶”这种不太可能发生的情况。首先假定牛奶用不完,然后用以下基本代码处理这种正常情况:

int donuts, milk;
double dpg;
cin>>donuts>>milk;
dpg = donuts/static_cast(milk);
cout< 

如果没有牛奶,上述代码会产生除以0的情况,这属于错误。为了处理“用完牛奶”这一特殊情况,可以测试这种异常。添加了测试的完整在下面给出(这里没有使用异常处理机制):

int donuts, milk;
double dpg;
cin>>donuts>>milk;
if(milk <= 0)
{
	cout<
	dpg = donuts/static_cast(milk);
	cout< 

下面我们使用C++异常处理机制来重写这个程序:

    int donuts, milk;
    double dpg;

    try
    {
        cin>>donuts>>milk;

        if(milk <= 0)
            throw NoMilk(donuts);

//        if(milk <= 0)
//            throw donuts;

        dpg = donuts/static_cast(milk);
        cout< 

如此简单的情况实际用不着异常,但它确实很好地演示了异常处理。虽然程序整体并没有变得更简单,但至少单词 try 和 catch 之间的代码变得更清晰了,这其实已暗示了异常的好处。

程序基本思路是:正常情况由单词 try 之后的代码处理,只有在异常情况下,才使用 catch 之后的代码。这样就成功隔离了正常情况和异常情况。

在 C++中处理异常时,要采取一个 try-throw-catch 三段式过程。try 块的语法如下:

try
{
	Some_Code // 一些代码
}

这个 try 块包含程序的基本算法,告诉计算机在一切正常的情况下进行什么处理。之所以叫 try 块,是因为不能百分之百保证一切正常,但你想 “试一下”。

现在,假如真的出现了异常,你就想抛出这个异常。“抛出异常”是指出“一件事情不对劲”的方式。添加 throw 之后,try 块的基本提纲就变成以下形式:

try
{
	Code_To_Try // 要尝试的代码
	Possibly_throw_An_Exception // 可能抛出异常
	More_Code // 更多的代码
}

有时将抛出的值直接成为异常(exception),所以执行 throw 语句就被称为 抛出异常(throwing an exception)。可以抛出任意类型的异常,上面的例子抛出 int 值。

执行 throw 语句时,该语句所在的 try 块会停止执行。如 try 块之后跟有一个合适的 catch 块,控制权就转交给那个 catch 块。throw 语句几乎肯定要嵌入一个分支语句(比如if语句)中。

通常将 catch 块称为异常处理程序。catch(int e) 中的标识符 e 看起来像是参数,行为也非常接近于参数。所以,将这个 e 称为 catch块参数(但要记住,这并不表示 catch 块是函数)。catch 块参数要做下面两件事情:

  • catch 块参数之前要附加类型名,指出 catch 块能捕捉什么类型的抛出值。
  • catch 块参数为捕捉的抛出值指定了名称,所以在 catch 块中写代码时,可对捕捉的抛出值进行处理。

如果上面例子中,要捕捉 int 类型的异常,却抛出 double 类型的异常,则不会被这个 catch 块捕捉到。如果没有抛出异常,catch 块会被忽略。

我们可以多个 throw 在不同情况下抛出不同的异常,并且可以在 try 块之后接多个 catch 块来捕捉不同的异常。需要注意的是,如果 catch 块中不需要参数,只需在参数列表中列出类型即可。

捕捉多个异常时,catch 块的顺序可能十分重要。try 块抛出异常值后,会依次尝试后续 catch 块,与所抛异常的类型匹配的第一个 catch 块得以执行。

下面是 catch 块的一个特例,它能捕捉抛出的任意类型的值。

catch(...)
{
	<这里随便放什么代码>
}

定义自己的异常类

throw 语句能抛出任意类型的一个值。程序员常做的事情就是定义一个类,它的对象专门容纳抛给 catch 块的信息。之所以要定义具体的异常类,更重要的原因是可用不同类型标识每种异常情况。需要注意的是异常类本质上还是类。

下面我们自定义异常类来改写上面的代码:

#include 

using namespace std;

class NoMilk
{
public:
    NoMilk(){}
    NoMilk(int howMany) : count(howMany) {}
    int getDonuts()
    {
        return count;
    }
private:
    int count;
};
int main()
{
    int donuts, milk;
    double dpg;

    try
    {
        cin>>donuts>>milk;
        if(milk <= 0)
            throw NoMilk(donuts);
        dpg = donuts/static_cast(milk);
        cout<
        cout< 

在函数中抛出异常

有时希望推迟处理异常。例如在某个函数中,如发现除以 0 就抛出异常。但可能不想在该函数中捕捉异常。抛出异常后,使用该函数的一些程序可能要直接终止,另一些程序则可能要做其他事情。因此,在函数内部捕捉异常,根本不知道拿异常要怎么办。在这种情况下,不要再函数定义中捕捉异常,而是让使用该函数的程序(或其他代码)将函数调用放到 try 块中,并在 try 块之后的 catch 块中捕捉异常。比如下面的例子:

#include 
#include 
using namespace std;

class DivideByZero
{};

double safeDivid(int top, int bottom) throw (DivideByZero);

int main()
{
    int numerator;
    int denominator;
    double quotient;
    cin>>numerator>>denominator;

    try
    {
        quotient = safeDivid(numerator, denominator);
    }
    catch(DivideByZero)
    {
        cout<<"Error: Division by zero!n"
            << "Program aborting.n";
        exit(0);
    }

    cout<double safeDivid(int top, int bottom) throw (DivideByZero);

异常规范要同时放在函数声明和函数定义中。如函数有多个函数声明,所有函数声明都必须有完全一致的异常规范。函数的异常规范有时也被称为 throw 列表。

如果函数定义可能抛出多个异常,就用逗号分隔不同异常类,如下所示:

void someFunction() throw (DivideByZero, OtherException);

需要注意的是如果没有任何异常规范,效果等同于在异常规范中自动列出所有可能的异常类型;换言之,抛出的任何异常都会得到正确处理。

但如果在函数中抛出异常,但异常规范没有列出该异常(也没有在函数内部捕捉),会直接导致程序终止,这种异常称为未处理异常。几种情况总结如下:

void someFunction() throw(DivideByZero, OtherException);
// DivideByZero 或 OtherException类型的异常会被正常处理
// 至于其他任何异常,如抛出后未在函数主体中捕捉,就终止程序

void someFunction() throw();
// 空异常列表:如抛出任何未在函数主体中捕捉的异常,就终止程序

void someFunctiom();
// 正常处理所有类型的所有异常

在派生类中 重定义(redefine) 或 重写(override) 函数定义时,它应该具有和基类一样的异常规范,或至少应该在新的异常规范中给出基类异常规范的一个子集。换言之,重定义或重写函数定义时,不可在异常规范中添加新异常。但如果愿意,可删减基类原有的异常。之所以有这个要求,是因为在能使用基类对象的任何地方,都能使用派生类对象。因此,重定义和重写的函数必须兼容未基类对象编写的任何代码。

我们还可以嵌套 try-catch 块,如果将 try 块及其后续 catch 块放到更大的 try 块中,而且内层 try 块抛出但未捕捉一个异常,则异常会被抛到外层 try 块中,以便在那里处理,甚至在那里捕捉。

在 catch 中抛出异常是合法的。极少数情况下,可能希望捕捉一个异常,再根据当前实际情况抛出同一个异常或抛出一个不同的异常,以便在异常处理链中进一步处理。

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1038863.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号