編寫程式過程中難免出錯。程式錯誤可分為三類,它們分別是語法錯誤、語義錯誤(或稱邏輯錯誤)和運行時錯誤。針對不同錯誤,C++語言具有不同的解決辦法,最終保證所開發的程式能夠正確、穩定地運行。針對程式運行時的錯誤C++設計了專門的異常處理機制,即try-catch機制。C++標準庫為異常處理機制提供多種 ...
編寫程式過程中難免出錯。程式錯誤可分為三類,它們分別是語法錯誤、語義錯誤(或稱邏輯錯誤)和運行時錯誤。針對不同錯誤,C++語言具有不同的解決辦法,最終保證所開發的程式能夠正確、穩定地運行。針對程式運行時的錯誤C++設計了專門的異常處理機制,即try-catch機制。C++標準庫為異常處理機制提供多種不同功能的異常類。
一、程式的三類錯誤
1、語法錯誤
C++程式員未能嚴格按照語法規則編寫程式,這就屬於語法錯誤。如"cin << N;" 通過<<(插入運算符)讀取數據就是一個語法錯誤。編譯時,C++編譯器負責檢查源程式中的語法錯誤,如無語法錯誤將其翻譯稱目標程式,否則將提示錯誤信息。
2、語義錯誤
C++程式員在編寫源程式時出現邏輯錯誤,導致出現的程式結果不符合預期。比如本來是兩個數相除,卻錯寫成相乘。C++編譯器不能幫助程式員發現語義錯誤。程式員必須通過運行測試,對比程式結果才能發現語義錯誤。
3、運行時錯誤
編寫的源程式既沒有語法錯誤也沒有語義錯誤,而是由於用戶操作不當或運行環境差異導致的程式錯誤,我們將這種錯誤稱為運行時錯誤。如用戶輸入0,程式計算100/0的結果時就會出現該情況。對於這類異常情況此程式員無法阻止異常情況的發生,但可以在程式中添加異常處理機制,避免因異常情況而導致程式死機等嚴重的運行時錯誤。
二、程式的異常處理機制
1、認識異常處理機制
常見的程式運行時的異常情況:用戶操作不得當、輸入的文件不存在、網路中斷、非法訪問記憶體單元等。一個異常處理指針包含兩個部分:
1)發現異常。程式員應在可能出現異常的程式位置增加檢查異常的代碼,其目的就是及時發現異常。
2)處理異常。發現異常後,程式需要改變演算法流程,增加異常處理流程。
下麵通過一個簡單示例瞭解異常處理機制:
#include<iostream>
using namespace std;
int Div(int n)
{
if(n<=0) //檢查異常:如果n<0,則屬於異常情況
return (-1); //執行異常處理流程
return(100/n);
}
int main()
{
int a;
cin >> a;
int result = Div(a);
if(result == -1) //檢查異常:如果result = 1,則屬於異常情況
{
cout << "輸入的數據必須是正整數" <<endl; //執行異常處理流程
}else {
cout << "100 / " << a << " = "<< result << endl;
}
return 0;
}
通過上面示例註意到,在多函數結構程式中運用異常處理,將會同時涉及到主調函數和被調函數。被調函數發現異常後處理異常,並通過特定返回值(即錯誤碼)向上級主調函數報告異常情況。如果函數未多級調用還需要逐級向各自的上級函數報告異常。
涉及主調函數與被調函數之間的異常處理機制被稱為多級異常處理機制。多級異常處理機制包括三個方面的內容,它們分別是發現異常、報告異常和粗粒異常。C++為多級異常處理機制提供了一種非常方便有效的機制,這就是try-catch異常處理機制。
2、try-catch異常處理機制
C++語言通過throw語句報告異常,並通過try-catch語句提供一個捕獲並處理異常的代碼框架。C++程式通過這條語句就可以實現一套完整的異常處理機制。
throw語句的語法細則:
語法形式為:throw 異常表達式:異常表達式的結果可以是基本數據類型、自定義數據類型或類類型。異常表達式的數據類型被用於區別不同類型的異常,結果的值被用於描述異常的詳細信息。異常表達式可以是單個常量、變數或對象。電腦執行throw語句拋出一個異常,並退出當前函數的執行。
示例:
throw 15; //拋出一個int型異常,異常的詳細信息是15
throw "Invalid value"; //拋出一個字元串型的異常,異常的詳細信息為"Invalid value"
try-catch語句的語法形式為:
try
{
受保護代碼段
}
catch (異常類型1)
{異常類型1的處理代碼}
catch (異常類型2)
{異常類型2的處理代碼}
... ...
try-catch語句的語法說明:
- 通過try語句將可能會出現異常的代碼段保護起來。受保護的代碼段如果通過throw拋出異常,將會被try後的catch捕獲處理。這裡的捕獲不僅可以捕獲被調函數的異常,還可以捕獲下級被調函數的異常類型。
- catch子句負責捕獲並處理異常。每個catch子句只負責處理一種類型的異常。異常類型就是拋出該異常的throw語句中表達式結果的類型。
- 若受保護代碼段未報告任何異常,則所有的catch子句都不會執行。
- 若受保護代碼段報告了異常,則根據異常類型依次匹配catch子句。如果在主調函數中匹配到catch子句,則進行依次處理,其他catch子句將不在匹配和執行。也就是說一個異常只能被捕獲一次。若在主調函數中未被任何catch子句捕獲,則逐級向上繼續報告異常,直到該異常被捕獲處理。假設某異常到最上級主函數main都未被捕獲,則自動使用預設方法來進行處理。一般預設方法就是中止當前程式的執行。
- catch(...)形式的子句可匹配並捕獲任意類型的異常,其後的其他catch子句都不在執行。一幫將其放到最後。
下麵通過一個示例來更好的瞭解try-catch的使用:
#include<iostream>
using namespace std;
int Div(int n)
{
if(n<=0) //檢查異常:如果n<0,則屬於異常情況
throw (-1); //執行異常處理流程:拋出int型異常-1,並退出當前函數執行
return(100/n);
}
int main()
{
int a;
cin >> a;
try //通過try保護代碼段,接收異常
{
int result = Div(a);
cout << "100 / " << a << " = "<< result << endl;
}
catch(int x) //捕獲int型異常,也可以是catch(int)
{
cout << "輸入的數據必須是正整數" <<endl;
cout << "異常詳細信息為:" << x <<endl;
catch(...) //捕獲任意類型異常
{
cout<<"發生了其他異常"<<endl;
}
return 0;
}
電腦在執行到throw語句時首先計算異常表達式,然後拋出異常並退出當前函數的執行。throw和return語句不同的是,在執行return語句將返回主調函數,繼續執行函數調用語句的下一條語句,而執行throw語句將直接跳轉到try子句後面catch子句,開始執行子句的異常捕獲及處理操作;return語句時正常退出函數,throw語句則是異常退出。try-catch機制保證異常退出時能自動釋放當前函數的形參,局部變數或對象。釋放局部對象時會自動調用其析構函數。
可以看出catch子句所定義的參數相當於函數形參,可以接收throw語句中異常表達式的結果。throw語句中的異常表達式相當於時調用函數的實參。
3、使用類描述異常
throw語句所拋出的異常可以是基本數據類型、自定義或類類型。類類型可以提供更多的異常信息和異常處理能力。
#include<iostream>
using namespace std;
class Error
{
public:
int errCode; //異常代碼
char errMsg[40]; //異常信息
Error(int code,char *msg) //構造函數
{ errCode = code;strcpy(errMsg,msg); }
void ShowError() //顯示異常的詳細信息
{ cout << errCode <<":"<<errMsg << endl; }
}
int Div(int n)
{
if(n<=0) //檢查異常:如果n<0,則屬於異常情況
{
Error err(-1,"輸入的數據必須是正整數"); //定義異常類,並初始化
throw (err);
} //執行異常處理流程:拋出int型異常err,並退出當前函數執行
return(100/n);
}
int main()
{
int a;
cin >> a;
try //通過try保護代碼段,接收異常
{
int result = Div(a);
cout << "100 / " << a << " = "<< result << endl;
}
catch(Error e) //捕獲Error類的異常,也可以定義成異常類的引用:catch(Error &re)
{
e.ShowError(); //顯示異常信息
catch(...) //捕獲任意類型異常
{
cout<<"發生了其他異常"<<endl;
}
return 0;
}
4、try-catch異常處理機制的代碼框架
try-catch異常處理機制的語法細節:
1)語句try-catch可以多層嵌套。
2)電腦執行throw語句,將跳轉到包含該throw語句最下一層的catch子句進行異常類型匹配。
3)語句try-catch可以直接包含throw語句。執行throw語句時的跳轉目標實際上時查找最近的catch子句,也就是本函數的函數子句。
4)子句catch在處理完異常後可以繼續拋出異常。再次拋出異常的原因在於,某個異常需要經過不同層級代碼多次處理,每個層級只完成整個異常處理環節的一部分。當捕獲到的異常再次拋給上層的try-catch,此時throw語句可以不帶異常表達式。
關鍵字throw可以被用在函數定義或原型聲明中,其作用是聲明該函數可能拋出的異常列表。其語法形式為:函數類型 函數名(形參列表)throw(異常類型列表);
示例:
void fun() throw(A,B,C); //函數fun能卻只能拋出A、B或C共三種類型的異常
void fun() throw(); //函數fun不會拋出任何類型的異常
void fun(); //函數fun可能會拋出任何類型的異常