1,C 語言崇尚簡潔高效,因此語言本身並沒有異常處理的相關語法規則,但是異常處理在 C 語言中 是存在的,我們有必要從 C 語言開始先看一看 C 語言中的異常處理是怎樣, 然后對比 C++ 里面的異常處理是怎樣;
2,異常的概念:
1,程序在運行過程中可能產生異常;
1,異常是我們在程序開發中必須考慮的一些特殊情況;
2,異常(Exception)與 Bug 的區別:
1,異常是程序運行時可預料的執行分支;
1,例如計算器相關的程序,涉及到除法操作的時候,就應該全面的想想有沒有會產生意外的情況,比如除數為 0,這是一種意外情況,但是我們已經預料到這種意外情況,並且這種意外情況在計算器相關的程序里面是不可避免的,因此我們在做開發的時候,就必須有一些代碼來處理這樣的情況,這些代碼就叫做異常處理的代碼,而異常是程序運行時可以預料的情況(這就是異常和 Bug 的區別);
2,Bug 是程序中的錯誤,是不被預期的運行方式;
3,異常和 Bug 的對比:
1,異常:
1,運行時產生除 0 的情況;
2,需要打開的外部文件不存在;
3,數組訪問的越界;
2,Bug:
1,使用野指針;
2,堆數組使用結束后未釋放;
1,內存泄漏一定是 Bug;
3,選擇排序無法處理長度為 0 的數組;
4,異常處理的方式:
1,C 語言經典處理方式:if ... else ...
1,代碼示例:
1 void func(...) 2 { 3 if( 判斷是否產生異常 ) 4 { 5 // 正常情況代碼邏輯; 6 } 7 else 8 { 9 // 異常情況代碼邏輯; 10 } 11 }
2,除法操作異常處理編程實驗:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 double divide(double a, double b, int* valid) 7 { 8 const double delta = 0.000000000000001; 9 double ret = 0; 10 11 if( !((-delta < b) && (b < delta)) ) 12 { 13 ret = a / b; 14 15 *valid = 1; // 為了處理除數為 0 和本身結果就是 0 之間的區別; 16 } 17 else 18 { 19 *valid = 0; // 異常了; 20 } 21 22 return ret; 23 } 24 25 int main(int argc, char *argv[]) 26 { 27 int valid = 0; 28 double r = divide(1, 0, &valid); 29 30 if( valid ) 31 { 32 cout << "r = " << r << endl; 33 } 34 else 35 { 36 cout << "Divided by zero..." << endl; 37 } 38 39 return 0; 40 }
1,工程上 if 處理正常分支,else 處理異常分支;
2,除法操作需要三個參數,而加減乘法都只要兩個參數,這樣不優雅;
3,如果第三個參數沒有指向合法的對象(NULL),就會出現段錯誤;
3,缺陷:
1,divide 函數有 3 個參數,難以理解其用法;
1,降低了代碼的可讀性,后期的維護成本就會增加;
2,改進的方向就是統一除法和加減乘法的調用方式,將三個參數改變為兩個參數,並且還有異常處理的代碼;
2,divide 函數調用后必須判斷 valid 代表的結果;
1,當 valid 為 true 時,運行結果正常;
2,當 valid 為 false 時,運行過程出現異常;
4,通過 setjmp() 和 longjmp() 進行優化:
1,int setjmp(jmp_buf env)
1,將當前上下文保存在 jmp_buf 結構體中;
2,jmp_buf 一般的是全局變量;
2,void longjmp(jmp_buf env, int val)
1,從 jmp_buf 結構體中恢復 setjmp() 保存的上下文;
2,最終從 setjmp 函數調用點返回,返回值為 val;
3,這兩個函數不要隨便調用,因為它們簡單粗暴,它們將破壞結構化程序設計的特性,結構化程序設計三大特性有順序執行、循環執行、分支執行,如果調用這兩個函數,將會有其他的執行方式出來;
5,除法操作異常處理優化編程實驗:
1 #include <iostream> 2 #include <string> 3 #include <csetjmp> 4 5 using namespace std; 6 7 static jmp_buf env; // 保存程序執行的上下文; 8 9 double divide(double a, double b) 10 { 11 const double delta = 0.000000000000001; 12 double ret = 0; 13 14 if( !((-delta < b) && (b < delta)) ) 15 { 16 ret = a / b; 17 } 18 else 19 { 20 longjmp(env, 1); // 當 b = 0 時,跳轉回 env 這個全局變量所保存的程序上下文的地方,也就是 main() 中的 if() 語句;此時 divide() 程序的執行立即停止,並從 setjmp() 函數返回,返回值為 longjmp() 中的參數 1; 21 } 22 23 return ret; 24 } 25 26 int main(int argc, char *argv[]) 27 { 28 if( setjmp(env) == 0 ) //調用 setjmp(env) 將程序的上下文保存在 env 里面; 29 { 30 double r = divide(1, 0); // Divided by zero...; 31 double r = divide(1, 1); // 1; 32 33 cout << "r = " << r << endl; 34 } 35 else 36 { 37 cout << "Divided by zero..." << endl; 38 } 39 40 return 0; 41 }
1,程序執行過程:
1,首先執行到 main() 中的 if() 語句;
2,直接調用 setjmp(),這個函數內部就會將當前程序執行的上下文全部保存在這個全局變量里面,因為是直接調用的,所以保存完了返回值就是 0;
3,運行到 divide() 后,判斷到 b 為 0,則執行 longjum(),它根據 env 這個全局變量里面保存的信息來恢復之前的程序上下文;
4,之前的程序上下文是在 if() 中調用 setjmp() 保存的,所以說 longjmp() 函數一旦被調用,則 divide() 函數立即停止,出口在 setjmp(),此時返回值是 longjmp() 中第二個參數 1;
5,執行到 if...else 語句的 else 語句中;
2,這兩個函數配合起來破壞了結構化程序設計的三大特性,所以簡單的程序理解起來很復雜;
3,優雅但理解難,工程應用不多,工程中推薦使用第一種異常處理方式;
6,改進后缺陷:
1,setjmp() 和 longjmp() 的引入:
1,必然涉及到使用全局變量;
2,暴力跳轉導致代碼可讀性降低;
3,本質還是 if ... else ... 異常處理方式;
7,C 語言中的經典異常處理方式問題:
1,會使得編程中邏輯中混入大量的處理異常代碼;
2,正常邏輯代碼和異常處理代碼混合在一起,導致代碼迅速膨脹,難以維護;
1,工程中約定,if 處理正常情況,else 處理異常情況,好歹代碼中能夠看出正常異常處理代碼放在什么地方;
5,異常處理代碼分析實例分析:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 #define SUCCESS 0 7 #define INVALID_POINTER -1 8 #define INVALID_LENGTH -2 9 #define INVALID_PARAMETER -3 10 11 int MemSet(void* dest, unsigned int length, unsigned char v) 12 { 13 if( dest == NULL ) 14 { 15 return INVALID_POINTER; 16 } 17 18 if( length < 4 ) 19 { 20 return INVALID_LENGTH; 21 } 22 23 if( (v < 0) || (v > 9) ) 24 { 25 return INVALID_PARAMETER; 26 } 27 28 unsigned char* p = (unsigned char*)dest; 29 30 for(int i=0; i<length; i++) 31 { 32 p[i] = v; 33 } 34 35 return SUCCESS; 36 } 37 38 int main(int argc, char *argv[]) 39 { 40 int ai[5]; 41 int ret = MemSet(ai, sizeof(ai), 0); 42 43 if( ret == SUCCESS ) // 處理異常代碼邏輯; 44 { 45 } 46 else if( ret == INVALID_POINTER ) // 三個地方處理異常代碼邏輯; 47 { 48 } 49 else if( ret == INVALID_LENGTH ) 50 { 51 } 52 else if( ret == INVALID_PARAMETER ) 53 { 54 } 55 56 return ret; 57 }
1,這對於職場新人很痛苦,因為代碼邏輯的模塊不清晰;
6,C++ 中有沒有更好的異常處理方式?
1,直接通過關鍵字知道哪些代碼處理正常情況的代碼,哪些是異常情況的代碼;
7,小結:
1,程序中不可避免的會發生異常;
2,異常是在開發階段就可以預見的運行時問題;
3,C 語言中通過經典的 if ... else ... 方式處理異常;
4,C++ 中存在更好的異常處理方式;