欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++中異常的深度解析

 更新時間:2025年03月27日 09:34:42   作者:源博不太 “ 淵博 ”  
異常處理機制允許程序中獨立開發(fā)部分能夠在運行時就出現(xiàn)的問題進行通信并做出相應的處理,這篇文章主要介紹了C++中異常的深度解析,需要的朋友可以參考下

1 異常的概念及使用

1.1 異常的概念

1>.異常處理機制允許程序中獨立開發(fā)部分能夠在運行時就出現(xiàn)的問題進行通信并做出相應的處理,異常使得我們能夠?qū)栴}的檢測與解決問題的過程分開,程序的一部分負責檢測問題的出現(xiàn),然后解決問題的任務傳遞給出現(xiàn)的另一部分,檢測環(huán)節(jié)無須知道問題的處理模塊的所有細節(jié)。

2>.C語言主要是通過錯誤碼的形式處理錯誤,錯誤碼本質(zhì)上就是對錯誤信息進行分類編號,拿到錯誤碼以后還需要我們自己去查詢錯誤信息,比較麻煩。異常時會拋出一個對象,這個對象可以涵蓋更全面的各種信息。

1.2 異常的拋出和捕獲

1>.程序出現(xiàn)問題時,我們通過拋出(throw)一個對象來引發(fā)一個異常,該對象的類型以及當前的調(diào)用鏈決定了改由哪個catch的處理代碼來處理異常。

2>.被選中的處理代碼是調(diào)用鏈中與該類型匹配且離拋出異常的位置最近的那一個catch的處理代碼。根據(jù)拋出對象的類型與內(nèi)容,程序的拋出異常部分要告知異常處理部分到底發(fā)生了什么錯誤。

3>.當throw執(zhí)行時,throw后面的語句將不再被執(zhí)行。程序的執(zhí)行從throw位置會跳到與之匹配的catch模塊,catch可能是同一個函數(shù)中的一個局部catch模塊,也可能是調(diào)用鏈中的另一個函數(shù)中的catch模塊,控制權(quán)從throw位置轉(zhuǎn)移到了catch模塊的位置。這里還有兩個重要的含義:1.沿著調(diào)用鏈的函數(shù)可能會提早推出;2.一旦程序開始執(zhí)行異常處理程序,沿著調(diào)用鏈創(chuàng)建的對象都將會自動被編譯器銷毀。

4>.拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一個局部對象,所以會生成一個拷貝對象,這個拷貝的對象會在catch模塊結(jié)束后就被銷毀了。(這里的處理類似于函數(shù)的傳值返回)

5>.在C++的異常處理過程中,我們常常選擇使用try-catch去處理異常,我們這里就先來講解一下這個try-catch:1.try:表示將有可能出現(xiàn)異常的代碼書寫在try代碼塊中;2.catch:try不能單獨使用,必須結(jié)合catch / finally / catch-finally(這里try結(jié)合catch),catch也不能單獨使用,必須結(jié)合try一起用。

int Divide(int a, int b)
{
	try
	{
		if (b == 0)//如果b等于0,就拋異常。
		{
			string s("Divide by zero condition!");
			throw s;//這里會將類型為string的對象s拋出去,去找這條調(diào)用鏈中與s這個對象類型匹配且離拋出異常的哪個位置的那一個catch代碼塊(拋出的并不是s對象,而是s這個異常對象的一個拷貝對象)。
		}
		else
		{
			return a / b;
		}
	}
	catch (int errid)//catch這個代碼塊接收的是int類型的一個對象。
	{
		cout << errid << endl;
	}
}
void Func(int a, int b)
{
	try
	{
		cout << Divide(a, b) << endl;
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
}
int main()
{
	int a = 0, b = 0;
	cin >> a >> b;
	try
	{
		Func(a, b);
	}
	catch (const string* errmsg)//catch接收的是一個string類型的對象。
	{
	    cout << errmsg << endl;
    }
	return 0;
}//我們開始運行程序,輸入兩個變量分別為10和0,在main函數(shù)中進入try代碼塊中,去調(diào)用Func這個函數(shù),進入Func這個局部棧幀中,又進入到try代碼中,再去調(diào)用Divide函數(shù),首先,又去進入到try代碼塊中,由于b==0,會進入到if語句中去執(zhí)行代碼,通過throw來將s對象跑出來引發(fā)異常,編譯器這里會順著調(diào)用鏈去找接收string類型對象的catch模塊,首先找到第15這行代碼的catch模塊(因為離throw的位置最近),類型不符合,再順著調(diào)用鏈去找,找到第26這行代碼的catch模塊,類型又不符合,再找到第39這行代碼的catch模塊,OK了,類型符合,就是errmsg這個對象接收到了Divide函數(shù)中跑出來的哪個對象s,既然是第39這行代碼的catch模塊接收了,那么程序的執(zhí)行就從拋出的位置跳到了第39這行代碼的catch模塊這里來了。
//當我們在Divide函數(shù)中拋出s對象時,那么第7這句代碼之后的語句將不會再被執(zhí)行(僅限于Divide這個棧幀中),而且Func函數(shù)在這里其實是提早退出了的,F(xiàn)unc這個函數(shù)中,如果在調(diào)用了這個函數(shù)之前多余開辟了空間的話,那么編譯器在這里會自動地將Func函數(shù)中開辟的那塊空間給銷毀掉。

上述函數(shù)的調(diào)用鏈:

1.3 棧展開

1>.拋出異常后,程序暫停當前函數(shù)的執(zhí)行,開始尋找與之匹配的catch子句,首先檢查throw本身是否在try模塊的內(nèi)部,如果在的話則查找匹配的那個catch模塊,如果有匹配的,則跳到那個與之匹配的catch模塊的那個地方去進行處理。

2>.如果當前所在的這個函數(shù)中沒有try/catch,或者有try/catch子句但是類型不匹配,則退出當前函數(shù),進行在外層調(diào)用函數(shù)鏈中去查找,上述查找的catch模塊的過程被稱之為是棧展開。

3>.如果我們到達main函數(shù)的棧幀,并且依舊沒有找到與之匹配的catch模塊,那么程序在這里會自動去調(diào)用標準庫中的terminate這個函數(shù)去終止程序,簡單來說就是報錯。

4>.如果找到匹配的catch模塊去處理后,catch模塊中的以及后續(xù)的代碼則會進行執(zhí)行。

上圖就是一個棧展開的過程。

1.4 查找匹配的處理代碼

1>.一般情況下拋儲對象和catch接收的那個對象的類型是完全匹配的,如果有多個類型匹配的catch子句,那么就選擇離他位置更近的那個catch子句。

2>.但是也有一些例外,允許從非常量向常量的類型準換,也就是權(quán)限縮??;允許數(shù)組轉(zhuǎn)換成指向數(shù)組元素類型的指針,函數(shù)被轉(zhuǎn)換成指向函數(shù)的指針;允許從派生類向基類類型的轉(zhuǎn)換,這一點非常實用,實際中繼承體系基本都是用這個方式去設計的。

3>.如果到main函數(shù)中,異常人就沒有被匹配的話就會被終止程序,不是發(fā)生嚴重錯誤的情況下,我們是不期望程序最終的,所以一般的main函數(shù)中在最后都會使用catch(...),它可以捕獲任意類型的異常,但是我們是不知道異常的錯誤是什么。注:一個try模塊我們可以搭配多個catch模塊。

//由于時間等等各種原因,我們這里就不一一為大家展示匹配的過程代碼了,我們接下來就來模擬設計一個繼承的匹配機制。
class person
{
public:
	person(const string& name)
		:_name(name)
	{
	}
protected:
	string _name;
};
class student :public person
{
public:
	student(const string& name, int id)
		:person(name)
		, _id(id)
	{
	}
private:
	int _id;
};
class teacher :public person
{
public:
	teacher(const string& name, int teach)
		:person(name)
		, _teach(teach)
	{
	}
private:
	int _teach;
};
void Print()
{
	if (rand() % 5 == 0)
	{
		throw student("學號", 20);
	}
	else if (rand() % 2 == 0)
	{
		throw teacher("工號", 32);
	}
	else
	{
		throw string();
	}
}
int main()
{
	try
	{
		Print();
	}
	catch (const person& p)
	{ }//可以捕捉所有繼承了person類型的對象。
	catch (...)//可以捕捉任意類型的異常對象。
	{ }
	return 0;
}//好了,我們這里直接來看Print函數(shù)中拋異常的操作,首先看第36到39這段代碼,它拋出的student類型的對象,在第55到56這段代碼中的catch子句被捕獲了,派生類的對象被基類類型的對象給捕獲了;再來看第40到43這段代碼,它拋出的是一個teacher類型的對象,在第55到56這段代碼中的catch子句被捕獲了,teacher這個派生類對象被person這個基類對象給捕獲了;最后看第44到47這段代碼,它所拋出的是一個string類型的對象,是被第57到58這段代碼中的catch子句捕獲的,第55到56這段代碼中的catch子句它主要捕獲的是person類型的對象以及繼承了person類的派生類對象,string類型與其不匹配,第55到56這段代碼中的catch子句捕獲不到,而第57到58這段代碼中的catch子句可以捕捉到任意類型的異常對象,因此就被第57到58這段代碼中的catch子句給捕捉到了。

1.5 異常重新拋出

1>.有時catch到一個異常對象后,需要對錯誤進行分類,其中的某種異常錯誤需要進行特殊的處理,其他錯誤則重新拋出異常給外層調(diào)用鏈處理。捕獲異常需要重新拋出,直接throw;就可以把捕捉到的對象再次拋出。

void Print()
{
	int a = rand() % 2;
	try
	{
		throw string();
	}
	catch (string& s)
	{
		if (a == 1)
		{
			throw;//如果a==1的話,就將捕獲到的那個string類型的對象再次拋出。
		}
		else
		{
			cout << s << endl;
		}
	}
}
int main()
{
	try
	{
		Print();
	}
	catch (string& s)//Print函數(shù)將捕捉到的那個對象重新拋出后,被這個catch子句重新捕捉到了。
	{
		cout << s << endl;
	}
	return 0;
}

1.6 異常安全問題

1>.異常拋出后,后面的代碼就不再執(zhí)行了,前面申請了資源(內(nèi)存、鎖等),后面要進行釋放(這里指的是我們自己用new/malloc向內(nèi)存申請的一塊資源,它在釋放時需要我們自己去調(diào)用delete函數(shù)),但是中間可能會拋異常就會導致資源沒有釋放,這里由于異常就引發(fā)了資源泄露,會產(chǎn)生安全性的問題。為了解決這個問題,那么我們就要在拋出到外層調(diào)用鏈之前要提前捕獲到這個異常對象,將那些資源釋放之后再將其重新拋出。當然我們下一章要講解的智能指針章節(jié)中所講的RALL方式解決這種問題時更好的。

2>.其次在析構(gòu)函數(shù)中,如果在析構(gòu)函數(shù)的過程中拋出了異常的話,那么就也需要慎重處理(在C類語言中,只要是開創(chuàng)資源的函數(shù),如new、malloc或釋放資源的函數(shù),如free、delete,這幾個函數(shù)都有可能會拋異常),比如析構(gòu)函數(shù)要釋放10個資源,在釋放到第5個時拋出異常,則也需要捕獲處理,否則的話后面的5個資源就沒有釋放,也會造成資源泄露。

void Print()
{
	int* array = new int[10] {0};//創(chuàng)建一個int類型的數(shù)組空間,數(shù)組的對象為10。
	try
	{
		string s;
		throw s;//拋出一個string類型的對象。
	}
	catch (...)//我們在拋出異常對象之前就申請了一塊有10個int類型空間大小的資源,為了防止出現(xiàn)資源泄露的問題,異常,我們需要Print函數(shù)內(nèi)部就捕獲到了這個異常對象,等將array執(zhí)行的那塊資源說服力之后,再將捕獲到的那個異常對象重新拋出即可。
	{
		delete[] array;
		throw;//將捕獲的那個對象重新拋出。
	}
	delete[] array;//如果這里并不會拋異常的話,編譯器不會走catch子句,異常這里還需再寫上一句刪除array指向的那塊資源的代碼。
}

1.7 異常規(guī)范

1>.對于用戶和編譯器而言,預先知道某個程序會不會拋出異常大有益處,知道某個函數(shù)是否會拋出異常會有助于簡化調(diào)用函數(shù)的代碼。

2>.C++98中函數(shù)參數(shù)列表的后面接throw(),表示該函數(shù)不會拋異常,函數(shù)參數(shù)列表的后面接throw(類型1,類型2,...)表示可能會拋出多種類型的異常,將可能會拋出的類型之間均用逗號分割。

3>.C++98的這種方式有點過于復雜,在實踐中其實并不好用,C++11中對其進行了簡化,函數(shù)參數(shù)列表后面若加noexcept這個關(guān)鍵字就表示該函數(shù)不會拋異常,若啥都不加的話則表示可能會拋出異常。

4>.編譯器并不會在編譯時去檢查noexcept修飾了,也就是說如果一個函數(shù)用noexcept修飾了,但是同時又包含了throw語句或者調(diào)用的函數(shù)可能會拋出異常,編譯器還是會順利通過的(有些編譯器可能會報個警告)。但是如果一個聲明了noexcept的函數(shù)拋出了異常的話,程序便會去調(diào)用terminate終止程序。

5>.noexcept(expression)還可以作為一個運算符去檢測一個表達式是否會拋出異常,可能會拋出異常的話則返回false,不會的話就會返回true。

void Print()noexcept
{
	int a = 0;
	cin >> a;
	if (a == 10)
	{
		throw "a==10";
	}
}
int main()
{
	try
	{
		Print();
	}
	catch (char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}//如果我們大家仔細看上述這段代碼時,稍微有一點問題,Print函數(shù)有拋出異常的風險,但是Print函數(shù)的參數(shù)后面加了noexcept這個關(guān)鍵字,理論上來說的話是不能加這個關(guān)鍵字的,通過前面的解析,我們可知,這種情況下有的編譯器是不會報錯的。我們現(xiàn)在來運行這個代碼來看一下,如果我們輸入5的話,編譯器確實不會報錯,而且還完整地運行了下來,但如果我們輸入10的話,程序在這里就別破中止運行了,原因是因為Print這個用noexcept修飾的函數(shù)在運行時拋出了一個異常對象。

2 標準庫的異常

1>.C++標準庫也定義了一套自己的異常繼承體系,基類是exception;所以我們?nèi)粘T趯懗绦驎r,需要在主函數(shù)捕獲exception即可,要獲取異常信息,調(diào)用what函數(shù),what函數(shù)是一個虛函數(shù),派生類可以重寫。

OK,今天我們就先講到這里了,那么,我們下一篇再見,謝謝大家的支持!

到此這篇關(guān)于C++中異常的深度解析的文章就介紹到這了,更多相關(guān)C++異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++中的std::funture和std::promise實例詳解

    C++中的std::funture和std::promise實例詳解

    在線程池中獲取線程執(zhí)行函數(shù)的返回值時,通常使用 std::future 而不是 std::promise 來傳遞返回值,這篇文章主要介紹了C++中的std::funture和std::promise實例詳解,需要的朋友可以參考下
    2024-05-05
  • C++實現(xiàn)基于時序公平的讀寫鎖詳解

    C++實現(xiàn)基于時序公平的讀寫鎖詳解

    讀寫鎖與普通的互斥鎖的區(qū)別在于有兩種上鎖方式:讀鎖和寫鎖,不用的用戶對同一個讀寫鎖獲取讀鎖是非互斥的,其他情況則是互斥的,本文小編將給大家詳細介紹C++實現(xiàn)基于時序公平的讀寫鎖,需要的朋友可以參考下
    2023-10-10
  • C數(shù)據(jù)結(jié)構(gòu)中串簡單實例

    C數(shù)據(jù)結(jié)構(gòu)中串簡單實例

    這篇文章主要介紹了C數(shù)據(jù)結(jié)構(gòu)中串簡單實例的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • C語言直接插入排序算法

    C語言直接插入排序算法

    大家好,本篇文章主要講的是C語言直接插入排序算法,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • C++超詳細講解友元與內(nèi)部類

    C++超詳細講解友元與內(nèi)部類

    朋友們好,這篇播客我們繼續(xù)C++的初階學習,現(xiàn)在對我們對C++的友元,內(nèi)部類知識點做出總結(jié),整理出來一篇博客供我們一起復習和學習,如果文章中有理解不當?shù)牡胤?還希望朋友們在評論區(qū)指出,我們相互學習,共同進步
    2022-06-06
  • OpenCV圖像處理之實現(xiàn)圖像膨脹腐蝕操作

    OpenCV圖像處理之實現(xiàn)圖像膨脹腐蝕操作

    圖像形態(tài)學操作是指基于形狀的一系列圖像處理操作的合集,主要是基于集合論基礎上的形態(tài)學數(shù)學對圖像進行處理。本文將為大家介紹一下如何利用OpenCV實現(xiàn)其中的腐蝕和膨脹操作,需要的可以參考一下
    2022-09-09
  • C++結(jié)構(gòu)體與類指針知識點總結(jié)

    C++結(jié)構(gòu)體與類指針知識點總結(jié)

    在本篇文章里小編給大家整理了關(guān)于C++結(jié)構(gòu)體與類指針知識點以及相關(guān)內(nèi)容,有興趣的朋友們參考學習下。
    2019-09-09
  • c++ vector 常用函數(shù)示例解析

    c++ vector 常用函數(shù)示例解析

    這篇文章主要介紹了c++ vector 常用函數(shù)示例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • C語言分別實現(xiàn)棧和隊列詳解流程

    C語言分別實現(xiàn)棧和隊列詳解流程

    棧和隊列,嚴格意義上來說,也屬于線性表,因為它們也都用于存儲邏輯關(guān)系為 "一對一" 的數(shù)據(jù),但由于它們比較特殊,因此將其單獨作為一章,做重點講解
    2022-04-04
  • 詳情介紹C++之命名空間

    詳情介紹C++之命名空間

    這篇文章主要詳情介紹了C++命名空間,命名空間的出現(xiàn)就是為了解決名稱沖突問題,對此感興趣的朋友可以參考下面文章
    2021-09-09

最新評論