異常處理
Error
Error 譯爲錯誤,常見的 Error 有編譯時 Error 和運行時 Error。運行時的 Error 又分爲不可預料的邏輯錯誤和可以預料的運行異常。
可以預料的運行異常,常見的有,I/0,文件打開失敗,動態內存分配失敗,越界訪問等。
C Error
C 語言中錯誤的處理,通常採用返回值的方式或是置位全局變量的方式。這就存在兩個問題。如果返回值正是我們需要的數據,且返回數據同出錯數據 容錯性不高。全局變量,在多線程中易引發競爭。而且,當錯誤發生時,上級函數要出錯處理,層層上報,造成過多的出錯處理代碼,且傳遞的效率低下。
C++ 通過異常實現了返回與錯誤的處理的分離。
C和C++ 解決容錯
#include <iostream>
#include <cmath>
using namespace std;
double triangleArea(double x, double y, double z)
{
double area;
double s = (x + y + z) / 2;
if (x + y > z && y + z > x && x + z > y) //邏輯代碼
area = sqrt(s * (s - x) * (s - y) * (s - z));
else //錯誤接口
return -1.0;
return area;
}
int main()
{
int a, b, c;
float area;
while (1)
{
cin >> a >> b >> c;
if (a > 0 && b > 0 && c > 0)
{
area = triangleArea(a, b, c);
if (area == -1.0)
cout << "輸入的三角形不合法" << endl;
else
cout << "Area:" << area << endl;
}
}
return 0;
}
運行結果爲:
上面函數triangleArea中把正常邏輯代碼和錯誤碼整合到一個函數接口進行返回。
那麼C++裏面對於錯誤就會單獨處理。
#include <iostream>
#include <cmath>
using namespace std;
double triangleArea(double x, double y, double z)
{
double area;
double s = (x + y + z) / 2;
if (x + y > z && y + z > x && x + z > y)
area = sqrt(s * (s - x) * (s - y) * (s - z));
else
throw - 1.0;
return area;
}
int main()
{
int a, b, c;
float area;
try
{
while (1)
{
cin >> a >> b >> c;
if (a > 0 && b > 0 && c > 0)
{
area = triangleArea(a, b, c);
cout << "Area:" << area << endl;
}
}
}
catch (double e)
{
cout << "return " << e << endl;
cout << "輸入的三角形不合法" << endl;
}
return 0;
}
運行結果爲:
上面代碼中,如果出現一場,就拋出異常。不和功能代碼進行整合。把錯誤處理和返回分離。
分析:
1,把可能發生異常的語句放在 try 語句聲當中。try 不影響原有語句的執行流程。
2,若未發生異常,catch 子語句並不起作用。程序會流轉到 catch 子句的後面執行。
3,若 try 塊中發生異常,則通過 throw 拋出異常。throw 拋出異常後,程序立即離開本函數,轉到上一級函數。所以 triangleArea 函數中的 return 語句不會執行。
圖解說明:
4,throw 拋出數據,類型不限。既可以是基本數據類型,也可以是構造數據類型。
5,程序流轉到 main 函數以後,try 語句塊中拋出進行匹配。匹配成功,執行 catch語句,catch 語句執行完畢後。繼續執行後面的語句。
6,如無匹配,系統調用 terminate 終止程序。
#include <iostream>
#include <cmath>
using namespace std;
double triangleArea(double x, double y, double z)
{
double area;
double s = (x + y + z) / 2;
if (x + y > z && y + z > x && x + z > y)
area = sqrt(s * (s - x) * (s - y) * (s - z));
else
throw - 1;
return area;
}
int main()
{
int a, b, c;
float area;
try
{
while (1)
{
cin >> a >> b >> c;
if (a > 0 && b > 0 && c > 0)
{
area = triangleArea(a, b, c);
cout << "Area:" << area << endl;
}
}
}
catch (double e)
{
cout << "return " << e << endl;
cout << "輸入的三角形不合法" << endl;
}
return 0;
}
上面代碼異常返回值爲1,但是catch參數類型爲double 那麼直接會出現問題:
運行結果:
我們可以換一種寫法,catch參數爲…
#include <iostream>
#include <cmath>
using namespace std;
double triangleArea(double x, double y, double z)
{
double area;
double s = (x + y + z) / 2;
if (x + y > z && y + z > x && x + z > y)
area = sqrt(s * (s - x) * (s - y) * (s - z));
else
throw - 1;
return area;
}
int main()
{
int a, b, c;
float area;
try
{
while (1)
{
cin >> a >> b >> c;
if (a > 0 && b > 0 && c > 0)
{
area = triangleArea(a, b, c);
cout << "Area:" << area << endl;
}
}
}
catch (double e)
{
cout << "return " << e << endl;
cout << "輸入的三角形不合法" << endl;
}
catch (...)
{
cout << "捕獲到了未知異常" << endl;
}
return 0;
}
運行結果爲:
如果沒有任何捕獲,系統將會殺死程序運行。
異常定義
語法格式
try
{
被檢查可能拋出異常的語句
}
catch(異常信息類型 [變量名])
{
進行異常處理的語句
}
使用條例
1,被檢語句必須放在 try 塊中,否則不起作用。
2,try catch 中花括號不可省。
3,一個 try-catch 結構中,只能有一個 try 塊,catch 塊卻可以有多個。以便與不同的類型信息匹配。
try{}
catch(double){}
catch(int){}
catch(char){}
catch(float){}
4,throw 拋出的類型,既可以是系統預定義的標準類型也可以是自定義類型。從拋出到 catch 是一次複製拷貝的過程。如果有自定義類型,要考慮自定義類型的拷貝問題。
5,異常匹配,不作類型轉化。如果 catch 語句沒有匹配異常類型信息,就可以用(…)表示可以捕獲任何異常類型的信息。
catch(...)
{
cout<<"catch a unknow exception"<<endl;
}
6.try-catch 結構可以與 throw 在同一個函數中,也可以不在同一個函數中,throw拋出異常後,先在本函數內尋找與之匹配的 catch 塊,找不到與之匹配的就轉到上一層,如果上一層也沒有,則轉到更上一層的 catch 塊。如果最終找不到與之匹配的 catch 塊,系統則會調有系統函數 terminate 使程序終止。
層級管理中,最好的處理方式就是在本層內解決,一旦出現異常層層上拋,工程量很大的時候就會打破層級管理的設計,所以不常使用。
異常流程測試
在中間環節,來測試異常的流程。
#include <iostream>
using namespace std;
//通過聲明的方式,告知,調用方,如何處理
void func() throw(char)
{
throw 'a';
}
//什麼都沒有寫的情況-> 上拋
//寫點什麼,處理自己可以處理的部分 ,若無匹配上拋
void foo()
{
try
{
func();
}
catch (int i)
{
cout << "foo() catch " << i << endl;
}
catch (...) //若無匹配,寫日誌上拋
{
cout << "log throw up" << endl;
throw;
}
}
int main()
{
try {
foo();
}
catch (int i) {
cout << "main() catch int " << i << endl;
}
catch (double i) {
cout << "main() catch double " << i << endl;
}
return 0;
}
運行結果爲:
如果拋出double類型,並且有double類型異常接受。
#include <iostream>
using namespace std;
//通過聲明的方式,告知,調用方,如何處理
void func() throw(double)
{
throw 1.1;
}
//什麼都沒有寫的情況-> 上拋
//寫點什麼,處理自己可以處理的部分 ,若無匹配上拋
void foo()
{
try
{
func();
}
catch (int i)
{
cout << "foo() catch " << i << endl;
}
catch (...) //若無匹配,寫日誌上拋
{
cout << "log throw up" << endl;
throw;
}
}
int main()
{
try
{
foo();
}
catch (int i)
{
cout << "main() catch int " << i << endl;
}
catch (double i)
{
cout << "main() catch double " << i << endl;
}
return 0;
}
運行結果爲:
拋出異常聲明
(1)一個不拋擲任何類型異常的函數可以聲明爲:
void func() throw(); //推薦使用
(2)如果在函數聲明中沒有包含異常接口聲明,則函數可以拋擲任何類型的異常,
例如:
void func(); //不推薦
(3)爲了加強程序的可讀性,可以在函數聲明中列出可能拋出的所有異常類型。
例如:
void func() throw (A, B, C , D); //這個函數 func()能夠且只能拋出類型 A B C D及其子類型的異常。
(4)如果一個函數拋出了它的異常接口聲明所不允許拋出的異常,該函數默認行爲調用 terminate 函數中止程序。
代碼說明:
#include <iostream>
using namespace std;
class up {};
class down {};
void g()
{
throw 1;
}
//異常規格說明,f 函數只能拋出 up 和 down 類型的異常
void f(int i)throw(up, down) {
switch (i) {
case 1: throw up();
case 2: throw down();
}
g();
}
int main()
{
try {
// f(1);
f(4);
}
catch (...)
{
cout << "捕獲一個未知異常" << endl;
}
return 0;
}
運行結果爲:
棧自旋
unwinding
異常被拋出後,從進入 try 塊起,到異常被拋擲前,這期間在棧上的構造的所有對象,都會被自動析構。析構的順序與構造的順序相反。這一過程稱爲棧的解旋(unwinding)。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
cout << "A constructor" << endl;
}
~A()
{
cout << "~A destructor" << endl;
}
};
int divide(int x, int y)
{
A a;
if (y == 0)
throw('a');
return x / y;
}
void myDivide(int x, int y)
{
divide(x, y);
}
int main()
{
try
{
myDivide(4, 0);
}
catch (int x)
{
cout << "x" << endl;
cout << x << endl;
}
catch (double y)
{
cout << "y" << endl;
cout << y << endl;
}
catch (...)
{
cout << "no x, no y" << endl;
}
return 0;
}
運行結果爲:
我們可以看到棧上發生了析構。
我們對於代碼進行修改:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
cout << "A constructor" << endl;
}
~A()
{
cout << "~A destructor" << endl;
}
};
int divide(int x, int y)
{
A *p = new A;
if (y == 0)
throw('a');
return x / y;
}
void myDivide(int x, int y)
{
divide(x, y);
}
int main()
{
try
{
myDivide(4, 0);
}
catch (int x)
{
cout << "x" << endl;
cout << x << endl;
}
catch (double y)
{
cout << "y" << endl;
cout << y << endl;
}
catch (...)
{
cout << "no x, no y" << endl;
}
return 0;
}
運行結果爲:
我們可以看到,如果是堆上的內存不會發生析構,那麼就會導致內存泄露。
我們對於代碼進行修改:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
cout << "A constructor" << endl;
}
~A()
{
cout << "~A destructor" << endl;
}
};
int divide(int x, int y)
{
A *p = new A;
auto_ptr<A> pa(new A); //RAII 資源獲取(new)及初始化(構造)
if (y == 0)
throw('a');
return x / y;
}
void myDivide(int x, int y)
{
divide(x, y);
}
int main()
{
try
{
myDivide(4, 0);
}
catch (int x)
{
cout << "x" << endl;
cout << x << endl;
}
catch (double y)
{
cout << "y" << endl;
cout << y << endl;
}
catch (...)
{
cout << "no x, no y" << endl;
}
return 0;
}
運行結果爲:
棧自旋也就是說,在拋出異常的時候要清棧,那麼new的資源必須經過RAII包裹,否則就會泄露。
RAII in Exception
而堆上的空間,則會泄漏。利用遵循 RAII 思想的智能指針來解決。
Standard Exception
異常自定義與拋出
MyExcept
如果導致異常的因素有5個,我們不能拋出5次,所以就打包爲一個類。
#include <iostream>
using namespace std;
class MyException
{
public:
MyException()
{
cout << "MyException constructor" << endl;
}
MyException(const MyException&)
{
cout << "MyException copy constructor" << endl;
}
~MyException()
{
cout << "~MyException destructor" << endl;
}
};
int divide(int x, int y)
{
if (y == 0)
throw(MyException()); //拋出類
return x / y;
}
void myDivide(int x, int y)
{
divide(x, y);
}
int main()
{
try {
myDivide(4, 0);
}
catch (const MyException& a) { //&
cout << "catch self define myexception" << endl;
}
return 0;
}
運行結果爲:
我們可以看到一次構造和一次析構。
標準異常
標準異常分類
標準異常使用
#include <iostream>
using namespace std;
int main()
{
double* p;
for (int i = 0; i < 1000; i++)
{
p = new double[900000000];
}
return 0;
}
上面代碼會編譯會出現錯誤:
我們對於異常進行處理:
#include <iostream>
using namespace std;
int main()
{
try {
double* p;
for (int i = 0; i < 1000; i++)
{
p = new double[900000];
}
}
catch (std::bad_alloc & e) {
cout << e.what() << endl;
exit(-1);
}
return 0;
}
運行結果爲: