一 前言:ios
異常處理,對於作面向對象開發的開發者來講是再熟悉不過了,例如在C#中有設計模式
tryapp
{ide
...函數
}spa
catch( Exception e){...}設計
finally{調試
.....code
}orm
在C++中,咱們經常會使用
try{}
...
catch(){}
塊來進行異常處理。
說了那麼多,那麼到底什麼是異常處理呢?
異常處理(又稱爲錯誤處理)功能提供了處理程序運行時出現的任何意外或異常狀況的方法。
異常處理通常有兩種模型,一種是"終止模型",一種是"恢復模型"
"終止模型":在這種模型中,將假設錯誤很是關鍵,將以至於程序沒法返回到異常發生的地方繼續執行.一旦異常被拋出,就代表錯誤已沒法挽回,也不能回來繼續執行.
"恢復模型":異常處理程序的工做是修正錯誤,而後從新嘗試調動出問題的方法,並認爲的二次能成功. 對於恢復模型,一般但願異常被處理以後能繼續執行程序.在這種狀況下,拋出異常更像是對方法的調用--能夠在Java裏用這種方法進行配置,以獲得相似恢復的行爲.(也就是說,不是拋出異常,而是調用方法修正錯誤.)或者,把try塊放在while循環裏,這樣就能夠不斷的進入try塊,直到獲得滿意的結果.
二 面向對象中的異常處理
大體瞭解了什麼是異常處理後,因爲異常處理在面嚮對象語言中使用的比較廣泛,咱們就先以C++爲例,作一個關於異常處理的簡單例子:
問題:求兩個數相除的結果。
這裏,隱藏這一個錯誤,那就是當除數爲0時,會出現,因此,咱們得使用異常處理來捕捉這個異常,並拋出異常信息。
具體看代碼:
1
#include
<
iostream
>
2
#include
<
exception
>
3
using
namespace
std;
4
class
DivideError:
public
exception
5
{
6
public
:
7
DivideError::DivideError():exception(){}
8
const
char
*
what(){
9
return
"
試圖去除一個值爲0的數字
"
;
10
}
11
12
};
13
double
quotion(
int
numerator,
int
denominator)
14
{
15
if
(
0
==
denominator)
//
當除數爲0時,拋出異常
16
throw
DivideError();
17
return
static_cast
<
double
>
(numerator)
/
denominator;
18
}
19
int
main()
20
{
21
int
number1;
//
第一個數字
22
int
number2;
//
第二個數字
23
double
result;
24
cout
<<
"
請輸入兩個數字:
"
;
25
while
(cin
>>
number1
>>
number2){
26
try
{
27
result
=
quotion(number1,number2);
28
cout
<<
"
結果是 :
"
<<
result
<<
endl;
29
30
}
//
end try
31
catch
(DivideError
&
divException){
32
cout
<<
"
產生異常:
"
33
<<
divException.what()
<<
endl;
34
}
35
}
36
37
}
38
在這個例子中,咱們使用了<expection>頭文件中的exception類,並使DivideError類繼承了它,同時重載了虛方法what(),以給出特定的異常信息。
而C#中的異常處理類則封裝的更有全面,裏面封裝了經常使用的異常處理信息,這裏就很少說了。
三 C語言中的異常處理
在C語言中異常處理通常有這麼幾種方式:
1.使用標準C庫提供了abort()和exit()兩個函數,它們能夠強行終止程序的運行,其聲明處於<stdlib.h>頭文件中。
2.使用assert(斷言)宏調用,位於頭文件<assert.h>中,當程序出錯時,就會引起一個abort()。
3.使用errno全局變量,由C運行時庫函數提供,位於頭文件<errno.h>中。
4.使用goto語句,當出錯時跳轉。
5.使用setjmp,longjmp進行異常處理。
接下來,咱們就依次對這幾種方式來看看究竟是怎麼作的:
咱們仍舊之前面處理除數爲0的異常爲例子。
1.使用exit()函數進行異常終止:
1
#include
<
stdio.h
>
2
#include
<
stdlib.h
>
3
double
diva(
double
num1,
double
num2)
//
兩數相除函數
4
{
5
double
re;
6
re
=
num1
/
num2;
7
return
re;
8
}
9
int
main()
10
{
11
double
a,b,result;
12
printf(
"
請輸入第一個數字:
"
);
13
scanf(
"
%lf
"
,
&
a);
14
printf(
"
請輸入第二個數字:
"
);
15
scanf(
"
%lf
"
,
&
b);
16
if
(
0
==
b)
//
若是除數爲0終止程序
17
exit(EXIT_FAILURE);
18
result
=
diva(a,b);
19
printf(
"
相除的結果是: %.2lf\n
"
,result);
20
return
0
;
21
}
其中exit的定義以下:
_CRTIMP void __cdecl __MINGW_NOTHROW exit (int) __MINGW_ATTRIB_NORETURN;
exit的函數原型:void exit(int)由此,咱們也能夠知道EXIT_FAILURE宏應該是一個整數,exit()函數的傳遞參數是兩個宏,一個是剛纔看到的EXIT_FAILURE,還有一個是EXIT_SUCCESS從字面就能夠看出一個是出錯後強制終止程序,而一個是程序正常結束。他們的定義是:
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
到此,當出現異常的時候,程序是終止了,可是咱們並無捕獲到異常信息,要捕獲異常信息,咱們可使用註冊終止函數atexit(),它的原型是這樣的:int atexit(atexit_t func);
具體看以下程序:
1
#include
<
stdio.h
>
2
#include
<
stdlib.h
>
3
void
Exception(
void
)
//
註冊終止函數,經過掛接到此函數,捕獲異常信息
4
{
5
printf(
"
試圖去除以一個爲0的數字,出現異常!\n
"
);
6
}
7
int
main()
8
{
9
double
a,b,result;
10
printf(
"
請輸入第一個數字:
"
);
11
scanf(
"
%lf
"
,
&
a);
12
printf(
"
請輸入第二個數字:
"
);
13
scanf(
"
%lf
"
,
&
b);
14
if
(
0
==
b)
//
若是除數爲0終止程序 ,並掛接到模擬異常捕獲的註冊函數
15
{
16
17
atexit(Exception);
18
exit(EXIT_FAILURE);
19
}
20
result
=
diva(a,b);
21
printf(
"
相除的結果是: %.2lf\n
"
,result);
22
return
0
;
23
}
這裏須要注意的是,atexit()函數老是被執行的,就算沒有exit()函數,當程序結束時也會被執行。而且,能夠掛接多個註冊函數,按照堆棧結構進行執行。abort()函數與exit()函數相似,當出錯時,能使得程序正常退出,這裏就很少說了。
2.使用assert()進行異常處理:
assert()是一個調試程序時常用的宏,切記,它不是一個函數,在程序運行時它計算括號內的表達式,若是表達式爲FALSE (0), 程序將報告錯誤,並終止執行。若是表達式不爲0,則繼續執行後面的語句。這個宏一般原來判斷程序中是否出現了明顯非法的數據,若是出現了終止程序以避免致使嚴重後果,同時也便於查找錯誤。
另外須要注意的是:assert只有在Debug版本中才有效,若是編譯爲Release版本則被忽略。
咱們就前面的問題,使用assert斷言進行異常終止操做:構造可能出現出錯的斷言表達式:assert(number!=0)這樣,當除數爲0的時候,表達式就爲false,程序報告錯誤,並終止執行。
代碼以下:
代碼
#include
<
stdio.h
>
#include
<
assert.h
>
double
diva(
double
num1,
double
num2)
//
兩數相除函數
{
double
re;
re
=
num1
/
num2;
return
re;
}
int
main()
{
printf(
"
請輸入第一個數字:
"
);
scanf(
"
%lf
"
,
&
a);
printf(
"
請輸入第二個數字:
"
);
scanf(
"
%lf
"
,
&
b);
assert(
0
!=
b);
//
構造斷言表達式,捕獲預期異常錯誤
result
=
diva(a,b);
printf(
"
相除的結果是: %.2lf\n
"
,result);
return
0
;
}
3.使用errno全局變量,進行異常處理:
errno全局變量主要在調式中,當系統API函數發生異常的時候,將errno變量賦予一個整數值,根據查看這個值來推測出錯的緣由。
其中的各個整數值都有一個相應的宏定義,表示不一樣的異常緣由:
代碼
#define
EPERM 1 /* Operation not permitted */
#define
ENOFILE 2 /* No such file or directory */
#define
ENOENT 2
#define
ESRCH 3 /* No such process */
#define
EINTR 4 /* Interrupted function call */
#define
EIO 5 /* Input/output error */
#define
ENXIO 6 /* No such device or address */
#define
E2BIG 7 /* Arg list too long */
#define
ENOEXEC 8 /* Exec format error */
#define
EBADF 9 /* Bad file descriptor */
#define
ECHILD 10 /* No child processes */
#define
EAGAIN 11 /* Resource temporarily unavailable */
#define
ENOMEM 12 /* Not enough space */
#define
EACCES 13 /* Permission denied */
#define
EFAULT 14 /* Bad address */
/*
15 - Unknown Error
*/
#define
EBUSY 16 /* strerror reports "Resource device" */
#define
EEXIST 17 /* File exists */
#define
EXDEV 18 /* Improper link (cross-device link?) */
#define
ENODEV 19 /* No such device */
#define
ENOTDIR 20 /* Not a directory */
#define
EISDIR 21 /* Is a directory */
#define
EINVAL 22 /* Invalid argument */
#define
ENFILE 23 /* Too many open files in system */
#define
EMFILE 24 /* Too many open files */
#define
ENOTTY 25 /* Inappropriate I/O control operation */
/*
26 - Unknown Error
*/
#define
EFBIG 27 /* File too large */
#define
ENOSPC 28 /* No space left on device */
#define
ESPIPE 29 /* Invalid seek (seek on a pipe?) */
#define
EROFS 30 /* Read-only file system */
#define
EMLINK 31 /* Too many links */
#define
EPIPE 32 /* Broken pipe */
#define
EDOM 33 /* Domain error (math functions) */
#define
ERANGE 34 /* Result too large (possibly too small) */
/*
35 - Unknown Error
*/
#define
EDEADLOCK 36 /* Resource deadlock avoided (non-Cyg) */
#define
EDEADLK 36
/*
37 - Unknown Error
*/
#define
ENAMETOOLONG 38 /* Filename too long (91 in Cyg?) */
#define
ENOLCK 39 /* No locks available (46 in Cyg?) */
#define
ENOSYS 40 /* Function not implemented (88 in Cyg?) */
#define
ENOTEMPTY 41 /* Directory not empty (90 in Cyg?) */
#define
EILSEQ 42 /* Illegal byte sequence */
這裏咱們就不之前面的除數爲0的例子來進行異常處理了,由於我不知道如何定義本身特定錯誤的errno,若是哪位知道,但願能給出方法。我以一個網上的例子來講明它的使用方法:
代碼
#include
<
errno.h
>
#include
<
math.h
>
#include
<
stdio.h
>
int
main(
void
)
{
errno
=
0
;
if
(NULL
==
fopen(
"
d:\\1.txt
"
,
"
rb
"
))
{
printf(
"
%d
"
, errno);
}
else
{
printf(
"
%d
"
, errno);
}
return
0
; }
這裏試圖打開一個d盤的文件,若是文件不存在,這是查看errno的值,結果是二、
當文件存在時,errno的值爲初始值0。而後查看值爲2的錯誤信息,在宏定義那邊#define ENOFILE 2 /* No such file or directory */
便知道錯誤的緣由了。
4.使用goto語句進行異常處理:
goto語句相信你們都很熟悉,是一個跳轉語句,咱們仍是以除數爲0的例子,來構造一個異常處理的例子:
代碼
#include
<
stdio.h
>
double
diva(
double
num1,
double
num2)
//
兩數相除函數
{
double
re;
re
=
num1
/
num2;
return
re;
}
int
main()
{
int
tag
=
0
;
double
a,b,result;
if
(
1
==
tag)
{
Throw:
printf(
"
除數爲0,出現異常\n
"
);
}
tag
=
1
;
printf(
"
請輸入第一個數字:
"
);
scanf(
"
%lf
"
,
&
a);
printf(
"
請輸入第二個數字:
"
);
scanf(
"
%lf
"
,
&
b);
if
(b
==
0
)
//
捕獲異常(或許這麼說並不恰當,暫且這麼理解)
goto
Throw;
//
拋出異常
result
=
diva(a,b);
printf(
"
%d\n
"
,errno);
printf(
"
相除的結果是: %.2lf\n
"
,result);
return
0
;
}
5.使用setjmp和longjmp進行異常捕獲與處理:
setjmp和longjmp是非局部跳轉,相似goto跳轉做用,可是goto語句具備侷限性,只能在局部進行跳轉,當須要跳轉到非一個函數內的地方時就須要用到setjmp和longjmp。setjmp函數用於保存程序的運行時的堆棧環境,接下來的其它地方,你能夠經過調用longjmp函數來恢復先前被保存的程序堆棧環境。異常處理基本方法:
使用setjmp設置一個跳轉點,而後在程序其餘地方調用longjmp跳轉到該點(拋出異常).
代碼以下所示:
#include
<
stdio.h
>
#include
<
setjmp.h
>
jmp_buf j;
void
Exception(
void
)
{
longjmp(j,
1
);
}
double
diva(
double
num1,
double
num2)
//
兩數相除函數
{
double
re;
re
=
num1
/
num2;
return
re;
}
int
main()
{
double
a,b,result;
printf(
"
請輸入第一個數字:
"
);
scanf(
"
%lf
"
,
&
a);
printf(
"
請輸入第二個數字:
"
);
if
(setjmp(j)
==
0
)
{
scanf(
"
%lf
"
,
&
b);
if
(
0
==
b)
Exception();
result
=
diva(a,b);
printf(
"
相除的結果是: %.2lf\n
"
,result);
}
else
printf(
"
試圖除以一個爲0的數字\n
"
);
return
0
;
}
四 總結:
除了以上幾種方法以外,另外還有使用信號量等等方法進行異常處理。固然在實際開發中每一個人都有各類調式的技巧,並且這文章並非說明異常處理必定要這樣作,這只是對通常作法的一些總結,也不要亂使用異常處理,若是弄的很差就嚴重影響了程序的效率和結構,就像設計模式同樣,不能胡亂使用。