需要异常处理的情况
程序运行时常会碰到一些异常情况,例如:
- 做除法的时候除数为 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 0↙ 2 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 0↙ 2 before dividing. 3 catch (...) 4 finished
或则这样:
1 0 6↙ 2 before dividing. 3 catch (double) -1 4 finished
当 m 为 0 时,拋出一个 double 类型的异常。虽然catch (double)和catch(…)都能匹配该异常,但是catch(double)是第一个能匹配的 catch 块,因此会执行它,而不会执行catch(…)块,这有点类似于函数重载的最佳匹配。
由于catch(…)能匹配任何类型的异常,它后面的 catch 块实际上就不起作用,因此不要将它写在其他 catch 块前面。
- 如果没有一个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 f = Devide(3, 0); 36 cout << "end of try block" << endl; 37 } 38 catch (CException e) { 39 cout << e.msg << endl; 40 } 41 cout << "f = " << f << endl; 42 cout << "finished" << endl; 43 return 0; 44 }
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 (); 标签:处理,C++,catch,拋出,include,异常,throw From: https://www.cnblogs.com/Sandals-little/p/17488797.html