C++異常處理,Error,C和C++ 解決容錯,棧自旋,Standard Exception【C++】(zzd)

異常處理

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;
}

運行結果爲:

運行結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章