C++ throw關(guān)鍵字實現(xiàn)拋出異常和異常規(guī)范
在《C++異常入門》一節(jié)中,我們講到了 C++ 異常處理的流程,具體為:
拋出(Throw)--> 檢測(Try) --> 捕獲(Catch)
異常必須顯式地拋出,才能被檢測和捕獲到;如果沒有顯式的拋出,即使有異常也檢測不到。
在 C++ 中,我們使用 throw 關(guān)鍵字來顯式地拋出異常,它的用法為:
throw exceptionData;
exceptionData 是“異常數(shù)據(jù)”的意思,它可以包含任意的信息,完全有程序員決定。exceptionData 可以是 int、float、bool 等基本類型,也可以是指針、數(shù)組、字符串、結(jié)構(gòu)體、類等聚合類型,請看下面的例子:
char str[] = "http://c.biancheng.net"; char *pstr = str; class Base{}; Base obj; throw 100; //int 類型 throw str; //數(shù)組類型 throw pstr; //指針類型 throw obj; //對象類型
一個動態(tài)數(shù)組的例子
C/C++ 規(guī)定,數(shù)組一旦定義后,它的長度就不能改變了;換句話說,數(shù)組容量不能動態(tài)地增大或者減小。這樣的數(shù)組稱為靜態(tài)數(shù)組(Static array)。靜態(tài)數(shù)組有時候會給編碼代碼不便,我們可以通過自定義的 Array 類來實現(xiàn)動態(tài)數(shù)組(Dynamic array)。所謂動態(tài)數(shù)組,是指數(shù)組容量能夠在使用的過程中隨時增大或減小。
下面這段代碼雖然有點長,但它是一個典型的使用異常的場景,請大家耐心閱讀。
#include <iostream> #include <cstdlib> using namespace std; //自定義的異常類型 class OutOfRange{ public: OutOfRange(): m_flag(1){ }; OutOfRange(int len, int index): m_len(len), m_index(index), m_flag(2){ } public: void what() const; //獲取具體的錯誤信息 private: int m_flag; //不同的flag表示不同的錯誤 int m_len; //當(dāng)前數(shù)組的長度 int m_index; //當(dāng)前使用的數(shù)組下標(biāo) }; void OutOfRange::what() const { if(m_flag == 1){ cout<<"Error: empty array, no elements to pop."<<endl; }else if(m_flag == 2){ cout<<"Error: out of range( array length "<<m_len<<", access index "<<m_index<<" )"<<endl; }else{ cout<<"Unknown exception."<<endl; } } //實現(xiàn)動態(tài)數(shù)組 class Array{ public: Array(); ~Array(){ free(m_p); }; public: int operator[](int i) const; //獲取數(shù)組元素 int push(int ele); //在末尾插入數(shù)組元素 int pop(); //在末尾刪除數(shù)組元素 int length() const{ return m_len; }; //獲取數(shù)組長度 private: int m_len; //數(shù)組長度 int m_capacity; //當(dāng)前的內(nèi)存能容納多少個元素 int *m_p; //內(nèi)存指針 private: static const int m_stepSize = 50; //每次擴容的步長 }; Array::Array(){ m_p = (int*)malloc( sizeof(int) * m_stepSize ); m_capacity = m_stepSize; m_len = 0; } int Array::operator[](int index) const { if( index<0 || index>=m_len ){ //判斷是否越界 throw OutOfRange(m_len, index); //拋出異常(創(chuàng)建一個匿名對象) } return *(m_p + index); } int Array::push(int ele){ if(m_len >= m_capacity){ //如果容量不足就擴容 m_capacity += m_stepSize; m_p = (int*)realloc( m_p, sizeof(int) * m_capacity ); //擴容 } *(m_p + m_len) = ele; m_len++; return m_len-1; } int Array::pop(){ if(m_len == 0){ throw OutOfRange(); //拋出異常(創(chuàng)建一個匿名對象) } m_len--; return *(m_p + m_len); } //打印數(shù)組元素 void printArray(Array &arr){ int len = arr.length(); //判斷數(shù)組是否為空 if(len == 0){ cout<<"Empty array! No elements to print."<<endl; return; } for(int i=0; i<len; i++){ if(i == len-1){ cout<<arr[i]<<endl; }else{ cout<<arr[i]<<", "; } } } int main(){ Array nums; //向數(shù)組中添加十個元素 for(int i=0; i<10; i++){ nums.push(i); } printArray(nums); //嘗試訪問第20個元素 try{ cout<<nums[20]<<endl; }catch(OutOfRange &e){ e.what(); } //嘗試彈出20個元素 try{ for(int i=0; i<20; i++){ nums.pop(); } }catch(OutOfRange &e){ e.what(); } printArray(nums); return 0; }
運行結(jié)果:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Error: out of range( array length 10, access index 20 )
Error: empty array, no elements to pop.
Empty array! No elements to print.
Array 類實現(xiàn)了動態(tài)數(shù)組,它的主要思路是:在創(chuàng)建對象時預(yù)先分配出一定長度的內(nèi)存(通過 malloc() 分配),內(nèi)存不夠用時就再擴展內(nèi)存(通過 realloc() 重新分配)。Array 數(shù)組只能在尾部一個一個地插入(通過 push() 插入)或刪除(通過 pop() 刪除)元素。
我們通過重載過的[ ]運算符來訪問數(shù)組元素,如果下標(biāo)過小或過大,就會拋出異常(第53行代碼);在拋出異常的同時,我們還記錄了當(dāng)前數(shù)組的長度和要訪問的下標(biāo)。
在使用 pop() 刪除數(shù)組元素時,如果當(dāng)前數(shù)組為空,也會拋出錯誤。
throw 用作異常規(guī)范
throw 關(guān)鍵字除了可以用在函數(shù)體中拋出異常,還可以用在函數(shù)頭和函數(shù)體之間,指明當(dāng)前函數(shù)能夠拋出的異常類型,這稱為異常規(guī)范(Exception specification),有些教程也稱為異常指示符或異常列表。請看下面的例子:
double func (char param) throw (int);
這條語句聲明了一個名為 func 的函數(shù),它的返回值類型為 double,有一個 char 類型的參數(shù),并且只能拋出 int 類型的異常。如果拋出其他類型的異常,try 將無法捕獲,只能終止程序。
如果函數(shù)會拋出多種類型的異常,那么可以用逗號隔開:
double func (char param) throw (int, char, exception);
如果函數(shù)不會拋出任何異常,那么( )中什么也不寫:
double func (char param) throw ();
如此,func() 函數(shù)就不能拋出任何類型的異常了,即使拋出了,try 也檢測不到。
1) 虛函數(shù)中的異常規(guī)范
C++ 規(guī)定,派生類虛函數(shù)的異常規(guī)范必須與基類虛函數(shù)的異常規(guī)范一樣嚴(yán)格,或者更嚴(yán)格。只有這樣,當(dāng)通過基類指針(或者引用)調(diào)用派生類虛函數(shù)時,才能保證不違背基類成員函數(shù)的異常規(guī)范。請看下面的例子:
class Base{ public: virtual int fun1(int) throw(); virtual int fun2(int) throw(int); virtual string fun3() throw(int, string); }; class Derived:public Base{ public: int fun1(int) throw(int); //錯!異常規(guī)范不如 throw() 嚴(yán)格 int fun2(int) throw(int); //對!有相同的異常規(guī)范 string fun3() throw(string); //對!異常規(guī)范比 throw(int,string) 更嚴(yán)格 }
2) 異常規(guī)范與函數(shù)定義和函數(shù)聲明
C++ 規(guī)定,異常規(guī)范在函數(shù)聲明和函數(shù)定義中必須同時指明,并且要嚴(yán)格保持一致,不能更加嚴(yán)格或者更加寬松。
請看下面的幾組函數(shù):
//錯!定義中有異常規(guī)范,聲明中沒有 void func1(); void func1() throw(int) { } //錯!定義和聲明中的異常規(guī)范不一致 void func2() throw(int); void func2() throw(int, bool) { } //對!定義和聲明中的異常規(guī)范嚴(yán)格一致 void func3() throw(float, char*); void func3() throw(float, char*) { }
請拋棄異常規(guī)范,不要再使用它
異常規(guī)范的初衷是好的,它希望讓程序員看到函數(shù)的定義或聲明后,立馬就知道該函數(shù)會拋出什么類型的異常,這樣程序員就可以使用 try-catch 來捕獲了。如果沒有異常規(guī)范,程序員必須閱讀函數(shù)源碼才能知道函數(shù)會拋出什么異常。
不過這有時候也不容易做到。例如,func_outer() 函數(shù)可能不會引發(fā)異常,但它調(diào)用了另外一個函數(shù) func_inner(),這個函數(shù)可能會引發(fā)異常。再如,您編寫的函數(shù)調(diào)用了老式的庫函數(shù),此時不會引發(fā)異常,但是庫更新以后這個函數(shù)卻引發(fā)了異常??傊惓R?guī)范的初衷實現(xiàn)起來有點困難,所以大家達(dá)成的一致意見是,最好不要使用異常規(guī)范。
異常規(guī)范是 C++98 新增的一項功能,但是后來的 C++11 已經(jīng)將它拋棄了,不再建議使用。
另外,各個編譯器對異常規(guī)范的支持也不一樣,請看下面的代碼:
#include <iostream> #include <string> #include <exception> using namespace std; void func()throw(char*, exception){ throw 100; cout<<"[1]This statement will not be executed."<<endl; } int main(){ try{ func(); }catch(int){ cout<<"Exception type: int"<<endl; } return 0; }
在 GCC 下,這段代碼運行到第 7 行時程序會崩潰。雖然 func() 函數(shù)中發(fā)生了異常,但是由于 throw 限制了函數(shù)只能拋出 char*、exception 類型的異常,所以 try-catch 將捕獲不到異常,只能交給系統(tǒng)處理,終止程序。
在 Visual C++ 下,輸出結(jié)果為Exception type: int,這說明異常被成功捕獲了。在 Visual C++ 中使用異常規(guī)范雖然沒有語法錯誤,但是也沒有任何效果,Visual C++ 會直接忽略異常規(guī)范的限制,函數(shù)可以拋出任何類型的異常。
到此這篇關(guān)于C++ throw關(guān)鍵字實現(xiàn)拋出異常和異常規(guī)范的文章就介紹到這了,更多相關(guān)C++ throw關(guān)鍵字 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C語言內(nèi)核中的鏈表與結(jié)構(gòu)體
Windows內(nèi)核中是無法使用vector容器等數(shù)據(jù)結(jié)構(gòu)的,當(dāng)我們需要保存一個結(jié)構(gòu)體數(shù)組時,就需要使用內(nèi)核中提供的專用鏈表結(jié)構(gòu)。本文分享了幾個內(nèi)核中使用鏈表存儲多個結(jié)構(gòu)體的通用案例,希望對你有所幫助2022-09-09C++實現(xiàn)八個常用的排序算法 插入排序、冒泡排序、選擇排序、希爾排序等
這篇文章主要介紹了C++如何實現(xiàn)八個常用的排序算法:插入排序、冒泡排序、選擇排序、希爾排序 、快速排序、歸并排序、堆排序和LST基數(shù)排序,需要的朋友可以參考下2015-07-07C++中putchar與getchar函數(shù)的細(xì)節(jié)及運用
C語言提供putchar函數(shù),用于給終端輸出一個字符;getchar函數(shù),可以從終端接收用戶輸入的一個字符,本文給大家分享C++中putchar與getchar函數(shù)的細(xì)節(jié)及運用,感興趣的朋友跟隨小編一起看看吧2021-07-07C語言編程中從密碼文件獲取數(shù)據(jù)的函數(shù)總結(jié)
這篇文章主要介紹了C語言編程中從密碼文件獲取數(shù)據(jù)的函數(shù)總結(jié),包括getpw()函數(shù)和getpwnam()函數(shù)以及getpwuid()函數(shù),需要的朋友可以參考下2015-08-08