您好,登錄后才能下訂單哦!
異常:程序在運行過程中可能產生異常(是程序運行時可預料的執行分支),如:運行時除0的情況,需要打開的外部文件不存在的情況,數組訪問越界的情況...
Bug:bug是程序中的錯誤,是不可被預期運行方式,如:野指針、堆內存結束后未釋放、選擇排序無法處理長度為0的數組...
if(判斷是否產生異常)
{
//正常代碼邏輯
}
else
{
//異常代碼邏輯
}
#include <iostream>
#include <string>
using namespace std;
double divide(double a, double b, int* valid)
{
const double delta = 0.000000000000001; //一般不要拿浮點數和0直接做比較
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
*valid = 1;
}
else
{
*valid = 0;
}
return ret;
}
int main(int argc, char *argv[])
{
int valid = 0;
double r = divide(1, 0, &valid);
if( valid )
{
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
int setjmp(jmp_buf env)
將當前上下文保存在jmp_buf結構體中
void longjmp(jmp_buf env, int val)
從jmp_buf結構體中恢復setjmp()保存的上下文
最終從setjmp函數調用點返回,返回值為val
#include <iostream>
#include <string>
#include <csetjmp>
using namespace std;
/**缺陷:setjmp()和longjmp()的引入必然涉及到全局變量,暴力跳轉導致代碼可讀性降低***/
static jmp_buf env; //定義全局變量
double divide(double a, double b)
{
const double delta = 0.000000000000001; //一般不要拿浮點數和0直接做比較
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
longjmp(env, 1);
}
return ret;
}
int main(int argc, char *argv[])
{
if( setjmp(env) == 0 )
{
double r = divide(1, 1);
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
缺陷:setjmp()和longjmp()的引入必然涉及到全局變量,暴力跳轉導致代碼可讀性降低
C++內置了異常處理的語法元素try、catch、throw
--try語句處理正常的代碼邏輯
--catch語句處理異常的情況
--C++通過throw語句拋出異常信息
try語句中的異常由對應的catch語句處理
函數在運行時拋出(throw)一個異常到函數調用的地方(try語句內部),try語句就會將異常交給對應的catch語句去處理
#include <iostream>
#include <string>
using namespace std;
/**函數在運行時拋出(throw)一個異常到函數調用的地方(try語句內部),try語句就會將異常交給對應的catch語句去處理**/
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
throw 0;
}
return ret;
}
int main(int argc, char *argv[])
{
try
{
double r = divide(1, 0);
cout << "r = " << r << endl;
}
catch(...)
{
cout << "Divided by zero..." << endl;
}
return 0;
}
--throw拋出的異常必須被catch處理
當前函數能夠處理異常,程序繼續往下執行
當前函數無法處理異常,則函數停止執行,并返回(未被處理的異常會順著函數調用棧向上傳播,直到被處理為止,否則程序將停止執行)
--不同的異常由不同的catch語句負責處理
--catch(...)用于處理所有類型的異常(只能被放在最后面)
#include <iostream>
#include <string>
using namespace std;
/**異常拋出后,至上而下將嚴格的匹配每一個catch語句處理的類型,不進行任何類型的轉換**/
void Demo1()
{
try
{
throw 'c';
}
catch(char c)
{
cout << "catch(char c)" << endl;
}
catch(short c)
{
cout << "catch(short c)" << endl;
}
catch(double c)
{
cout << "catch(double c)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
}
void Demo2()
{
throw string("D.T.Software");
}
int main(int argc, char *argv[])
{
Demo1();
try
{
Demo2();
}
catch(char* s)
{
cout << "catch(char *s)" << endl;
}
catch(const char* cs)
{
cout << "catch(const char *cs)" << endl;
}
catch(string ss)
{
cout << "catch(string ss)" << endl;
}
return 0;
}
注意:任何異常都只能被捕獲(catch)一次,異常拋出后,捕獲時至上而下將嚴格的匹配每一個catch語句處理的類型,不進行任何類型的轉換
catch中捕獲的異常可以被重新解釋拋出,catch拋出的異常需要外層的try...catch...捕獲
為什么要重新拋出異常?
實際工程中我們可以對第三方庫中拋出的異常進行捕獲、重新解釋(統一異常類型,方便代碼問題定位),然后再拋出
#include <iostream>
#include <string>
using namespace std;
void Demo()
{
try
{
try
{
throw 'c';
}
catch(int i)
{
cout << "Inner: catch(int i)" << endl;
throw i;
}
catch(...)
{
cout << "Inner: catch(...)" << endl;
throw;
}
}
catch(...)
{
cout << "Outer: catch(...)" << endl;
}
}
/*
假設: 當前的函數是第三方庫中的函數,因此,我們無法修改源代碼
函數名: void func(int i)
拋出異常的類型: int
-1 ==》 參數異常
-2 ==》 運行異常
-3 ==》 超時異常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i) //調用第三方庫函數,捕獲并重新解釋異常,然后拋出
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw "Invalid Parameter"; //捕獲異常并重新解釋并拋出
break;
case -2:
throw "Runtime Exception";
break;
case -3:
throw "Timeout Exception";
break;
}
}
}
int main(int argc, char *argv[])
{
Demo();
try
{
MyFunc(11);
}
catch(const char* cs)
{
cout << "Exception Info: " << cs << endl;
}
return 0;
}
注意:
(1)異常的類型可以是自定義類類型,對于類類型異常的匹配依舊是至上而下、嚴格匹配
(2)賦值兼容原則在異常匹配中依然適用,一般而言
--匹配子類異常的catch放在上部
--匹配父類異常的catch放在下部
#include <iostream>
#include <string>
using namespace std;
class Base
{
};
//異常的類型可以是自定義類類型
class Exception : public Base
{
int m_id;
string m_desc;
public:
Exception(int id, string desc)
{
m_id = id;
m_desc = desc;
}
int id() const
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假設: 當前的函數式第三方庫中的函數,因此,我們無法修改源代碼
函數名: void func(int i)
拋出異常的類型: int
-1 ==》 參數異常
-2 ==》 運行異常
-3 ==》 超時異常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw Exception(-1, "Invalid Parameter");
break;
case -2:
throw Exception(-2, "Runtime Exception");
break;
case -3:
throw Exception(-3, "Timeout Exception");
break;
}
}
}
int main(int argc, char *argv[])
{
try
{
MyFunc(11);
}
//在定義catch語句塊時推薦使用引用作為參數(防止拷貝構造)
// 賦值兼容原則在異常匹配中依然適用,一般而言
catch(const Exception& e) // 匹配子類異常的catch放在上部
{
cout << "Exception Info: " << endl;
cout << " ID: " << e.id() << endl;
cout << " Description: " << e.description() << endl;
}
catch(const Base& e) // 匹配父類異常的catch放在下部
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
在工程中會定義一系列的異常類,每個類代表工程中可能出現的一種異常類型
代碼復用時可能需要重新解釋不同的異常類
在定義catch語句塊時推薦使用引用作為參數(防止拷貝構造)
(1)C++標準庫中提供了實用異常類族,都是從exception類派生的,主要有兩個分支
--logic_error(常用于程序中可避免的邏輯錯誤)
--runtime_error(常用于程序中無法避免的惡性錯誤)
標準庫中的異常:
main函數中跑出異常會發生什么?如果異常不處理會傳到哪里?
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
}
};
int main()
{
static Test t;
throw 1;
return 0;
}
實驗結果證明,異常不被處理會導致程序會異常結束,并打印異常語句。
那么異常語句是哪里打印的?
如果異常無法被處理,terminate()函數會被自動調用,該函數用于結束異常,同時在terminate()函數中會調用庫函數abort()函數終止程序(abort函數使得程序執行異常并立即退出)。
C++語法支持自定義terminate()函數的實現:
(1)定義一個無返回值無參數的函數(函數類型為void(*)()類型)
a)不能拋出異常
b)必須以某種方式結束當前程序(abort/exit/…)
(2)調用set_terminate注冊自定義的terminate()函數
a)返回值為默認的terminate函數入口地址。
#include <iostream>
#include <cstdlib>
#include <exception>
using namespace std;
void my_terminate()
{
cout << "void my_terminate()" << endl;
exit(1);
}
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
}
};
int main()
{
set_terminate(my_terminate);
static Test t;
throw 1;
return 0;
}
析構函數中拋出異常會怎么樣?
#include <iostream>
#include <cstdlib>
#include <exception>
using namespace std;
void my_terminate()
{
cout << "void my_terminate()" << endl;
// exit(1);
abort(); // C++ 標準庫中terminate()函數調用的為abort直接結束程序,不會再去調用析構函數,防止析構函數中還有異常扔出
}
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
throw 2; // terminate函數是整個程序釋放資源的最后機會
// 析構函數中不能拋出異常,會導致terminate函數被多次調用,造成資源重復釋放
}
};
int main()
{
set_terminate(my_terminate);
static Test t;
throw 1;
return 0;
}
實驗結果證明在Linux這樣比較穩定的環境中析構函數中拋出異常會調用terminate()函數。貌似沒有什么問題,但對于某些嵌入式系統,可能導致系統的不穩定。
結論:
terminate函數是整個程序釋放資源的最后機會。
析構函數中不能拋出異常,會導致terminate函數被多次調用,造成資源重復釋放。
C++ 標準庫中terminate()函數調用的為abort函數,直接結束程序,不會再去調用析構函數,防止析構函數中還有異常扔出
C++語法提供了用于申明函數所拋出的異常,異常做為函數聲明的 修飾符寫函數聲明的后面。
// 可能拋出任何異常
void fun(void) ;
// 只能拋出int型異常
void fun(void) throw(int);
// 不能拋出異常
void fun(void) throw();
#include <iostream>
using namespace std;
void func() throw(int)
{
cout << "func()";
cout << endl;
throw 'c';
}
int main()
{
try
{
func();
}
catch(int)
{
cout << "catch(int)";
cout << endl;
}
catch(char)
{
cout << "catch(char)";
cout << endl;
}
return 0;
}
異常規格說明的意義:
-提示函數調用這必須做好異常處理的準備
-提示函數的維護者不要拋出其他異常
-異常規格 說明是函數接口的一部分。
函數拋出的異常不在規格說明中,全局函數unexpected()會被調用
默認的unexpected()函數會調用全局的terminate()函數
可以自定義unexpected()函數
--函數類型void(*)(void)
--能夠再次拋出異常
a)在次拋出的異常符合觸發函數的異常規格函數時,程序恢復執行
b)否則,調用terminate()函數結束程序
--調用set_unexpected()函數設置自定義的異常函數,返回值為默認的unexpected函數入口地址。
注意不是所有C++編譯器都支持這個標準行為,其中vs就不支持(會直接處理拋出的異常,盡管其不在異常規格申明中)。
#include <iostream>
#include <cstdlib>
#include <exception>
/**
函數拋出的異常不在規格說明中,全局函數unexpected()會被調用
默認的unexpected()函數會調用全局的terminate()函數
可以自定義unexpected()函數
--函數類型void(*)(void)
--能夠再次拋出異常
a)在次拋出的異常符合觸發函數的異常規格函數時,程序恢復執行
b)否則,調用terminate()函數結束程序
--調用set_unexpected()函數設置自定義的異常函數,返回值為默認的unexpected函數入口地址。
注意不是所有C++編譯器都支持這個標準行為,其中vs就不支持(會直接處理拋出的異常,盡管其不在異常規格申明中)。
**/
using namespace std;
void my_unexpected()
{
cout << "void my_unexpected()" << endl;
// exit(1);
throw 1;
}
void func() throw(int)
{
cout << "func()";
cout << endl;
throw 'c';
}
int main()
{
set_unexpected(my_unexpected);
try
{
func();
}
catch(int)
{
cout << "catch(int)";
cout << endl;
}
catch(char)
{
cout << "catch(char)";
cout << endl;
}
return 0;
}
1、try…catch用于分隔正常邏輯的代碼和異常處理代碼,可以直接將函數實現分隔為兩部分。
2、函數聲明和定義時可以直接指定拋出的異常類型,異常聲明成為函數的一部分可以提高代碼的可讀性。
#include <iostream>
#include <string>
using namespace std;
int func(int i, int j) throw(int, char)
{
if( (0 < j) && (j < 10) )
{
return (i + j);
}
else
{
throw '0';
}
}
void test(int i) try
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i)
{
cout << "Exception: " << i << endl;
}
catch(...)
{
cout << "Exception..." << endl;
}
int main(int argc, char *argv[])
{
test(5);
test(10);
return 0;
}
顯然這種寫法可讀性降低。
本文參考唐老師課程,特此鳴謝。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。