一篇文章帶你瞭解C++中的異常
異常
在c語言中,對錯誤的處理總是兩種方法:
1,使用整型的返回值表示錯誤(有時候用1表示正確,0表示錯誤;有的時候0表示正確,1表示錯誤)
2,使用errno宏(可以簡單理解為一個全局整形變量)去記錄錯誤。(如果錯誤,就將被改變的全局整形變量返回)
c++中仍然可以用上面的兩種方法,但是有缺點。
(1)返回值不統一,到底是1表示正確,還是0表示正確。
(2)返回值隻有一個,通過函數的返回值表示錯誤代碼,那麼函數就不能返回其他的值。(輸出的值到底是表示異常的值-1,還是最後在那個結果就是-1呢)
拋出異常基本操作
c++處理異常的優點:
異常處理可以帶調用跳級。
在C程序中出現瞭異常,返回值為-1。如果C直接將-1傳給B,不進行處理(也不給B報錯),那麼B收到-1返回值以後就會進行自己的處理,然後返回給A,然後A再進行自己的處理,那麼最終程序返回的值肯定是錯誤的。
所以在c++中,要求必須要處理異常。如果C處理不瞭允許拋給B處理,B處理不瞭也允許拋給A處理,如果A也處理不瞭,那麼就直接終止代碼報錯。
int myDivision(int a, int b) { if (b == 0) { throw -1;//拋出-1 } else return 1; } int main() { int a = 10; int b = 0; try { myDivision(a, b); } catch (int) { cout << "int類型異常捕獲" << endl; } return 0; }
如果拋出來的是char類型的數據(異常),那麼就需要有個char類型的接收處理代碼(catch+類型)。
除瞭int,char,double以外的拋出類型,可以用...
來接收。
catch (...) { cout << "其他類型異常捕獲" << endl; }
如果捕獲到瞭異常,但是不想處理,那麼可以繼續向上拋出異常。
int myDivision(int a, int b) { if (b == 0) { throw -1; } } void test() { int a = 10; int b = 0; try { myDivision(a, b); } catch (int) { throw; } } int main() { try { test(); } catch (int) { cout << "int類型異常捕獲" << endl; } return 0; }
自定義的異常類
註意:類名加()就是匿名對象
class MyException { public: void printError() { cout << "我自己的異常" << endl; } }; int myDivision(int a, int b) { if (b == 0) { throw MyException();//類名加()就是匿名對象,拋出的就是匿名對象。 } } int main() { int a = 10; int b = 0; try { myDivision(a, b); } catch (MyException e) { e.printError();//可以直接用這個對象來調用成員函數 } return 0; }
總結:
1,c++中如果出現異常,不像c中return -1,而是直接throw -1,然後後面再用try catch進行處理。
2,可能出現異常的地方使用try
3,如果與拋出的異常匹配的處理沒有找到,那麼運行函數terminate將被自動調用,其缺省功能調用abort終止程序。
棧解旋
從try代碼行開始 到 throw將代碼拋出去之前。所有棧上的數據會被自動的釋放掉。
釋放的順序和創建的順序是相反的。(棧:先進後出)
class Person { public: Person() { cout << "Person的默認構造調用" << endl; } ~Person() { cout << "Person的析構調用" << endl; } }; int myDivision(int a, int b) { if (b == 0) { Person p1; Person p2; throw Person();//匿名對象 } } int main() { int a = 10; int b = 0; try { myDivision(a, b); } catch (Person) { cout << "拿到Person類異常,正在處理" << endl; } return 0; }
輸出結果:
Person的默認構造調用
Person的默認構造調用
Person的默認構造調用
Person的析構調用
Person的析構調用
拿到Person類異常,正在處理
Person的析構調用
在throw之前創建瞭兩個對象,並拋出一個匿名對象。發現在拋出去之前,兩個對象就被釋放瞭。然後拋出去的對象在程序結束時候釋放。這就是棧解旋
異常接口聲明
隻允許拋出規定類型的異常。
//異常接口的聲明 void func() throw(int , double)//隻允許拋出int和double類型的異常。 { throw 3.14; } int main() { try { func(); } catch (int) { cout << "int類型異常捕獲" << endl; } catch (...) { cout << "其他類型異常捕獲" << endl; } return 0; }
throw()的意思就是不允許拋出異常。
這個代碼在VS中是不能正確執行的,都不會報錯。但是在QT和linux下是可以正確執行的。
異常變量的生命周期
class MyException { public: MyException() { cout << "MyException的默認構造調用" << endl; } MyException(const MyException&e) { cout << "MyException的拷貝構造調用" << endl; } ~MyException() { cout << "MyException的析構調用" << endl; } }; void doWork() { throw MyException();//拋出匿名對象 } int main() { try { doWork(); } catch (MyException e) { cout << "自定義異常的捕獲" << endl; } return 0; }
運行的結果:
MyException的默認構造調用
MyException的拷貝構造調用
自定義異常的捕獲
MyException的析構調用
MyException的析構調用
throw匿名對象的時候創建瞭對象,所以用默認構造。
用MyException e來接收對象的時候,是用的值來接收的,所以會調用拷貝構造函數。
然後就打印,並且將兩個對象刪除掉。
這樣效率不高,如果接收對象的時候不用值來接收,而是用引用來接收,這樣就能少調用一次的拷貝構造和一次析構函數。
catch (MyException &e) { cout << "自定義異常的捕獲" << endl; }
運行結果:
MyException的默認構造調用 自定義異常的捕獲 MyException的析構調用
還有一種方式,就是將匿名函數的地址穿進來,這樣也不需要調用析構函數。
class MyException { public: MyException() { cout << "MyException的默認構造調用" << endl; } MyException(const MyException&e) { cout << "MyException的拷貝構造調用" << endl; } ~MyException() { cout << "MyException的析構調用" << endl; } }; void doWork() { throw & MyException();//拋出匿名對象 } int main() { try { doWork(); } catch (MyException *e) { cout << "自定義異常的捕獲" << endl; } return 0; }
運行結果:(其實沒有運行成功)
MyException的默認構造調用
MyException的析構調用
自定義異常的捕獲
如果傳的是指針,那麼匿名對象很快就會釋放掉(匿名對象的特點就是執行完就釋放掉),最終得到瞭指針也沒有辦法進行操作。
但如果匿名對象在=的右邊,且左邊還給這個對象起名瞭(如同上面的傳對象,引用接收),那麼匿名對象的壽命就會延續到左邊的變量上。如果傳的是指針,給指針起名和給對象起名不一樣,所以就會釋放。
如果不想被釋放掉,還有一種方式,那就是將這個對象創建在堆區,等待著程序員自己去釋放。(不會調用析構)
void doWork() { throw new MyException();//拋出匿名對象 }
異常的多態
//異常的基類 class BaseException { public: virtual void printError() = 0;//純虛函數 }; //空指針異常 class NULLPointerException:public BaseException { public: virtual void printError() { cout << "空指針異常" << endl; } }; //越界異常 class outOfRangeException :public BaseException { public: virtual void printError() { cout << "越界異常" << endl; } }; void doWork() { //throw NULLPointerException(); throw outOfRangeException(); } int main() { try { doWork(); } catch (BaseException &e)//用父類的引用接收子類的對象 { e.printError(); } return 0; }
提供一個基類的異常類,其中有個純虛函數(有可能是虛函數),然後子類重寫。
調用的時候,用父類的引用來接收子類的對象就可以,這樣就實現瞭異常的多態。拋出的是什麼類的對象,那麼就會調用什麼類的函數。
c++的標準異常庫
標準庫中提供瞭很多的異常類,它們是通過類繼承組織起來的。
如果使用系統提供的標準異常的時候,需要調用規定的頭文件
#include <stdexcept>
std:標準 except:異常
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include<string> #include<stdexcept> class Person { public: Person(int age) { if (age < 0 || age>150) { throw out_of_range("年齡必須在0-150之間"); } } int m_age; }; int main() { try { Person p(151); } catch (out_of_range&e) { cout << e.what() << endl;//what函數是獲得字符串中的內容 } return 0; //如果使用多態:(異常子類的名字太難記,不好寫) //catch (exception &e) }
自己平時不會主動調用系統的標準異常。在寫的系統提供的異常後面加()的字符串然後在接收的時候用父類的引用接收,然後用這個引用e的what函數就可以找到這個字符串。
編寫自己的異常類
標準異常類是優先的,可以自己編寫異常類。
和上面自己寫的MyException不太一樣。給系統提供的派生類exception提供兒子(需要重寫父類的函數等)
ps:在非靜態成員函數後面加const,表示成員函數隱含傳入的this指針為const指針,決定瞭在該成員函數中,任意修改它所在的類的成員操作是不允許的。
經過考察上面的有關out_of_range的代碼可得:拋出的是out_of_range類的一個對象,接收的時候也是用引用e來接收的這個對象。然後這個引用可以調用what()的函數來返回一個字符串,這個字符串正好是創建out_of_range對象的時候待用有參函數要傳入的 字符串。
所以,自己寫的out_of_range類一定要有個有參構造,參數就是字符串,然後還有個what的重寫函數,需要返回這個字符串,這個字符串作為屬性。
ps:註意:const char*可以隱式轉換為string,但是反過來就不成立。
所以如果要使得string轉換成const char*,需要調用string中的成員函數函數
.c_str()
const char* what() const { string s; return s.c_str(); //返回的就是const char*瞭。 }
完整代碼:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include<string> #include<stdexcept> class MyOutOfRangeException:public exception//先繼承一下這個父親 { //到底要重寫什麼呢?點開exception以後,發現有兩個virtual的虛函數,一個析構,還有一個what,析構不需要重寫 //所以需要重寫what函數。 public: MyOutOfRangeException(const char* str) { //const char*可以隱式類型轉換為string 反之不可以 this->m_myerrorString = str; } //可以再重載一下這個函數,使得接收的參數改為string類型 MyOutOfRangeException(string str) { this->m_myerrorString = str; } virtual char const* what() const { return m_myerrorString.c_str();//加瞭.c_str就可以返回const char*瞭 } string m_myerrorString;//字符串屬性 }; class Person { public: Person(int age) { if (age < 0 || age>150) { throw MyOutOfRangeException("年齡必須在0-150之間");//const char* throw MyOutOfRangeException(string("年齡必須在0-150之間"));//string,返回的是string類的匿名對象 } else { this->m_age = age; } } int m_age; }; int main() { try { Person p(1000); } catch (MyOutOfRangeException e)//用exception也可以,證明創建的這個類確實是exception的子類。 { cout << e.what() << endl; } return 0; }
但是最後發現好像沒有成功的將MyOutOfRangeException手寫異常類變成exception的子類,不知道為啥。
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!