程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误: 1.语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。 2.逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。 3.运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。 C++ 异常(Exception)机制就是为解决运行时错误而引入的。 4.运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。 C++ 提供了异常(Exception)机制,让我们能够捕获运行时错误,给程序一次“起死回生”的机会,或者至少告诉用户发生了什么再终止程序。
没捕获异常的程序
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "http://www.baidu.com";
char ch1 = str[100];
cout << ch1 << endl;
char ch2 = str.at(100);
cout << ch2 << endl;
return 0;
}
输出:
terminate called after throwing an instance of 'std::out_of_range'
what(): basic_string::at: __n (which is 100) >= this->size() (which is 20)
at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与[ ]不同,at() 会检查下标是否越界,如果越界就抛出一个异常;而[ ]不做检查,不管下标是多少都会照常访问。
捕获异常
C++中使用try{}catch(){}来捕获异常
#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main() {
string str = "http://www.baidu.com";
try {
char ch1 = str[100];
cout << ch1 << endl;
} catch (exception e) {
cout << "[1]out of bound" << endl;
}
try {
char ch2 = str.at(100);
cout << ch2 << endl;
} catch (exception &e) {
cout << "[2]out of bound!" << endl;
}
return 0;
}
输出:
[2]out of bound!
第一个 try 没有捕获到异常,输出了一个没有意义的字符(垃圾值)。因为[ ]不会检查下标越界,不会抛出异常, 所以即使有错误,try 也检测不到。换句话说,发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到。所谓抛出异常,就是明确地告诉程序发生了什么错误。 第二个 try 检测到了异常,并交给 catch 处理,执行 catch 中的语句。需要说明的是,异常一旦抛出,会立刻被 try 检测到,并且不会再执行异常点(异常发生位置)后面的语句。 本例中抛出异常的位置是at() 函数,它后面的 cout 语句就不会再被执行,所以看不到它的输出。 说得直接一点,检测到异常后程序的执行流会发生跳转,从异常点跳转到 catch 所在的位置,位于异常点之后的、并且在当前 try 块内的语句就都不会再执行了;即使 catch 语句成功地处理了错误,程序的执行流也不会再回退到异常点,所以这些语句永远都没有执行的机会了。
抛出异常
C++中throw关键字用来抛出一个异常,这个异常会被 try 检测到,进而被 catch 捕获。
#include <iostream>
#include <string>
#include <exception>
using namespace std;
const char *ERRORLOG = "Unknown Exception";
void func_error() {
throw ERRORLOG;
cout << "this code will not be executed" << endl;
}
int main() {
try {
func_error();
cout << "this code will not be executed too" << endl;
} catch (const char *&e) {
cout << e << endl;
}
return 0;
}
输出:
Unknown Exception
func_error() 在 try 块中被调用,它抛出的异常会被 try 检测到,进而被 catch 捕获。从运行结果可以看出,func_error() 中的 cout 和 try 中的 cout 都没有被执行。 再来看一个例子:
#include <iostream>
#include <string>
#include <exception>
using namespace std;
const char *ERRORLOG = "Unknown Exception";
void func_error_inner() {
throw ERRORLOG;
cout << "this code will not be executed" << endl;
}
void func_error_outer() {
func_error_inner();
cout << "this code will not be executed!" << endl;
}
int main() {
try {
func_error_outer();
cout << "this code will not be executed too" << endl;
} catch (const char *&e) {
cout << e << endl;
}
cout << "the statement outside of the try_catch is called normal" << endl;
return 0;
}
输出:
Unknown Exception
the statement outside of the try_catch is called normal
发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇见 try 才停止。在这个回退过程中,调用链中剩下的代码(所有函数中未被执行的代码)都会被跳过,没有执行的机会了。本例中try检测到func_error_outer()中有异常,回退中发现异常抛出的位置发生在func_error_inner(),在此函数体中捕获到异常停止执行之后的代码。
在使用try_catch中发现,catch关键字后面的括号里有异常类型的形参,其中exception 是异常类型,e 是一个形参变量,用来接收异常信息。当程序抛出异常时,会创建一份数据,这个数据中包含了错误信息,在开发的时候可以根据这些信息来判断程序到底出了什么问题。 异常既然是一份数据,那么就应该有数据类型。C++ 规定,异常类型可以是 int、char、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型。 C++ 语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的异常。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象。 异常类型的形参和函数的形参非常类似,当异常发生后,会将异常数据传递给e 这个变量,这和函数传参的过程类似。当然,只有跟 exception 类型匹配的异常数据才会被传递给 e ,否则 catch 不会接收这份异常数据,也不会执行 catch 块中的语句。换句话说,catch 不会处理当前的异常。 但是 catch 和真正的函数调用又有区别: 1.真正的函数调用,形参和实参的类型必须要匹配,或者可以自动转换,否则在编译阶段就报错了。 2.而对于 catch,异常是在运行阶段产生的,它可以是任何类型,没法提前预测,所以不能在编译阶段判断类型是否正确,只能等到程序运行后,真的抛出异常了,再将异常类型和 catch 能处理的类型进行匹配,匹配成功 的话就“调用”当前的 catch,否则就忽略当前的 catch。 总起来说,catch 和真正的函数调用相比,多了一个在运行阶段将实参和形参匹配 的过程。 另外需要注意的是,如果不希望 catch 处理异常数据,也可以将 e 省略掉,写作:
try{
}catch(exception){
}
这样只会将异常类型和 catch 所能处理的类型进行匹配,不会传递异常数据了。
|