C++初学者指南 第十二篇(1)

转载请标明出处:http://blog.csdn.net/zhangxingping
12篇 异常,模板和其它的高级特性

到目前为止我们已经学习了很多关于C++的知识。在本书的最后一章中,我们将学习到几个重要的C++高级特性。它们是:异常处理,模板,动态分配和命名空间。本章还将学习运行时类型标识和强制转换运算符。请记住,C++是一个强大的、成熟的、专业的编程语言。在作为初级教材的本书中我们不可能把所有的高级特性,特殊的技术和编程技巧都一一涵盖。但是,学习完本章后,我们就已经掌握了C++语言的核心,并能够开始编写真实的程序了。

必备技能12.1:异常处理

异常就是运行时出现的错误。使用C++中提供的异常处理机制,我们可以以一种结构化的,可控的方式来处理运行时的错误。当我们使用异常处理机制时,在出现异常时候程序会自动唤起异常处理的子程序。这样的最大好处就是能把一切之前我们必须手动完成的错误处理程序自动化。

异常处理的基础

C++中的异常处理是基于三个关键字的:try,catchthrow。更通俗地来说,我们把需要进行监视的代码块放置在try的代码块中,以便监视其异常。如果在try的代码块中出现了异常,异常就会被抛出。我们使用catch子句来捕获抛出的异常,并对其进行处理。下面我们将对这些进行详细的说明。

我们要监视的以便处理其异常的代码必须放置在try代码块中。try代码块中的代码所抛出的异常会被其后的catch子句所捕获。trycatch的常用形式如下:

try

{

   // try 的代码块

}

catch(类型参数arg)

{

    //catch 代码块

}

catch(类型参数arg)

{

    //catch 代码块

}

//...

catch(类型参数arg)

{

    //catch 代码块

}

try中的代码块就是我们要监视的,以便处理其异常的代码段。这段代码可以很短,比如可以是一个函数中的几条语句;也可以是整个main()函数(这样一来就是整个程序都被监视了。)。

当程序抛出异常的时候,对应的catch子句就会捕获这个异常,然后处理这个异常。一个try子句可以有多个catch子句和其相匹配。try中语句抛出异常的类型决定了那个catch子句会被使用,其中的语句会被执行。当捕获一个异常的时候参数arg就会接收到异常的值。任何类型的数据都可以被接收,这也包括我们自己创建的类。

throw语句的通用形式如下:

throw exception;

该语句就生成一个execption指定的异常。如果要捕获这个异常,那么trow语句就必须是在一个try语句的代码块中,或者是在try代码块中调用的函数中。

如果抛出了一个异常而没有对应的catch子句,程序就会异常终止。也就是说,程序会以一种我们不能控制的方式而结束。因此,我们应该对抛出的所有异常都进行捕获。

下面的简单程序展示了C++中的异常处理机制:

//一个简单的异常处理程序
#include <iostream>
using namespace std;
 
int main()
{
    cout << "start\n";
 
    try
    {
        //try代码块的开始
        cout << "Inside try block\n";
        throw 99;
        cout << "This will not execute";
    }
    catch ( int i )//捕获异常
    {
        cout << "Caught an exception -- value is : ";
        cout << i << "\n";
    }
 
    cout << "end";
 
    return 0;
}


上面程序的输出如下:

start

Inside try block

Caught an exception -- value is : 99

end

仔细研究上面的代码,我们可以看到其中的try子句中有三条语句,catch子句捕获了一个整型数的异常。try代码块中的三条语句只执行了两条:第一个cout语句和throw语句。一旦有异常抛出,程序的执行就会转向catch子句,try代码块就结束了。也就是说,catch子句不是被调用的,而是程序的执行转跳到catch子句的。因此thow语句后面的catch是永远不会被执行的。

一般来说,catch子句中的语句都是用来对错误进行补救的。如果异常是可以被修正的,程序会继续执行catch子句后面的语句。否则,程序应该以一种可控的方式结束。

正如前面我们讲到的那样,抛出异常的类型必须和catch子句中异常的类型是相匹配的。例如,在前面的例子中,如果我们把catch子句中异常的类型修改为double,那么这个异常就不会被捕获,程序就会异常终止。如下所示:

//一个简单的异常处理程序
#include <iostream>
using namespace std;
 
int main()
{
    cout << "start\n";
 
    try
    {
        //try代码块的开始
        cout << "Inside try block\n";
        throw 99;
        cout << "This will not execute";
    }
    catch ( double i )//捕获异常
    {
        cout << "Caught an exception -- value is : ";
        cout << i << "\n";
    }
 
    cout << "end";
 
    return 0;
}


由于程序抛出的整型数异常不会被catch子句捕获,程序的输出将如下:

start

Inside try block

 

This application has requested the Runtime to terminate it in an unusual way.

Please contact the application's support team for more information.

 

try代码块中调用的函数抛出的异常也可以由try来处理。例如,下面的程序是正确的

#include <iostream>
using namespace std;
 
 
void Xtest(int test)
{
    cout << "Inside Xtest, test is: " << test << "\n";
    if ( test ) throw test;
}
 
int main()
{
    cout << "start\n";
 
    try //监视的代码段
    {
        cout << "Inside try block\n";
        Xtest(0);
        Xtest(1);
        Xtest(2);
    }
    catch( int i ) //捕获异常
    {
        cout << "Caught an exception -- value is: ";
        cout << i << "\n";
    }
 
    cout << "end";
 
return 0;
}


上面程序的输出如下:

start

Inside try block

Inside Xtest, test is: 0

Inside Xtest, test is: 1

Caught an exception -- value is: 1

end

正如程序的输出那样,在函数Xtest()中抛出的异常在函数main()中被捕获了。try子句可以被放置在一个函数中。当每次调用这个函数的时候,函数中用来处理异常的代码就会被重置。如下面的程序那样

//try代码块可以局部化到一个函数中
#include <iostream>
using namespace std;
 
//当每次进入到函数的时候,其中的try和catch都会被重置
void Xhandler( int test)
{
    try
    {
        if (test) throw test;
    }
    catch ( int i )
    {
        cout << "Caught One! Ex.#: " << i << "\n";
    }
}
 
int main()
{
    cout << "start\n";
 
    Xhandler(1);
    Xhandler(2);
    Xhandler(0);
    Xhandler(3);
 
    cout << "end";
 
    return 0;    
}


程序的输出如下:

start

Caught One! Ex.#: 1

Caught One! Ex.#: 2

Caught One! Ex.#: 3

end

在这个实例程序中,共计抛出了三个异常。在每次抛出异常之后,函数就都退出了。当再次调用这个函数的时候,异常处理就被重置了。try代码块也是在每次进入的时候会被重置。因此,如果try代码块是作为循环的一部分,那么每次循环的时候try代码块就会被重置。

练习:

1. C++语言中,什么是异常?

2. 异常处理时基于那三个关键字的?

3. 异常的捕获是基于它们的类型。对吗?

使用多个catch语句

正如我们前面讲过的那样,我们可以把多个catch语句和一个try关联起来。实际上,这样做是很普遍的。然而,每个catch子句捕获的异常类型必须是不相同的。例如,下面的程序捕获的就是整型数和字符型指针的异常。

//使用多个catch语句
#include <iostream>
using namespace std;
 
//不同类型的异常都可以被捕获
void Xhandler( int test )
{
    try
    {
        if ( test ) throw test; //抛出一个int类型的异常
        else throw "Value is zero"; //抛出一个字符指针异常
    }
    catch(int i)
    {
        cout << "Caught One Ex.#: " << i << '\n';
    }
    catch(char *str)
    {
        cout << "Caught a string: ";
        cout << str << "\n";
    }
}
 
int main()
{
    cout << "start\n";
 
    Xhandler(1);
    Xhandler(2);
    Xhandler(0);
    Xhandler(3);
 
    cout << "end";
 
    return 0;
}


通常来讲,catch表达式是根据它们在程序中出现的先后位置来检查的。只有异常类型匹配的那一个catch子句的代码块才会被执行。其它的catch子句都会被忽略。

捕获基类的异常

    关于多个catch语句有一个和基类相关的重要问题:捕获基类异常的catch子句是能够捕获到子类的异常的。因此,当我们需要同时捕获基类和子类的异常的时候,我们需要把捕获子类的catch子句放置捕获基类异常的catch子句前面。如果不这样做,捕获基类异常的catch子句会捕获到子类的异常。例如下面的程序:

//捕获子类的异常。这个程序是错误的
#include <iostream>
using namespace std;
 
class B
{
};
 
class D: public B
{
};
 
int main()
{
    D derived;
 
    try
    {
        throw derived;
    }
    catch(B b)
    {
        cout << "Caught a base calls.\n";
    }
    catch (D d)
    {
        cout <<"This won't execute.\n";
    }
 
    return 0;
}


这里,由于derived是一个基类为B的子类对象,所以它将被第一个catch子句所捕获,第二个catch子句是永远也不会被执行的。有些编译器会对这样的程序产生告警。还有的编译器则会报告编译错误,从而停止编译。总之,要修改这样的错误或者告警,只要调整一下catch子句的顺序即可。

捕获所有的异常

    在某些情况下,我们需要异常处理程序捕获所有的异常,而不是捕获某种特定类型的异常。我们可以采用下面的形式来实现这样的功能:

catch(...) { //处理所有的异常}

这里,省略号表示所有的异常。下面的程序演示了这种用法:

#include <iostream>
using namespace std;
 
void Xhandler(int test)
{
    try
    {
        if ( test == 0) throw test; //抛出一个整型数的异常
        if ( test == 1) throw 'a';  //抛出一个字符类型的异常
        if ( test == 2) throw 123.33;//抛出一个浮点类型的异常
    }
    catch(...)//捕获所有的异常
    {
        cout << "Caught one!\n";
    }
}
 
int main()
{
    cout << "start\n";
 
    Xhandler(0);
    Xhandler(1);
    Xhandler(2);
 
    cout << "end";
 
    return 0;
}


程序的输出如下:

start

Caught one!

Caught one!

Caught one!

end

Xhandler()函数抛出了三种类型的异常:整型的,字符型的和浮点类型的。所有类型的异常都可以通过语句catch(...)来捕获了。

    这种catch的最典型的一种用法就是把这个catch放置在一个try的众多的catch子句中的最后一个。这样,就提供了一种缺省的或者是针对所有类型异常的处理方法。这种方式也是一种处理那些我们不想明确如何处理的异常的好方法。同样这种方式可以处理因为我们没有捕获某个异常而导致程序异常终止的情况。

在函数中明确抛出异常

我们可以指定函数会抛出的异常的类型,也可以限制函数抛出某种异常。只需要在函数定义的后面加上throw子句就可以了。这种throw子句的基本形式如下:

ret-type func-name(arg-list) throw(type-list)

{

    //...

}

其中,只有在throw后面的由逗号间隔开来的数据类型的异常才可以由该函数爆出。如果函数抛出其他类型的异常则会导致程序异常终止。如果我们不希望函数抛出任何异常,我们可以保持这个列表为空。

注意:在编写本书的时候, Visual C++实际上还不能阻止我们在函数中抛出不在异常列表类型中的异常的做法。这是一种不标准的行为。我们仍然可以指定throw子句,但是该子句只是提供了一些函数习惯的信息而已。(译者注:言下之意,不参与是否能进行正确编译的判断)。

下面的程序就展示了如何指定函数抛出各种类型的异常:

//指定函数抛出异常的类型
 
 
#include <iostream>
using namespace std;
 
//下面的函数只能抛出整形,字符型和双精度浮点型的异常
void Xhandler(int test) throw(int,char, double) //throw子句指明了函数能抛出的异常的类型
{
    if ( test == 0 ) throw test;
    if ( test == 1 ) throw 'a';
    if ( test == 2 ) throw 123.23;
}
 
int main()
{
    cout << "start\n";
    try
    {
        Xhandler(0); //同样,我们也可以传入1和2
    }
    catch (int i)
    {
    cout << "Caught int\n";
    }
    catch (char c)
    {
        cout << "Caught char\n";
    }
    catch (double  d)
    {
        cout << "Caught double\n";
    }
 
    cout << "end";
 
    return 0;
}

在上面的这个程序中, Xhandler() 函数只能抛出整形,字符型和双精度类型的异常。如果该函数企图抛出其他类型的异常,程序会异常终止。为了掩饰这一点,我们可以把上面函数抛出 int 类型的异常删除掉,然后重试该程序,就会看到程序异常终止。(就面前 Visual C++ 来说,程序不会异常终止,这点我们在前面有提到过。)

非常重要的一点:我们可以限制函数给调用他的try代码块抛出的异常的类型;但是函数中的一个try块是可以抛出任意类型的异常的,只要这些异常在同一个函数中被捕获。这点限制仅局限于函数需要把异常抛出到函数本身之外。

再一次抛出异常

    我们可以在异常处理程序中再次抛出同一个异常。这种做法将使得当前的异常被传递给更外层的try/catch序列块来处理。这种情况最常用于对同一个异常进行多次处理的情况。例如,一个异常处理程序只能处理异常的某一个方面,而别的处理程序可以处理其他的方面。异常只能在catch代码块中被重新抛出或者是由在该段代码块中调用的函数重新抛出。当异常被重新抛出的时候,它是不会被同一个catch语句所捕获的,而是会被传播到更外层的catch语句的。下面的程序就演示了重新抛出异常。其中重新抛出的是一个char*类型的异常。//演//演示重新抛出一个异常

#include <iostream>
using namespace std;
 
void Xhandler()
{
    try
    {
        throw "hello"; //抛出一个字符指针类型的异常
    }
    catch (char* e)
    {
        cout<< "Caught char * inside Xhandler\n";
        throw; //重新抛出该字符指针类型的异常到函数的外部
    }
    
}
 
int main()
{
    cout << "start \n";
 
    try
    {
        Xhandler();
    }
    catch (char* e)
    {
        cout << "Caught char * inside main\n";
    }
 
    cout << "End";
    
    return 0;
}


上面程序的输出如下;

start

Caught char * inside Xhandler

Caught char * inside main

End

练习:

1. 怎么才能捕获所有的异常?

2. 如何指定可以被抛出到函数体外的异常的类型?

3. 如何将一个异常重新抛出?

专家答疑:

问:

    通过上面的学习我们可以看出函数有两种方式可以来报告错误:一个是通过返回错误码,另外一个就是通过抛出异常。请问,通常情况下,我们应该使用哪种方式呢?

答:

    你说的是正确的。通常有两种方式来报告错误:一个是通过函数返回错误码,一个是通过抛出异常。现如今,大多数的专家更倾向于使用异常机制而不是返回错误码的方法。例如,在JavaC#中都是依靠异常机制来报告错误的,例如报告打开文件的错误或者是算术运算溢出的错误。由于C++是从C发展而来,他融合了错误码方式和异常机制两者来报告错误,因此,许多C++库函数还都是通过返回错误码的方式来报告错误的。然而,在新编写的代码中,我们应该考虑使用异常处理机制来报告错误。这也是现代的编程模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值