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