1. 程式人生 > >c++學習筆記(15) 異常處理

c++學習筆記(15) 異常處理

異常處理概述:

異常是用一個throw語句丟擲,同時用try-catch來捕獲,例如一個簡單的例子:

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	cout << "Enter two number: " << endl;
	int number1, number2;
	cin >> number1 >> number2;
	try
	{
		if(number2==0)   // 如果除數為零 
		  throw number1;   // throw語句丟擲異常 
	    cout << number1 << "/" << number2 << " is " << (number1/number2) << endl; 
	}
	catch (int ex)   // catch捕獲異常 ex: catch塊引數
	{
		// 異常處理 
		cout << "Excetion: an number " << ex << "can not be divided by zero" << endl;
	}
	cout << "Exception end" << endl;
	return 0;
}

C++允許throw任何型別的值。當異常被丟擲後,程式的正常執行流程被中斷。當catch塊捕獲i到異常後,就執行裡面的程式碼。

catch快塊就像一個函式,其引數與丟擲的異常值匹配,而與函式不同的是,catch塊呼叫完畢後,程式控制流程不會返回到丟擲異常的地方,而是直接執行catch塊後的語句。

異常處理的優點:

將上述的程式碼改寫為函式的形式:

#include <iostream>

using namespace std;

int quotient(int number1, int number2)
{
	if(number2==0)
	 throw number1;
	return number1/number2; 
}

int main(int argc, char *argv[])
{
	cout << "Enter two number: " << endl;
	int number1, number2;
	cin >> number1 >> number2;
	try
	{
		int result = quotient(number1, number2);
	    cout << number1 << "/" << number2 << " is " << result << endl; 
	}
	catch (int)   // catch捕獲異常 
	{
		// 異常處理 
		cout << "Excetion: an number can not be divided by zero" << endl;
	}
	cout << "Exception end" << endl;
	return 0;
}

quotient() 函式丟擲異常,呼叫者的catch塊會捕獲到這個異常。這種機制允許一個函式給他的呼叫者丟擲異常,否則函式自己必須處理這種異常,或者終止程式。例如錯誤發生時,一個被呼叫的函式,尤其是庫函式,其自身不知道如何處理異常。庫函式可以檢測到錯誤,但只有函式呼叫者才知道如何處理異常。

異常處理的思路是將錯誤檢測和異常處理分開。

異常類:

C++標準中的異常類

catch塊的引數如果是類的話,則可以傳遞更多的資訊

exception類定義在<exception>標頭檔案中,類中包含一個虛擬函式what(),可以返回異常物件的錯誤資訊

runtime_error是描述執行時錯誤的標準異常類的基類

overflow_error算術運算溢位

underflow_error溢位

logic_error描述邏輯錯誤

bad_alloc: new運算子在無法分配記憶體時丟擲的異常

bad_cast是dynamic_cast在轉換型別時發生錯誤所丟擲的異常。

invalid_argument: 描述將非法的引數傳遞給函式時丟擲的異常

out_of_range: 值超出允許範圍

length_error: 物件大小超過最大允許長度

bad_except:  描述了從未預料的異常處理程式所丟擲的異常

例如,對上面的程式碼進行修改,使用異常類。

#include <iostream>
#include <stdexcept>   // 包含異常類的標頭檔案 
using namespace std;

int quotient(int number1, int number2)
{
	if(number2==0)
	 throw runtime_error("Divisor can not be zero");  // 例項化一個runtime_error()物件 
	return number1/number2; 
}

int main(int argc, char *argv[])
{
	cout << "Enter two number: " << endl;
	int number1, number2;
	cin >> number1 >> number2;
	try
	{
		int result = quotient(number1, number2);
	    cout << number1 << "/" << number2 << " is " << result << endl; 
	}
	catch (runtime_error& ex)   // catch捕獲異常, 引數為runtime_error物件 
	{
		// 異常處理 
		cout << ex.what() << endl;
	}
	cout << "Exception end" << endl;
	return 0;
}

可以在程式中同時捕獲多個地方的異常:

#include <iostream>
#include <stdexcept>   // 包含異常類的標頭檔案 
using namespace std;

int quotient(int number1, int number2)
{
	if(number2==0)
	 throw runtime_error("Divisor can not be zero");  // 例項化一個runtime_error()物件 
	return number1/number2; 
}

double getArea(double radius)
{
	if(radius<0)
	  throw invalid_argument("Radius can not be negative");
        return 3.14*radius*radius;
}

int main(int argc, char *argv[])
{
	cout << "Enter two number: " << endl;
	int number1, number2;
	cin >> number1 >> number2;
	try
	{
		int result = quotient(number1, number2);
	    cout << number1 << "/" << number2 << " is " << result << endl; 
	}
	catch (runtime_error& ex)   // catch捕獲異常, 引數為runtime_error物件 
	{
		// 異常處理 
		cout << ex.what() << endl;
	}
	
	double radius;
	cout << "Enter the radius: " << endl;
	cin >> radius;
	try
	{
	    double area = getArea(radius); 
	    cout << "The area of the area is " << area << endl;
	}
	catch (invalid_argument& ex)
	{
		cout << "Exception: " << ex.what() << endl; 
	}
	cout << "Exception end" << endl;
	return 0;
}

自定義異常類:

C++允許定義自己的異常類。異常類與其他c++類沒有什麼差別,但是自定義的異常類應派生自exception類,這樣就能夠應用exception類中的一些公共特性(例如what()函式):

例如:定義一個派生自Geometric類的類Triangle, 在對Triangle的屬性(邊長)進行初始化和修改的時候,應該滿足三角形三條邊之間的關係,否則應該丟擲異常,可以定義一個TriangleException來描述這個異常。

TriangleException.h檔案      // 包含了類的實現,這種內聯方式實現對於簡短的函式來說效率更高

#ifndef TRIANGLEEXCEPTION_H
#define TRIANGLEEXCEPTION_H
#include <stdexcept>
using namespace std;

// 自定義異常類 TriangleException
// TriangleException類派生自logic_error 
class TriangleException: public logic_error
{
   private:     
   // 資料域 
   double side1;
   double side2;
   double side3;
   
   // 派生類中,如果沒有顯示的呼叫基類的建構函式, 
   // 則在派生類的建構函式中會預設呼叫基的無參建構函式, 
   // 因為 logic_error類沒有無參的建構函式, 
   // 所以在這裡需要顯示呼叫基類有引數的建構函式。
   // 呼叫logic_error("Invalid triangle")設定了一個錯誤資訊
   // 當異常物件呼叫what()時就會返回錯誤資訊 
   public: 
   TriangleException(double side1, double side2, double side3):logic_error("Invalid triangle") 
   {
   	   this->side1 = side1;
   	   this->side2 = side2;
   	   this->side3 = side3;
   }
   
   double getSide1() const
   {
   	   return side1; 
   } 	
   double getSide2() const
   {
   	   return side2;
   }
   double getSide3() const
   {
   	   return side3;
   }
}; 
#endif
 

對triangle類的定義:

#ifndef TRIANGLE_H
#define TRIANGLE_H
#include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h"   // 異常類標頭檔案 
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"     // 基類標頭檔案 
#include <cmath>
class Triangle: public Geometric
{
	private:
	double side1;
	double side2;
	double side3;
	bool isValid(double side1, double side2, double side3)
	{
		return (side1<side2+side3)&&(side2<side1+side3)&&(side3<side2+side1);
	}
	public:
	Triangle()
	{
		side1 = 1;
		side2 = 2;
		side3 = 3;
	}
	Triangle(double side1, double side2, double side3)
	{
		if (!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);   // 不滿足三邊關係則丟擲異常 
		this->side1 = side1;
		this->side2 = side2;
		this->side3 = side3;
	}
	double getSide1() const
	{
		return side1;
	}
	double getSide2() const
	{
		return side2;
	}
	double getSide3() const
	{
		return side3;
	}
	
	void setSide1(double side1)
	{
		if(!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);   // 不滿足三邊關係則丟擲異常 
		this->side1 = side1;
	}
	
	void setSide2(double side2)
	{
		if(!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);
		this->side2 = side2;
	}
	
	void setSide3(double side2)
	{
		if(!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);
		this->side3 = side3;
	}
	
	double getPerimeter() const
	{
		return side1 + side2 + side3;
	}
	
	double getArea() const
	{
		double s = getPerimeter()/2;
		return sqrt(s*(s-side1)*(s-side2)*(s-side3));
	} 
};
#endif 

 

main.cpp檔案

#include <iostream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h"   // 異常類標頭檔案 
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\Triangle.h"

using namespace std;

int main(int argc, char *argv[])
{
	
	try
	{
		Triangle tria(3,3,4);
		tria.setSide1(1);
		cout << "The Area is " <<tria.getArea() << endl;
		//tria.setSide1(1); 
		
	}
	catch (TriangleException& ex)
	{
		cout << ex.what() << endl;
		cout << "The sides are " << ex.getSide1() << " " << ex.getSide2() << " " << ex.getSide3() << endl; 
	}
	//displayGeometric(g1);
	//displayGeometric(circle1);    //  超型別的變數引用子型別的物件 
	//displayGeometric(rec1);
	//cout << "rec1 area is " << rec1.getArea() << endl;
	//cout << equalArea(circle1, rec1);
	//cout << "circle area is " << circle1.getArea() << endl;
	//cout << "The circle and rectangle area is equal? " << ((equalArea(circle1, rec1))?"Yes":"No") << endl;
	return 0;
	
}

執行結果:// 捕獲異常

多重異常捕獲

一個try-catch模組可能包含多個catch語句。可以處理tr語句丟擲的各種異常。

例如,對於前面三角形的例子,可以在定義一個異常類NoPositiveSideException(存在負的邊長時也丟擲異常)

NoPositiveSideException.h檔案

#ifndef NOPOSITIVESIDEEXCEPTION_H
#define NOPOSITIVESIDEEXCEPTION_H
#include <stdexcept>

using namespace std;
class NoPositiveSideException: public logic_error 
{
	private:
	double side;
	//double side2;
	//double side3;
	
	public:
	NoPositiveSideException(double side): logic_error("No-Positive side!")
	{
		this->side = side;
	}
	double getSide() 
	{
		return side;
	}
};
#endif

對Triangle類的修改:
 

#ifndef TRIANGLE_H
#define TRIANGLE_H
#include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h"   // 異常類標頭檔案 
#include "E:\back_up\code\c_plus_code\chapter15\external_file\NoPositiveSideException.h"   // 異常類標頭檔案 
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"     // 基類標頭檔案 
#include <cmath>
class Triangle: public Geometric
{
	private:
	double side1;
	double side2;
	double side3;
	bool isValid(double side1, double side2, double side3)
	{
		return (side1<side2+side3)&&(side2<side1+side3)&&(side3<side2+side1);
	}
	public:
	Triangle()
	{
		side1 = 1;
		side2 = 2;
		side3 = 3;
	}
	
	Triangle(double side1, double side2, double side3)
	{
		if(side1<=0)
		   throw NoPositiveSideException(side1);
        if(side2<=0)
           throw NoPositiveSideException(side2);
        if(side3<=0)
           throw NoPositiveSideException(side3);
		
		if (!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);   // 不滿足三邊關係則丟擲異常 
		this->side1 = side1;
		this->side2 = side2;
		this->side3 = side3;
	}
	double getSide1() const
	{
		return side1;
	}
	double getSide2() const
	{
		return side2;
	}
	double getSide3() const
	{
		return side3;
	}
	
	void setSide1(double side1)
	{
		if(side1<=0)
		   throw NoPositiveSideException(side1);
		if(!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);   // 不滿足三邊關係則丟擲異常 
		this->side1 = side1;
	}
	
	void setSide2(double side2)
	{
		if(side2<=0)
           throw NoPositiveSideException(side2);
		if(!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);
		this->side2 = side2;
	}
	
	void setSide3(double side2)
	{
		if(side3<=0)
           throw NoPositiveSideException(side3);
		if(!isValid(side1, side2, side3))
		   throw TriangleException(side1, side2, side3);
		this->side3 = side3;
	}
	
	double getPerimeter() const
	{
		return side1 + side2 + side3;
	}
	
	double getArea() const
	{
		double s = getPerimeter()/2;
		return sqrt(s*(s-side1)*(s-side2)*(s-side3));
	} 
};
#endif 

 

main.cpp檔案

#include <iostream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h"   // 異常類標頭檔案 
#include "E:\back_up\code\c_plus_code\chapter15\external_file\NoPositiveSideException.h"   // 異常類標頭檔案 
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\Triangle.h"

using namespace std;

int main(int argc, char *argv[])
{
    cout << "Enter the three sides: " << endl;
    double side1, side2, side3;
    cin >> side1 >> side2 >> side3;
	try
	{	
		Triangle tria(side1, side2, side3);
		//tria.setSide1(1);
		cout << "The Area is " <<tria.getArea() << endl;
		//tria.setSide1(1); 
		
	}
	catch (TriangleException& ex)  //多重異常捕獲
	{
		cout << ex.what() << endl;
		cout << "The sides are " << ex.getSide1() << " " << ex.getSide2() << " " << ex.getSide3() << endl; 
	}
	catch (NoPositiveSideException& ex)   // 多重異常捕獲
	{
		cout << ex.what() << endl;
		cout << "The side " << ex.getSide() << " is negative" << endl;
	}

	return 0;
	
}

多個不同的異常類可以派生自同一個基類,如果catch的引數是基類的異常物件,則它能夠捕獲所有派生類的異常物件。還有catch模組的次序也很重要!派生類的catch在前,基類的catch在後

注:

catch的引數可以為(...),這同樣的catch能捕獲所有型別的異常。這種catch應放在所有的catch之後,作為預設異常處理程式,捕獲所有沒有被之前catch模組所捕獲的異常。

異常的傳播

在 try語句中發生異常的時候,c++會由前到後依次檢查每個catch模組,檢查異常物件是否與catch模組引數的型別相匹配。

重丟擲異常:
一個異常被捕獲後,他可以被重新丟擲給函式的呼叫者。

#include <iostream>
#include <stdexcept>   // 包含異常類的標頭檔案 
using namespace std;

void f1()
{
	try
	{
		throw runtime_error("Exception in f1"); 
	}
	catch (exception& ex)
	{
		cout << ex.what() << endl;
		cout << "Exception caught in f1" << endl;
		throw;    //  重丟擲異常runtime_error() 
	}
}

int main(int argc, char *argv[])
{
    try
    {
    	f1();   // f1內部丟擲異常,處理後再重丟擲異常 
    }
    catch (exception& ex)   // 捕獲被重新丟擲的異常
    {
	cout << "Exception caught in main" << endl;
	cout << ex.what() << endl;
    } 
    return 0;
}

執行結果:

異常說明:

可以在函式的頭部宣告這個函式可能丟擲的異常型別有哪些:

例如:

void f1() throw(runtime_error, logic_error)   // 異常說明,函式可能會丟擲那些異常類,throw(ExceptionList)
{
	try
	{
		throw runtime_error("Exception in f1"); 
		throw logic_error("Logic error");
	}
}

throw()稱為空異常說明,放置於函式頭後,說明函式不能丟擲任何異常。

異常型別列表中如果有bad_exception,則函式丟擲一列表中未定義的異常時,會丟擲一個bad_exception異常,如果列表中沒有bad_exception,發生這種情況時程式會終止。

------------------------------------------------------end---------------------------------------------------------