中國大學MOOC程序設計與算法(三):C++ 面向對象程序設計 第十週 C++11新特性和C++高級主題 筆記 之 異常處理

第十週 C++11新特性和C++高級主題
1.C++11新特性(一)
2.C++11新特性(二)
3.強制類型轉換
4.異常處理

4.異常處理,課程最後一節

程序運行發生異常

程序運行中總難免發生錯誤
(1)數組元素的下標超界、訪問NULL指針
(2)除數爲0
(3)動態內存分配new需要的存儲空間太大
……
引起這些異常情況的原因可能是:
(1)代碼質量不高,存在BUG
(2)輸入數據不符合要求
(3)程序的算法設計時考慮不周到
…….
發生異常怎麼辦
(1)不只是簡單地終止程序運行
(2)能夠反饋異常情況的信息:哪一段代碼發生的、什麼異常
(3)能夠對程序運行中已發生的事情做些處理:取消對輸入文件的改動、釋放已經申請的系統資源
……

通常的做法是:在預計會發生異常的地方,加入相應的代碼,但這種做法並不總是適用的
……//對文件A進行了相關的操作。
fun(arg, ……);//可能發生異常
……
調用者該如何知道fun(arg, …)是否發生異常?沒有發生異常,可以繼續執行;發生異常,應該在結束程序運行前還原對文件A的操作。但是,
fun(arg, …)是別人已經開發好的代碼
fun(arg, …)的編寫者不知道其他人會如何使用這個函數
fun(arg, …)會出現在表達式中,通過返回值的方式區分是否發生異常,不符合編寫程序的習慣,而且可能發生多種異常,通過返回值判斷也很麻煩。
那麼,需要一種手段:(1)把異常與函數的接口分開,並且能夠區分不同的異常;(2)在函數體外捕獲所發生的異常,並提供更多的異常信息.

異常處理

一個函數運行期間可能產生異常。在函數內部對異常進行處理未必合適。因爲函數設計者無法知道函數調用者希望如何處理異常。
告知函數調用者發生了異常,讓函數調用者處理比較好
用函數返回值告知異常不方便

用try、catch進行異常處理

try塊包含有可能產生異常的語句,如果遇到異常就用throw拋出異常,用類型表示異常類型。一旦遇到異常,throw拋出後就跳出try塊,進入catch塊。一個try塊後可以跟很多個catch塊,但是catch塊只有一個會被真的執行,因爲遇到異常就結束try塊了,異常只有一個。如果try塊沒有拋出異常,就不會進入任何catch塊。catch塊捕獲任何類型的異常,參數匹配try塊中throw出的數據類型,catch(…)捕獲任何類型的異常,可以作爲最後一個catch塊,防止有異常類型沒有被捕獲。

#include <iostream>
using namespace std;
int main()
{
	double m ,n;
	cin >> m >> n;
	try {
		cout << "before dividing." << endl;
		if( n == 0)
			throw -1; //拋出int類型異常
		else
			cout << m / n << endl;
		cout << "after dividing." << endl;
	}
	catch(double d) {
		cout << "catch(double) " << d << endl;
	}
	catch(int e) {
		cout << "catch(int) " << e << endl;
	}
	cout << "finished" << endl;
	return 0;
}
程序運行結果如下:
9 6↙
before dividing.
1.5
after dividing.
finished
#include <iostream>
using namespace std;
int main()
{
	double m ,n;
	cin >> m >> n;
	try {
		cout << "before dividing." << endl;
		if( n == 0)
			throw -1; //拋出整型異常
		else if( m == 0 )
			throw -1.0; //拋出double型異常
		else
			cout << m / n << endl;
		cout << "after dividing." << endl;
	}
	catch(double d) {
		cout << "catch(double) " << d << endl;
	}
	catch(...) {
		cout << "catch(...) " << endl;
	}
	cout << "finished" << endl;
	return 0;
}
程序運行結果:
9 0↙
before dividing.
catch(...)
finished

0 6↙
before dividing.
catch(double) -1
finished

異常的再拋出

如果一個函數在執行的過程中,拋出的異常在本函數內就被catch塊捕獲並處理了,那麼該異常就不會拋給這個函數的調用者(也稱“上一層的函數”);如果異常在本函數中沒被處理,就會被拋給上一層的函數。

#include <iostream>
#include <string>
using namespace std;
class CException
{
	public :
		string msg;
		CException(string s):msg(s) { }
};
double Devide(double x, double y)
{
	if(y == 0)
		throw CException("devided by zero");//這個異常就要被返回到上一層函數,讓程序員看到
	cout << "in Devide" << endl;
	return x / y;
}
int CountTax(int salary)
{
	try {
		if( salary < 0 )
			throw -1;
		cout << "counting tax" << endl;
	}
	catch (int ) {//異常在函數內部就被處理掉了
		cout << "salary < 0" << endl;
	}
	cout << "tax counted" << endl;
	return salary * 0.15;
}
int main()
{
	double f = 1.2;
	try {
		CountTax(-1);
		f = Devide(3,0);//Devide函數內部遇到異常,直接跳出,賦值語句不會被執行
		cout << "end of try block" << endl;
	}
	catch(CException e) {
		cout << e.msg << endl;
	}
	cout << "f=" << f << endl;
	cout << "finished" << endl;
	return 0;
}
輸出結果:
salary < 0
tax counted
devided by zero
f=1.2
finished

C++標準異常類

C++標準庫中有一些類代表異常,這些類都是從exception類派生而來

bad_cast
在用 dynamic_cast進行從多態基類對象(或引用),到派生類的引用的強制類型轉換時,如果轉換是不安全的,則會拋出此異常。

#include <iostream>
#include <stdexcept>
#include <typeinfo>
using namespace std;
class Base
{
	virtual void func(){}
};
class Derived : public Base
{
	public:
		void Print() { }
};
void PrintObj( Base & b)
{
	try {
		Derived & rd = dynamic_cast<Derived&>(b);//此轉換若不安全,會拋出bad_cast異常
		rd.Print();
	}
	catch (bad_cast& e) {
		cerr << e.what() << endl;
	}
}
int main ()
{
	Base b;
	PrintObj(b);
	return 0;
}
輸出結果:
Bad dynamic_cast!

bad_alloc
在用new運算符進行動態內存分配時,如果沒有足夠的內存,則會引發此異常。

#include <iostream>
#include <stdexcept>
using namespace std;
int main ()
{
	try {
		char * p = new char[0x7fffffff];//無法分配這麼多空間,會拋出異常
	}
	catch (bad_alloc & e) {
		cerr << e.what() << endl;
	}
	return 0;
}
輸出結果:
bad allocation

out_of_range
用vector或string的at成員函數根據下標訪問元素時,如果下標越界,就會拋出此異常。[]也可以根據下標訪問元素,速度比at成員函數快,但是遇到越界時不會拋出異常。例如:

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
int main ()
{
	vector<int> v(10);
	try {
		v.at(100)=100; //拋出out_of_range異常
	}
	catch (out_of_range& e) {
		cerr << e.what() << endl;
	}
	string s = "hello";
	try {
		char c = s.at(100); //拋出out_of_range異常
	}
	catch (out_of_range& e) {
		cerr << e.what() << endl;
	}
	return 0;
}
輸出結果:
invalid vector<T> subscript
invalid string position

運行時類型檢查

C++運算符typeid是單目運算符,可以在程序運行過程中獲取一個表達式的值的類型。typeid運算的返回值是一個type_info類的對象,裏面包含了類型的信息。
typeid和type_info用法示例

#include <iostream>
#include <typeinfo> //要使用typeinfo,需要此頭文件
using namespace std;
struct Base { }; //非多態基類
struct Derived : Base { };
struct Poly_Base {virtual void Func(){ } }; //多態基類
struct Poly_Derived: Poly_Base { };
int main()
{
	//基本類型
	long i; int * p = NULL;
	cout << "1) int is: " << typeid(int).name() << endl;
	//輸出 1) int is: int
	cout << "2) i is: " << typeid(i).name() << endl;
	//輸出 2) i is: long
	cout << "3) p is: " << typeid(p).name() << endl;
	//輸出 3) p is: int *
	cout << "4) *p is: " << typeid(*p).name() << endl ;
	//輸出 4) *p is: int
	//非多態類型
	Derived derived;
	Base* pbase = &derived;
	cout << "5) derived is: " << typeid(derived).name() << endl;
	//輸出 5) derived is: struct Derived
	cout << "6) *pbase is: " << typeid(*pbase).name() << endl;
	//輸出 6) *pbase is: struct Base
	cout << "7) " << (typeid(derived)==typeid(*pbase) ) << endl;
	//輸出 7) 0
	//多態類型
	Poly_Derived polyderived;
	Poly_Base* ppolybase = &polyderived;
	cout << "8) polyderived is: " << typeid(polyderived).name() << endl;
	//輸出 8) polyderived is: struct Poly_Derived
	cout << "9) *ppolybase is: " << typeid(*ppolybase).name() << endl;
	//輸出 9) *ppolybase is: struct Poly_Derived
	cout << "10) " << (typeid(polyderived)!=typeid(*ppolybase) ) << endl;
	//輸出 10) 0
}

課程全部結束!!!!!!!!!!!!!!

發佈了53 篇原創文章 · 獲贊 4 · 訪問量 1984
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章