18異常

惡魔布發表於2020-12-29

18異常

C語言錯誤處理方法

  1. 返回值(if…else語句判斷錯誤)
  2. errno

    linux系統呼叫與C庫函式

    返回值為-1, 並且置errno相應錯誤程式碼

  3. goto語句, 區域性跳轉
int main()
{
	char* p = (char*)malloc(10);
	if(p==NULL)
	{
		goto POS1;
	}
	else
	{}
POS1:
	//todo
	
	return 0;
}
  1. setjmp, longjmp (這種轉跳不會呼叫物件解構函式, 因為物件不能被正常清理)
    • 錯誤的丟擲點與錯誤處理點相隔的距離相對較遠
#include <setjump.h>

jmp_buf buf; //setjmp, longjmp的一個重要引數
const unsigned int ERR_CODE = 1;
double Divide(double a, double b)
{
	if(b == 0.0)
	{
		longjump(buf, ERR_CODE); //跳轉到buf儲存位置,即setjump(buf)
	}
	return a/b;
}

int main()
{
	int ret;
	ret = setjump(buf); //buf儲存位置
	if(ret == 0)
	{
		//todo
		Divide(5.0, 0.0);
	}
	else if(ret == ERR_CODE)
	{
		//todo handle error
	}
	return 0;
}
  • C語言錯誤處理方法被認為是緊耦合的,在非常靠近函式呼叫的地方編寫錯誤處理程式碼, 笨拙和難以使用

C++異常處理方法

  • try
  • catch
  • throw
try
{
	throw 1; //丟擲異常
}
catch (int)
{
	//handle error
}
  • 例子
//#include <setjump.h>

//jmp_buf buf; //setjmp, longjmp的一個重要引數
//const unsigned int ERR_CODE = 1;
double Divide(double a, double b)
{
	if(b == 0.0)
	{
		//longjump(buf, ERR_CODE); //跳轉到buf儲存位置,即setjump(buf)
		throw 1;
	}
	return a/b;
}

int main()
{
	//int ret;
	//ret = setjump(buf); //buf儲存位置
	//if(ret == 0)
	try
	{
		//todo
		Divide(5.0, 0.0);
	}
	//else if(ret == ERR_CODE)
	catch (int) //一般使用引用
	{
		//todo handle error
	}
	return 0;
}

C++異常處理優點

  • 集中精力在正常流程,在一個地方編寫一次錯誤處理程式碼
  • 錯誤不能被忽略

程式錯誤

  • 編譯錯誤,即語法錯誤
  • 執行時錯誤
    1. 不可預料的邏輯錯誤
    2. 可以預料的執行異常
    • 動態分配空間時可能不成功
    • 開啟檔案可能會失敗
    • 除法運算時分母為0
    • 整數相乘可能溢位
    • 陣列越界…

異常語法

try
{
	//try語句塊
	
	//丟擲一個類物件,會呼叫拷貝建構函式
	//同時銷燬區域性物件,稱為棧展開
	//throw <表示式>; 
}
catch (型別1 引數1) //一般使用引用
{
	//handle error
}
catch (型別n 引數n)
{
	
}

異常丟擲

  • 可以丟擲內建型別異常, 也可以丟擲自定義型別異常
  • throw丟擲一個類物件會呼叫拷貝建構函式
  • 異常發生之前建立的區域性物件會被銷燬,這一過程稱為棧展開

異常捕獲

  • 一般只捕獲一種型別的異常
  • 異常處理器的引數型別和丟擲異常的型別相同,不作型別轉換
  • …表示可以捕獲任何異常

異常傳播

  • try可以巢狀
  • 按順序尋找匹配的異常處理器,丟擲的異常被第一個型別符合的異常處理器捕獲
  • 如果內層try塊沒有找到合適的異常處理器,該異常向外傳播,到外層try塊後的catch尋找
  • 沒有被捕獲的異常將呼叫terminate函式,terminate函式預設呼叫abort終止程式的執行
  • 可以使用set_terminate函式指定terminate函式將呼叫的函式
    set_terminate(函式名)

棧展開

棧展開 : 沿著巢狀呼叫連結向上查詢,直到為異常找到一個catch子句

  • 為區域性物件呼叫解構函式
  • 解構函式應該從不丟擲異常 - 引發的異常還沒處理,又丟擲新的異常, 將會呼叫terminate函式
  • 異常與建構函式 - 建構函式可以丟擲異常,可能存在物件部分被構造,也要確保銷燬已構造的成員(記憶體洩漏, 主要是堆上物件,棧上已構造的物件會自動呼叫解構函式)

從零開始學C++之異常(三):異常與繼承、異常與指標、異常規格說明

異常與繼承

class MyExceptionD : public MyException

  • 如果異常型別為C++的類,並且該類有其基類,則應該將派生類的錯誤處理程式放在前面,基類的錯誤處理程式放在後面
  • 派生類的異常能夠被基類所捕獲, 如果把基類的放在最前面,而且不是引用的形式,如 catch (MyException e); 那麼將會被這個所catch 到,而且在構造e 的過程會有object slicing 的問題。- 相當於派生類物件自動轉化為基類物件

異常與指標

  • 丟擲指標通常是一個壞主意,因為丟擲指標要求在對應處理程式碼存在的任意地方都存在指標所指向的物件(注意此時throw丟擲時複製的是指標本身,不會去複製指標指向的內容)
  • 任何型別的指標都能被void*指標捕獲
int main( void)
{
     try
    {
         //MyExceptionD e("test exception");
         //throw &e;   //捕獲異常時,棧上物件e會被銷燬,導致指標空懸
         throw  new MyExceptionD( "test exception"); //堆上物件,不會被銷燬
    }
     /*catch (void* e) //任何型別的指標都能被void*指標捕獲 
    {
        cout<<"catch void* ..."<<endl;
        cout<<((MyExceptionD*)e)->what()<<endl;
        delete (MyExceptionD*)e; //需要手動銷燬指標,不然會導致記憶體洩漏
    }*/
     catch (MyExceptionD *e)
    {
        cout <<  "catch MyExceptionD ..." << endl;
        cout << e->what() << endl;
         delete e; //需要手動銷燬指標,不然會導致記憶體洩漏
    }
     catch (MyException &e)
    {
        cout <<  "catch MyException ..." << endl;
        cout << e.what() << endl;
    }

     return  0;
}

異常規格說明

目的 : 讓函式使用者知道該函式可能丟擲的異常有哪些。

  1. 在函式的宣告中列出這個函式可能拋擲的所有異常型別。
    void fun() throw(A,B,C,D);
  2. 無異常介面宣告,則此函式可以拋擲任何型別的異常
  3. 不拋擲任何型別異常的函式宣告如下:
    void fun() throw();

相關文章