需要异常处理的情况

程序运行时常会碰到一些异常情况,例如:

  • 做除法的时候除数为 0;
  • 用户输入年龄时输入了一个负数;
  • 用 new 运算符动态分配空间时,空间不够导致无法分配;
  • 访问数组元素时,下标越界;打开文件读取时,文件不存在。

这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。

异常处理的原理

首先要明确:函数A的异常可以在函数A中自己处理掉,也可以留给它的调用者B去处理

try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}

语句组中会书写想要处理的异常的代码或则说何时发生异常的代码,其实语句组中还必须有throw语句,它后面要接一个表达式其值的类型可以是基本类型,也可以是类,类的话通常会输出异常的信息也就是发生了一个什么样的异常。如果说catch像一个函数的话那么异常类型就是形参,throw后面的表达式就是实参,throw抛出的类型会被catch捕获。整套代码有点像switch,如果一个throw表达式被一个catch所捕获,当然它上面也没有catch捕获这个表达式,那么它后面的catch就直接被跳过,然后继续执行正常的程序段。

 1 #include <iostream>
 2 using namespace std;
 3 int main()
 4 {
 5     double m ,n;
 6     cin >> m >> n;
 7     try {
 8         cout << "before dividing." << endl;
 9         if( n == 0)
10             throw -1; //抛出int类型异常
11         else
12             cout << m / n << endl;
13         cout << "after dividing." << endl;
14     }
15     catch(double d) {
16         cout << "catch(double) " << d <<  endl;
17     }
18     catch(int e) {
19         cout << "catch(int) " << e << endl;
20     }
21     cout << "finished" << endl;
22     return 0;
23 }

运行结果如下:

1 9 6
2 before dividing.
3 1.5
4 after dividing.
5 finished

说明当 n 不为 0 时,try 块中不会拋出异常。因此程序在 try 块正常执行完后,越过所有的 catch 块继续执行,catch 块一个也不会执行。

或则这样将有异常出现:

1 9 02 before dividing.
3 catch(int) -1
4 finished//异常处理完后面的程序正常执行
  • 如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:
1 catch(...) {
2     ...
3 }
 1 #include <iostream>
 2 using namespace std;
 3 int main()
 4 {
 5     double m, n;
 6     cin >> m >> n;
 7     try {
 8         cout << "before dividing." << endl;
 9         if (n == 0)
10             throw - 1;  //抛出整型异常
11         else if (m == 0)
12             throw - 1.0;  //拋出 double 型异常
13         else
14             cout << m / n << endl;
15         cout << "after dividing." << endl;
16     }
17     catch (double d) {
18         cout << "catch (double)" << d << endl;
19     }
20     catch (...) {
21         cout << "catch (...)" << endl;
22     }
23     cout << "finished" << endl;
24     return 0;
25 }
1 9 02 before dividing.
3 catch (...)
4 finished

或则这样:

1 0 62 before dividing.
3 catch (double) -1
4 finished

当 m 为 0 时,拋出一个 double 类型的异常。虽然catch (double)和catch(…)都能匹配该异常,但是catch(double)是第一个能匹配的 catch 块,因此会执行它,而不会执行catch(…)块,这有点类似于函数重载的最佳匹配。

由于catch(…)能匹配任何类型的异常,它后面的 catch 块实际上就不起作用,因此不要将它写在其他 catch 块前面。

 

  • 如果try块中有两个throw语句,则只执行第一个,即便第一个没有捕获它的catch语句,程序也会使库函数terminate自动调用,调用abort终止上程序,而仍然不会执行第二个throw(throw语句就像goto,一旦运行了该throw语句,try块中的throw下面的任何内容将不再被执行,直接跳到捕获它的地方)

 

 1 int main()
 2 {
 3     try
 4     {
 5         throw - 1;
 6         throw 0.5;
 7     }
 8     catch (int)
 9     {
10         cout << "This is a int exception" << endl;
11     }
12     catch (double)
13     {
14         cout << "This is a double exception" << endl;
15     }
16 }

 

 

 1 int main()
 2 {
 3     try
 4     {
 5         throw - 1;
 6         throw 0.5;
 7     }
 8     catch (string)
 9     {
10         cout << "This is a int exception" << endl;
11     }
12     catch (double)
13     {
14         cout << "This is a double exception" << endl;
15     }
16 }

 

 

  • 如果没有一个catch能够捕获throw后面的语句的话,也就是没有一个形参可以和实参相匹配的话,那么这个异常就要交给调用它的函数去处理,如果没有函数调用它的话则库函数terminate将被自动调用,其默认是调用abort终止上程序。

    

 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 class CException
 5 {
 6 public:
 7     string msg;
 8     CException(string s) : msg(s) {}
 9 };
10 double Devide(double x, double y)
11 {
12     if (y == 0)
13         throw CException("devided by zero");
14     cout << "in Devide" << endl;
15     return x / y;
16 }
17 int CountTax(int salary)
18 {
19     try {
20         if (salary < 0)
21             throw - 1;
22         cout << "counting tax" << endl;
23     }
24     catch (int) {
25         cout << "salary < 0" << endl;
26     }
27     cout << "tax counted" << endl;
28     return salary * 0.15;
29 }
30 int main()
31 {
32     double f = 1.2;
33     try {
34         CountTax(-1);
35         //Devide函数体内部没有捕捉throw CException("devided by zero")的语句
36         //因此在调用它的函数中找
37         f = Devide(3, 0);
38         //直接跳转到捕捉Devide函数体内部throw语句的catch,下面的语句不会被执行
39         cout << "end of try block" << endl;
40     }
41     catch (CException e) {
42         cout << e.msg << endl;
43     }
44     //f=1.2说明Devide函数中执行throw语句抛出异常时
45     //程序就跳转到捕捉异常的部分catch (CException e)
46     //而后面的程序甚至return语句也不会被执行
47     cout << "f = " << f << endl;
48     cout << "finished" << endl;
49     return 0;
50 }

 

salary < 0
tax counted
devided by zero
f=1.2
finished
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  • 有时,虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。在 catch 块中拋出异常可以满足这种需要。例如:
 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 int CountTax(int salary)
 5 {
 6     try {
 7         if( salary < 0 )
 8             throw string("zero salary");
 9         cout << "counting tax" << endl;
10     }
11     catch (string s ) {
12         cout << "CountTax error : " << s << endl;
13         throw; //继续抛出捕获的异常,如果一个catch语句里面还有一个throw,那么它将会把它捕获到的异常再抛给调用它的函数
14     }
15     cout << "tax counted" << endl;
16     return salary * 0.15;
17 }
18 int main()
19 {
20     double f = 1.2;
21     try {
22         CountTax(-1);
23         cout << "end of try block" << endl;
24     }
25     catch(string s) {
26         cout << s << endl;
27     }
28     cout << "finished" << endl;
29     return 0;
30 }
  • 为了增强程序的可读性和可维护性,使程序员在使用一个函数时就能看出这个函数可能会拋出哪些异常,C++ 允许在函数声明和定义时,加上它所能拋出的异常的列表,具体写法如下:
1 //声明
2 void func() throw (int, double, A, B, C);
//定义
void func() throw (int, double, A, B, C){...}

上面的写法表明 func 可能拋出 int 型、double 型以及 A、B、C 三种类型的异常。异常声明列表可以在函数声明时写,也可以在函数定义时写。如果两处都写,则两处应一致

如果异常声明列表如下编写:

1 void func() throw ();

则说明 func 函数不会拋出任何异常。

一个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。

函数如果拋出了其异常声明列表中没有的异常,在编译时不会引发错误,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作用。

C++标准异常类

C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图 1 所示。

 

bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。

下面分别介绍以上几个异常类。本节程序的输出以 Visual Studio 2010为准,Dev C++ 编译的程序输出有所不同。

1) bad_typeid

使用 typeid运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常。

 1 #include <iostream>
 2 
 3 #include <typeinfo>
 4 
 5 
 6 
 7 class Base {
 8 
 9 public:
10 
11     virtual ~Base() {}
12 
13 };
14 
15 
16 
17 class Derived1 : public Base {};
18 
19 class Derived2 : public Base {};
20 
21 
22 
23 int main() {
24 
25     Base* p = nullptr; // 定义一个空指针
26 
27     
28 
29     try {
30 
31         std::cout << typeid(p).name() << std::endl; // 输出 "Null pointer to a class of type 'Base'"
32 
33     } catch (std::exception& e) {
34 
35         std::cerr << e.what() << std::endl; // 输出 "nullptr"
36 
37     }
38 
39     
40 
41     return 0;
42 
43 }

2) bad_cast

在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。程序示例如下:

C++中的dynamic_cast是一种类型转换运算符,它允许在运行时检查一个对象的类型并将其转换为更派生类。当您需要将一个基类对象向下转换为其派生类对象,但不确定该对象是否实际继承自所需的派生类时,可以使用dynamic_cast

 

 1 #include <iostream>
 2 #include <stdexcept>
 3 using namespace std;
 4 class Base
 5 {
 6     virtual void func() {}
 7 };
 8 class Derived : public Base
 9 {
10 public:
11     void Print() {}
12 };
13 void PrintObj(Base & b)
14 {
15     try {
16         Derived & rd = dynamic_cast <Derived &>(b);
17         //此转换若不安全,会拋出 bad_cast 异常,如果是C++标准库中的异常,可以不用throw
18         rd.Print();
19     }
20     catch (bad_cast & e) {
21         cerr << e.what() << endl;
22     }
23 }
24 int main()
25 {
26     Base b;
27     PrintObj(b);
28     return 0;
29 }

 

1 Bad dynamic_cast!

在 PrintObj 函数中,通过 dynamic_cast 检测 b 是否引用的是一个 Derived 对象,如果是,就调用其 Print 成员函数;如果不是,就拋出异常,不会调用 Derived::Print。

3) bad_alloc

在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。程序示例如下:

 1 #include <iostream>
 2 #include <stdexcept>
 3 using namespace std;
 4 int main()
 5 {
 6     try {
 7         char * p = new char[0x7fffffff];  //无法分配这么多空间,会抛出异常
 8     }
 9     catch (bad_alloc & e)  {
10         cerr << e.what() << endl;
11     }
12     return 0;
13 }
bad allocation
ios_base::failure

4) out_of_range

用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。例如:

 1 #include <iostream>
 2 #include <stdexcept>
 3 #include <vector>
 4 #include <string>
 5 using namespace std;
 6 int main()
 7 {
 8     vector<int> v(10);
 9     try {
10         v.at(100) = 100;  //拋出 out_of_range 异常
11     }
12     catch (out_of_range & e) {
13         cerr << e.what() << endl;
14     }
15     string s = "hello";
16     try {
17         char c = s.at(100);  //拋出 out_of_range 异常
18     }
19     catch (out_of_range & e) {
20         cerr << e.what() << endl;
21     }
22     return 0;
23 }

在C++中,`operator[]`和`at()`都是用于访问容器中的元素的方法。它们的主要区别在于对越界访问的处理方式。

`operator[]`是标准库容器(如vector、list、map等)提供的成员函数,它允许您使用整数索引来访问容器中的元素。如果索引超出了容器的范围,则会抛出`std::out_of_range`异常。因此,在使用`operator[]`时需要特别小心,以避免出现未定义的行为。

相比之下,`at()`是C++11引入的成员函数,它提供了一种更安全的方式来访问容器中的元素。与`operator[]`不同,`at()`不会抛出异常,而是返回一个默认值(通常是容器类型对应的默认构造函数生成的对象),表示访问越界如果您尝试访问超出范围的元素,则可以使用`at()`来避免抛出异常并提供更好的错误处理机制。因此,如果您需要访问容器中的元素并且可以接受可能的越界访问,则可以使用`operator[]`。但是,如果您需要更安全地访问容器中的元素,并且希望在访问越界时获得更好的错误处理机制,则应该使用`at()`。

 

参考文章

(92条消息) C++异常处理 详解_Yuyao_Xu的博客-CSDN博客

 

void func() throw ();
 
posted on 2023-06-18 10:35  小凉拖  阅读(47)  评论(0编辑  收藏  举报