C++異常機制html
1、 概述
C++自身有着很是強的糾錯能力,發展到現在,已經創建了比較完善的異常處理機制。C++的異常狀況無非兩種,一種是語法錯誤,即程序中出現了錯誤的語句,函數,結構和類,導致編譯程序沒法進行。另外一種是運行時發生的錯誤,通常與算法有關。
關於語法錯誤,沒必要多說,寫代碼時心細一點就能夠解決。C++編譯器的報錯機制可讓咱們輕鬆地解決這些錯誤。
第二種是運行時的錯誤,常見的有文件打開失敗、數組下標溢出、系統內存不足等等。而一旦出現這些問題,引起算法失效、程序運行時無端中止等故障也是常有的。這就要求咱們在設計軟件算法時要全面。好比針對文件打開失敗的狀況,保護的方法有不少種,最簡單的就是使用「return」命令,告訴上層調用者函數執行失敗;另一種處理策略就是利用c++的異常機制,拋出異常。
2、c++異常處理機制
C++異常處理機制是一個用來有效地處理運行錯誤的很是強大且靈活的工具,它提供了更多的彈性、安全性和穩固性,克服了傳統方法所帶來的問題.
異常的拋出和處理主要使用瞭如下三個關鍵字: try、 throw 、 catch 。
拋出異常即檢測是否產生異常,在C++中,其採用throw語句來實現,若是檢測到產生異常,則拋出異常。該語句的格式爲:
throw 表達式;
若是在try語句塊的程序段中(包括在其中調用的函數)發現了異常,且拋棄了該異常,則這個異常就能夠被try語句塊後的某個catch語句所捕獲並處理,捕獲和處理的條件是被拋棄的異常的類型與catch語句的異常類型相匹配。因爲C++使用數據類型來區分不一樣的異常,所以在判斷異常時,throw語句中的表達式的值就沒有實際意義,而表達式的類型就特別重要。
try-catch語句形式以下 :ios
try c++
{ 算法
包含可能拋出異常的語句; 數組
} 安全
catch(類型名 [形參名]) // 捕獲特定類型的異常 ide
{ 函數
} 工具
catch(類型名 [形參名]) // 捕獲特定類型的異常 post
{
}
catch(...) // 三個點則表示捕獲全部類型的異常
{
}
【範例1】處理除數爲0的異常。該範例將上述除數爲0的異常能夠用try/catch語句來捕獲異常,並使用throw語句來拋出異常,從而實現異常處理,實現代碼如代碼清單1-1所示。
// 代碼清單1-1
#include<iostream.h> //包含頭文件
#include<stdlib.h>
double fuc(double x, double y) //定義函數
{
if(y==0)
{
throw y; //除數爲0,拋出異常
}
return x/y; //不然返回兩個數的商
}
void main()
{
double res;
try //定義異常
{
res=fuc(2,3);
cout<<"The result of x/y is : "<<res<<endl;
res=fuc(4,0); 出現異常,函數內部會拋出異常
}
catch(double) //捕獲並處理異常
{
cerr<<"error of dividing zero.\n";
exit(1); //異常退出程序
}
}
【範例2】自定義異常類型
// 代碼清單1-2
#include "stdafx.h"
#include<stdlib.h>
#include<crtdbg.h>
#include <iostream>
// 內存泄露檢測機制
#define _CRTDBG_MAP_ALLOC
#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
// 自定義異常類
class MyExcepction
{
public:
// 構造函數,參數爲錯誤代碼
MyExcepction(int errorId)
{
// 輸出構造函數被調用信息
std::cout << "MyExcepction is called" << std::endl;
m_errorId = errorId;
}
// 拷貝構造函數
MyExcepction( MyExcepction& myExp)
{
// 輸出拷貝構造函數被調用信息
std::cout << "copy construct is called" << std::endl;
this->m_errorId = myExp.m_errorId;
}
~MyExcepction()
{
// 輸出析構函數被調用信息
std::cout << "~MyExcepction is called" << std::endl;
}
// 獲取錯誤碼
int getErrorId()
{
return m_errorId;
}
private:
// 錯誤碼
int m_errorId;
};
int main(int argc, char* argv[])
{
// 內存泄露檢測機制
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
// 能夠改變錯誤碼,以便拋出不一樣的異常進行測試
int throwErrorCode = 110;
std::cout << " input test code :" << std::endl;
std::cin >> throwErrorCode;
try
{
if ( throwErrorCode == 110)
{
MyExcepction myStru(110);
// 拋出對象的地址 -> 由catch( MyExcepction* pMyExcepction) 捕獲
// 這裏該對象的地址拋出給catch語句,不會調用對象的拷貝構造函數
// 傳地址是提倡的作法,不會頻繁地調用該對象的構造函數或拷貝構造函數
// catch語句執行結束後,myStru會被析構掉
throw &myStru;
}
else if ( throwErrorCode == 119 )
{
MyExcepction myStru(119);
// 拋出對象,這裏會經過拷貝構造函數建立一個臨時的對象傳出給catch
// 由catch( MyExcepction myExcepction) 捕獲
// 在catch語句中會再次調用經過拷貝構造函數建立臨時對象複製這裏傳過去的對象
// throw結束後myStru會被析構掉
throw myStru;
}
else if ( throwErrorCode == 120 )
{
// 不提倡這樣的拋出方法
// 這樣作的話,若是catch( MyExcepction* pMyExcepction)中不執行delete操做則會發生內存泄露
// 由catch( MyExcepction* pMyExcepction) 捕獲
MyExcepction * pMyStru = new MyExcepction(120);
throw pMyStru;
}
else
{
// 直接建立新對象拋出
// 至關於建立了臨時的對象傳遞給了catch語句
// 由catch接收時經過拷貝構造函數再次建立臨時對象接收傳遞過去的對象
// throw結束後兩次建立的臨時對象會被析構掉
throw MyExcepction(throwErrorCode);
}
}
catch( MyExcepction* pMyExcepction)
{
// 輸出本語句被執行信息
std::cout << "執行了 catch( MyExcepction* pMyExcepction) " << std::endl;
// 輸出錯誤信息
std::cout << "error Code : " << pMyExcepction->getErrorId()<< std::endl;
// 異常拋出的新對象並不是建立在函數棧上,而是建立在專用的異常棧上,不須要進行delete
//delete pMyExcepction;
}
catch ( MyExcepction myExcepction)
{
// 輸出本語句被執行信息
std::cout << "執行了 catch ( MyExcepction myExcepction) " << std::endl;
// 輸出錯誤信息
std::cout << "error Code : " << myExcepction.getErrorId()<< std::endl;
}
catch(...)
{
// 輸出本語句被執行信息
std::cout << "執行了 catch(...) " << std::endl;
// 處理不了,從新拋出給上級
throw ;
}
// 暫停
int temp;
std::cin >> temp;
return 0;
}
3、異常的接口聲明
爲了增強程序的可讀性,使函數的用戶可以方便地知道所使用的函數會拋出哪些異常,能夠在函數的聲明中列出這個函數可能拋出的全部異常類型,例如:
void fun() throw( A,B,C,D);
這代表函數fun()可能而且只可能拋出類型(A,B,C,D)及其子類型的異常。
若是在函數的聲明中沒有包括異常的接口聲明,則此函數能夠拋出任何類型的異常,例如:
void fun();
一個不會拋出任何類型異常的函數能夠進行以下形式的聲明:
void fun() thow();
5、異常處理中須要注意的問題
1. 若是拋出的異常一直沒有函數捕獲(catch),則會一直上傳到c++運行系統那裏,致使整個程序的終止
2. 通常在異常拋出後資源能夠正常被釋放,但注意若是在類的構造函數中拋出異常,系統是不會調用它的析構函數的,處理方法是:若是在構造函數中要拋出異常,則在拋出前要記得刪除申請的資源。
3. 異常處理僅僅經過類型而不是經過值來匹配的,因此catch塊的參數能夠沒有參數名稱,只須要參數類型。
4. 函數原型中的異常說明要與實現中的異常說明一致,不然容易引發異常衝突。
5. 應該在throw語句後寫上異常對象時,throw先經過Copy構造函數構造一個新對象,再把該新對象傳遞給 catch.
那麼當異常拋出後新對象如何釋放?
異常處理機制保證:異常拋出的新對象並不是建立在函數棧上,而是建立在專用的異常棧上,所以它才能夠跨接多個函數而傳遞到上層,不然在棧清空的過程當中就會被銷燬。全部從try到throw語句之間構造起來的對象的析構函數將被自動調用。但若是一直上溯到main函數後尚未找到匹配的catch塊,那麼系統調用terminate()終止整個程序,這種狀況下不能保證全部局部對象會被正確地銷燬。
6. catch塊的參數推薦採用地址傳遞而不是值傳遞,不只能夠提升效率,還能夠利用對象的多態性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,不然,派生類的異常沒法被撲獲。
7. 編寫異常說明時,要確保派生類成員函數的異常說明和基類成員函數的異常說明一致,即派生類改寫的虛函數的異常說明至少要和對應的基類虛函數的異常說明相同,甚至更加嚴格,更特殊。
出處: http://www.cnblogs.com/armstrong-cn/archive/2011/09/02/2163553.html