我们总是希望自己所写的程序都是正确,并且运行结果也是正确的。但这是很难的,因此我们必须要考虑程序存在错误的情况。
程序中常见的错误:语法错误
和运行错误
。
语法错误在编译时编译系统是能够发现的,并且编译系统会告诉我们错误是什么、位置是在哪儿,因此这样的错误是比较容易发现和纠正的。
另外还有一些程序它能正常通过编译也能运行,但是在运行过程中会出现异常,往往得不到正确的结果,甚至导致程序
死亡
。
程序死亡的三种方式:
- 无疾而终(自然死亡,通常是return 0)
- 自杀(abort与exit)
- 他杀(系统对异常进行处理使程序终止)
我们都知道在拨打电话号码时,若不小心拨的是空号,此时电话系统不会让你无终止的等待,也不会简单的挂机了事;反而它会告诉你“对不起,您拨打的号码是空号”,这样人们就明白出了什么样的错误,应该采取什么样的措施纠正。
同样地,人们不仅希望程序能在正确的情况下运行,对于有错的情况也能做出相应的处理,帮助我们尽快发现问题并且改正错误,而不致使程序莫名其妙的中止。
对异常能进行处理,这就是系统的容错能力。
在设计程序时应当事先分析程序运行时可能出现的各种意外情况,并且分别制定出相应的处理方法这就是程序的异常处理的任务。
异常处理具体过程:在程序中设置了异常处理机制,若在运行时出现了异常,程序的流程就会跳转到异常处理代码段处理。
####C语言–异常处理
- 终止程序(除数为0)
- 返回一个表示错误的值,附加错误码(GetLastError())
- 返回一个合法值,让程序处于某种非法的状态(坑爹的atoi())
- 调用一个预先准备好在出现”错误”的情况下用的函数(回调函数)
- 暴力解决方式:abort()或者exit()
- 使用goto语句
- setjmp()和longjmp()组合
#####1. 终止程序(除数为0)
void FunTest()
{
int tmp = 0;
int i = 5 / tmp;
}
int main()
{
FunTest();//程序会直接崩溃
return 0;
}
#####2. 返回一个表示错误的值,附加错误码(GetLastError())
int main()
{
FILE* pf = fopen("1.txt", "rb");
if (NULL == pf) {
cout << "error=" << errno << endl;
int i = GetLastError();//获取最近的错误码 #include<windows.h>
cout << "i=" << i << endl;
return 0;
}
fclose(pf);
return 0;
}
输出结果:
输出结果为2,代表了什么意思,使用 &err,hr 在监视里查看错误码和错误消息(&err,hr的意义相当于让调试器帮你获取GetLastError的值)或者打开工具栏的错误查找,输入2,查看错误信息。
#####3. 返回一个合法值,让程序处于某种非法的状态(坑爹的atoi())
int main()
{
cout << atoi("-12345.67") << endl;//输出-12345
cout << atoi("12ab34") << endl;//输出12
return 0;
}
atoi() 函数扫描参数 str 字符串,会跳过前面的空白字符(例如空格,tab缩进等)直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时(‘\0’)才结束转换,并将结果返回,且只适用于十进制数字;上面的代码中若"12ab34"中的’ab’为字符,则输出没有错;若‘ab’为十六进制数字,则输出就不是我们想要的结果了。
#####4. 调用一个预先准备好在出现”错误”的情况下用的函数(回调函数)
void Error()
{
printf("文件不存在\n");
}
int main()
{
FILE* pf = fopen("1.txt", "r");
if (NULL == pf) {
printf("操作无效,");//若1.txt不存在,则会调用Error
Error();
system("pause");
return 0;
}
printf("打开文件成功\n");
fclose(pf);
system("pause");
return 0;
}
#####5. 暴力解决方式:abort()或者exit()
两者区别:abort是直接中断;exit是退出,会做一些清理工作
int Div(int a,int b){
if(b==0){
exit(1); //直接退出
//abort(); //abort是直接中断,但在Windows下会弹出一个信息框
}
return a/b;
}
int main(){
printf("正常情况:4/2 = %d\n",Div(4,2));
printf("错误情况:4/0 = %d\n",Div(4,0));
return 0;
}
#####6. 使用goto语句
goto语句通常与条件语句配合使用来改变程序流向,使得程序转去执行语句标号所标识的语句。
int main()
{
FILE* pf = fopen("1.txt", "rb");
if (NULL == pf) {
int i = GetLastError();
cout << "i=" << i << endl;
goto L;//goto语句
}
fclose(pf);
L:
return 0;
}
补充一下:
若代码是这个样子,使用goto语句会跳转到百度页面吗?
答案:并不会,编译时也不会报错。
编译器的理解:
http: 是一个标签,地位相当于L:
//www.baidu.com是一个标注,地位相当于 //goto语句
#####7. setjmp()和longjmp()组合
非局部跳转语句:setjmp和longjmp函数。非局部指的是,这不是goto语句,goto语句可以实现在一个函数内实施的跳转,而setjmp和longjmp函数是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。
#include <setjmp.h>
int setjmp(jmp_buf env);
//返回值:若直接调用则返回0,若从longjmp调用则返回非0值的longjmp中的val值
Void longjmp(jmp_buf env,int val);
//调用此函数则返回到语句setjmp所在的地方,其中env 就是setjmp中的 env,而val 则是使setjmp的返回值变为val。
上代码:
#include<setjmp.h>
jmp_buf buf;//将buf定义成全局变量,为了保存当前信息的执行点
void Test1()
{
FILE* pf = fopen("1.txt", "r");
if (NULL == pf) {
longjmp(buf, 1);
}
//正常操作
fclose(pf);
}
void Test2()
{
int* p = (int*)malloc(sizeof(int) * 100000000);
if (NULL == p) {
longjmp(buf, 2);
}
//正常操作
free(p);
}
int Test3(int a, int b)
{
if (0 == b) {
longjmp(buf, 2);
}
return a/b;
}
int main()
{
int istate = setjmp(buf);//换成int istate = 0 程序会崩溃
if (0 == istate) {
Test1();
Test2();
Test3(10, 0);
}
else {
switch (istate) {
case 1:
cout<<"打开文件失败"<<endl;
break;
case 2:
cout << "申请空间失败" << endl;
break;
case 3:
cout << "除数为0非法" << endl;
break;
default:
cout << "未知错误" << endl;
}
}
system("pause");
return 0;
}
猜猜这段代码最后输出的结果是什么?
没想到吧,感觉这段代码只调用了Test1()函数,实际上我们通过调试发现真的只调用了Text1()函数,Text2与Text3根本就没有调用。
使用setjmp和longjmp要注意:
setjump必须先调用,在异常位置通过调用longjmp以恢复先前被保存的程序执行点,否则将导致不可预测的结果,甚至程序崩溃
####C+±-异常处理
C++异常机制使用了三个的关键字
抛出(throw)
──抛出一个异常
检查(try)
──标识可能出现的异常代码段
捕捉(catch)
──处理异常的代码段
异常,当一个函数发现自己无法处理的错误时会抛出异常,让函数的调用者
直接或间接的来处理这个问题。
1.异常的抛出和捕获
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
- 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的
异常对象副本
(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销
2.栈展开
- 抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。首先检查throw本身是否在try块内部,如果是,再查找匹配的catch语句。如果有匹配的,则处理;没有则退出当前函数栈,继续在调用函数的栈中进行查找,不断重复上述过程。
- 若到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行
3.异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配
。只有以下几种情况例外:
- 允许从非const对象到const的转换
- 允许从派生类型到基类类型的转换
- 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针
4.异常重新抛出
- 有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理