C++之異常

目錄

前言

一、什么是異常

二、C++中的異常

2.1 C語言中的異常處理

2.2 C++中的異常處理

2.3 異常的拋出與捕獲

2.4 棧展開

2.5 查找匹配的處理代碼

2.6 異常重新拋出

2.7 異常安全問題

2.8 異常規范

2.9 標準庫的異常


前言

在之前我們已經學習了C++中不少知識了,但是其中的一些異常,我們當時可能就隨便糊弄過去了,我們不清楚C++中處理異常的機制,而今天我們就來學習一下C++中如何處理異常。


一、什么是異常

異常指程序、系統或事物運行中出現的不符合預期或正常邏輯的狀態或事件,可能導致流程中斷、錯誤結果或功能失效,這是我們在百度上的對異常這個名詞的權威解釋。那么,編程語言中的異常是指程序在運行過程中遇到的一些不正常的情況,如除以零、訪問不存在的文件、內存不足等。這些異常如果不處理,可能導致程序崩潰或產生不可預期的結果。異常處理機制允許程序檢測到這些異常,并采取相應的措施,如記錄錯誤信息、提示用戶或嘗試恢復程序,從而提高程序的健壯性和用戶體驗。常見的異常處理機制包括try-catch塊,用于包圍可能拋出異常的代碼,并在catch塊中處理異常。有效的異常處理可以幫助開發者寫出更加可靠和穩定的代碼。

簡單來說,我們在編程中所遇到的那些異常就是因為我們的某些操作,導致出現了一些可能會讓計算機程序崩潰的情況,我們把這些情況都叫做異常。

二、C++中的異常

2.1 C語言中的異常處理

我們在C語言階段,當時我們學習時是沒有學習到異常的,因為在C語言中并沒有內置的處理異常的機制。在C語言中處理異常通常是使用返回值或者全局變量。例如,當我們的一個函數出現異常時,它會返回一個錯誤碼信息,從而使得編譯器發生報錯。這樣做看起來,十分地麻煩,因為每個函數都返回一個錯誤碼的話,就會導致代碼冗長復雜。

2.2 C++中的異常處理

C++中的異常處理機制相較于C語言更為強大和靈活,提供了try、catch、throw等關鍵字,支持類型化異常和棧展開,從而更優雅地處理錯誤,減少資源泄漏的風險。這樣一來,即使出現了異常,我們也有辦法去處理。接下來我們就來學習學習C++中處理異常的方式。

2.3 異常的拋出與捕獲

  • 當程序出現問題時,我們通過拋出(throw)一個對象來引發一個異常,該對象的數據類型以及當前的調用鏈決定了應該由哪個catch的處理代碼來處理異常。(這里我們需要注意的是,我們的try塊語句不一定就和下一個catch進行匹配,它們是根據數據類型進行匹配的)
  • 被選中的處理代碼是調用鏈中與改對象類型匹配且與拋出異常位置最近的那一個。根據拋出對象的類型和內容,程序的拋出異常部分會告知異常處理部分到底發生了什么錯誤。
  • 當throw執行時,throw后面的語句就不再被執行。程序的執行從throw位置跳到與之匹配的catch模塊,catch可能是同一函數中的一個局部的catch,也可能時調用鏈中另一個函數中的catch,控制權從throw轉移到了catch位置。這里還有兩個重要的含義:1.沿用調用鏈的函數可能提早退出;2.一旦程序開始執行異常處理程序,沿用調用鏈創建的對象都將被銷毀。
  • 拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一些局部對象,我們在離開那個函數后,那些局部對象就銷毀,所以會生成一個拷貝對象,這個拷貝的對象會在catch子句后銷毀。(這里類似我們之前的函數傳值返回,我們只是傳遞的是函數的值,對于形參的操作并不會影響對實參的內容。我們這里對原來那個異常對象的拷貝對象進行處理不會影響原來的那個異常對象)

2.4 棧展開

  • 當我們拋出異常時,程序會暫停當前函數的執行,開始尋找與之匹配的catch子句,首先檢查throw本身是否在try塊內部,如果在則查找匹配的catch子句,如果有匹配的,則跳到對應的catch的地方進行處理。(這一系列操作在運行時動態進行的)
  • 如果當前函數中沒有try/catch子句,或者有try/catch子句但是類型不匹配,則退出當前函數,繼續在外層調用函數鏈中進行查找,上述的查找catch過程被稱作為棧展開。
  • 如果到達main函數,依舊沒有找到匹配的catch子句,程序會調用標準庫中的 terminate 函數終止程序。
  • 如果找到匹配的catch子句處理后,catch子句代碼會繼續執行。

如下圖是我們拋出異常后棧展開的示意圖:

接下來我們用如下示例代碼來給大家展示一下,異常是用哪個catch來進行捕獲的(我們寫了兩個函數一個除法函數,我們在函數體中使用try...catch語句來捕獲異常;還有一個函數則是在try塊語句中調用了除法函數同時也拋出異常了,最后我們在main函數中try塊語句中調用我們的Func函數,這樣就形成了一個層層調用的形式,我們來通過運行結果來看看最后匹配的catch是哪個

double Divide(int a, int b)
{try{// 當b == 0時拋出異常if (b == 0){string s("Divide by zero condition!");throw s;}else{return ((double)a / (double)b);}}catch (int errid){cout << errid << endl;}return 0;
}void Func()
{int len, time;cin >> len >> time;try {cout << Divide(len, time) << endl;}catch (const char* errmsg){cout << errmsg << endl;cout << "Func" << endl;}}int main()
{while (1){try{Func();}catch (const string& errmsg){cout << errmsg << endl;cout << "main" << endl;}}return 0;
}

從上面的運行代碼結果,我們可以看出來它最終匹配的catch是我們main函數中的catch,因為我們在Divide函數中拋出的異常是一個string類型的語句,由于我們的匹配原則是根據數據類型來進行匹配的,因此我們匹配的是main函數中的catch語句,它的catch參數是string類型的。我們通過上面的例子,我們更加清晰地認識到了我們try中的異常來匹配catch,是通過拋出的異常數據類型與catch參數類型是否匹配來進行匹配選擇的。

2.5 查找匹配的處理代碼

  • 一般情況下拋出對象和catch類型是完全匹配的,如果有多個類型匹配的,就選擇離它位置更近的那個。
  • 但是有一些例外,它允許從非常量向常量類型進行轉換(即將普通變量向有const修飾的變量進行轉換)即我們之前所說的權限縮小;它還允許數組轉換成指向數組元素類型的指針,函數轉換成函數的指針;允許從派生類向基類類型進行轉換,這點非常常用,實際中繼承體系基本都是用這個方式來進行設計的。
  • 如果到main函數,異常仍沒有被匹配的話,就終止程序。不是在發生嚴重的錯誤情況下,我們是不期望程序終止的,所以一般main函數中最后都會使用catch(...),它可以捕獲任意類型的異常,這樣我們就不用寫好幾個不同類型的catch語句來進行捕獲異常了,但是這種catch語句我們是不知道異常錯誤是什么。

如下代碼,我們通過一個異常基類來獲取一些基本的信息:錯誤碼,id等,然后我們再將幾個派生類來繼承那個基類,除此之外加上自己的一些異常信息。我們是為了測試查找匹配的異常處理代碼,于是我們在main函數中設置一系列的隨機數,并定時出現結果。

#include<thread>class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}int getid() const{return _id;}
protected:string _errmsg;int _id;
};class SqlException : public Exception
{
public:SqlException(const string& errmsg, int id, const string& sql):Exception(errmsg, id), _sql(sql){}virtual string what() const{string str = "SqlException:";str += _errmsg;str += "->";str += _sql;return str;}
private:const string _sql;
};class CacheException : public Exception
{
public:CacheException(const string& errmsg, int id):Exception(errmsg, id){}virtual string what() const{string str = "CacheException:";str += _errmsg;return str;}
};class HttpException : public Exception
{
public:HttpException(const string& errmsg, int id, const string& type):Exception(errmsg, id), _type(type){}virtual string what() const{string str = "HttpException:";str += _type;str += ":";str += _errmsg;return str;}
private:const string _type;
};void SQLMgr()
{if (rand() % 7 == 0){throw SqlException("權限不足", 100, "select * from name = '張三'");}else{cout << "SQLMgr 調用成功" << endl;}
}void CacheMgr()
{if (rand() % 5 == 0){throw CacheException("權限不足", 100);}else if (rand() % 6 == 0){throw CacheException("數據不存在", 101);}else{cout << "CacheMgr 調用成功" << endl;}SQLMgr();
}void HttpServer()
{if (rand() % 3 == 0){throw HttpException("請求資源不存在", 100, "get");}else if (rand() % 4 == 0){throw HttpException("權限不足", 101, "post");}else{cout << "HttpServer調用成功" << endl;}CacheMgr();
}int main()
{srand(time(0));while (1){this_thread::sleep_for(chrono::seconds(1));try{HttpServer();}catch (const Exception& e) // 這里捕獲基類,基類對象和派生類對象都可以被捕獲{// 多態cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

我們可以看到,由于我們輸入的是隨機信息,因此最后的結果也是隨機的。我們從上面的結果可以看出,我們匹配catch語句,它的數據類型是可以通過派生類繼承基類來實現的,而這也是我們以后主要使用到的。

2.6 異常重新拋出

有時catch到一個異常對象后,需要對我們所查找的異常對象所造成的錯誤進行分類,其中的某種異常錯誤需要進行特殊的處理,其他錯誤則重新拋出異常給外層調用鏈來進行處理。捕獲異常后需要重新拋出,我們直接使用throw這個關鍵字就可以把捕獲的對象直接拋出。

其實對異常重新拋出就是對我們所拋出的那個異常在放到一個循環中,我們在這個循環中來進行對異常的處理,達到條件我們就將其拋出。如下代碼是我們模擬我們平時信息對話,信號不好嘗試多次才發送出去的情況。

void _SeedMsg(const string& s)
{if (rand() % 2 == 0){throw HttpException("網絡不穩定,發送失敗", 102, "put");}else if (rand() % 7 == 0){throw HttpException("你已經不是對象的好友,發送失敗", 103, "put");}else{cout << "發送成功" << endl;}
}void SendMsg(const string& s)
{// 發送消息失敗,則再重試3次for (size_t i = 0; i < 4; i++){try{_SeedMsg(s);break;}catch (const Exception& e){// 捕獲異常,if中是102號錯誤,網絡不穩定,則重新發送// 捕獲異常,else中不是102號錯誤,則將異常重新拋出if (e.getid() == 102){// 重試三次以后否失敗了,則說明網絡太差了,重新拋出異常if (i == 3)throw;cout << "開始第" << i + 1 << "重試" << endl;}else{throw;}}}
}
int main()
{srand(time(0));string str;while (cin >> str){try{SendMsg(str);}catch (const Exception& e){cout << e.what() << endl << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

我們從上面的代碼可以看出,異常的重新拋出,我們就是將我們的主要異常封裝成一個函數,然后我們在另一個函數體中使用循環多次調用那個函數,同時使用throw來拋出異常,這里由于我們拋出的異常我們在第一個函數中就已經展示出來了,這里我們的throw就不用接其他內容了。

2.7 異常安全問題

  • 異常拋出后,后面的代碼就不再執行,前面申請了資源(內存,鎖等),后面進行釋放,但是中間可能會拋出異常就會導致資源沒有釋放,這里由于異常就引發了資源泄露,產生安全性問題。中間我們需要捕獲異常,釋放資源后再重新拋出。
  • 其次析構函數中,如果拋出異常后也要謹慎處理,比如析構函數需要釋放10個資源,釋放到第5個時拋出異常的話,我們也是需要進行捕獲處理的,否則后面的5個資源就沒法釋放,也就造成了資源泄露了。

下面的代碼是有資源申請的,我們在捕獲異常時需要對資源進行釋放,另外我們在捕獲異常的后面對兩個指針進行釋放,再輸出相應的地址。

double Divide(int a, int b)
{try{// 當b == 0時拋出異常if (b == 0){string s("Divide by zero condition!");throw s;}else{return ((double)a / (double)b);}}catch (int errid){cout << errid << endl;}return 0;
}void Func()
{int* ptr1 = new int[10];int* ptr2 = new int[10];int len, time;cin >> len >> time;try {cout << Divide(len, time) << endl;}catch (...){delete[] ptr1;cout << "delete:" << ptr1 << endl;delete[] ptr2;cout << "delete:" << ptr2 << endl;// 重新拋出throw;}delete[] ptr1;cout << "delete:" << ptr1 << endl;delete[] ptr2;cout << "delete:" << ptr2 << endl;
}int main()
{while (1){try{Func();}catch (const string& errmsg){cout << errmsg << endl;}catch (...){cout << "未知異常" << endl;}}return 0;
}

我們從上面的運行結果,我們可以看出來地址都是相同的,說明最后都是被釋放的。(我們在catch語句塊中選擇了對指針的資源釋放)

2.8 異常規范

  • 對于用戶和編譯器而言,預先知道某個程序會不會拋出異常會大有裨益,知道某個函數是否會拋出異常有助于簡化調用函數的代碼。
  • C++98中函數參數列表的后面接throw(),表示函數不拋出異常,函數參數列表的后面接throw(類型1,類型2...)表示可能會拋出多種類型的異常,可能會拋出的異常類型用逗號來進行分割。
  • C++98的方式過于復雜,實踐中并不好用,在C++11中進行了簡化,在函數參數列表后面加上noexcept則表示不會拋出異常,啥都不加的話就表示可能會拋出異常。
  • 編譯器并不會在編譯時檢查noexcept,也就是說如果一個函數用noexcept修飾了,但是同時又包含了throw語句或者調用的函數可能會拋出異常,編譯器還是會順利編譯通過的(有些編譯器可能會報個警告)但是一個聲明了noexcept的函數拋出了異常,程序會調用 terminate 來終止程序。
  • noexcept(expression)還可以作為一個運算符去檢測一個表達式是否會拋出異常,可能會拋出異常的話就返回false,不會拋出異常的話就返回true。

有時候,我們傳遞一個函數(會拋出異常的函數)的話,它是根據那個函數是否會拋出異常來返回返回值的,我們不是通過看傳遞的參數來進行判斷的,即使我們傳遞的參數不會造成異常,但是這個函數是可能會拋出異常的,因此它還是會返回一個false的。

// C++98
// 這?表?這個函數只會拋出bad_alloc的異常 
void* operator new (std::size_t size) throw (std::bad_alloc);
// 這?表?這個函數不會拋出異常 
void* operator delete (std::size_t size, void* ptr) throw();
// C++11
size_type size() const noexcept;
iterator begin() noexcept;
const_iterator begin() const noexcept;
double Divide(int a, int b) noexcept
{// 當b == 0時拋出異常 if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
int main()
{try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "Unkown Exception" << endl;}int i = 0;cout << noexcept(Divide(1,2)) << endl;cout << noexcept(Divide(1,0)) << endl;cout << noexcept(++i) << endl;return 0;
}

2.9 標準庫的異常

在C++標準庫中也定義了一套自己的異常繼承體系庫,基類是exception,所以我們日常寫程序時,需要在主函數捕獲exception即可,要獲取異常信息,調用what函數,what函數是一個虛函數,因為我們在前面就說過了我們可以使用派生類來繼承一個異常的基類,我們可以在派生類中重寫那些what虛函數。如下圖是一些標準庫中的異常

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/904546.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/904546.shtml
英文地址,請注明出處:http://en.pswp.cn/news/904546.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

$在R語言中的作用

在 R 語言中&#xff0c;$ 是一個非常重要的操作符&#xff0c;主要用于訪問對象的成員或組件。它的用途非常廣泛&#xff0c;不僅限于數據框&#xff08;data frame&#xff09;&#xff0c;還可以用于列表&#xff08;list&#xff09;、環境&#xff08;environment&#xf…

設計一個分布式系統:要求全局消息順序,如何使用Kafka實現?

一、高吞吐低延遲 Kafka 集群設計要點 1. 分區策略優化 // 計算合理分區數公式&#xff08;動態調整&#xff09; int numPartitions max(Tp, Tc) / min(Tp, Tc) // Tp生產者吞吐量 Tc消費者吞吐量建議初始按業務鍵&#xff08;如訂單ID&#xff09;哈希分區單分區吞吐建議…

[dify]官方模板DeepResearch工作流學習筆記

一、功能 根據用戶輸入的主題進行多輪搜索并生成綜合報告 1、流程分析 1.1 初始階段 Start節點&#xff1a;接收用戶輸入的"depth"參數&#xff0c;決定搜索的深度/輪數 參數可以不填&#xff0c;不填的時候取默認值3 Create Array節點&#xff1a;根據depth參數…

hadoop中的序列化和反序列化(3)

3. Java的序列化 Java提供了內置的序列化機制&#xff0c;通過java.io.Serializable接口實現。 3.1 如何實現Java序列化 讓類實現Serializable接口。 使用ObjectOutputStream進行序列化。 使用ObjectInputStream進行反序列化。 示例代碼 序列化 java 復制 import jav…

6、CMake基礎:流程控制

流程控制 1. 條件判斷1.1 基本表達式1.2 邏輯判斷1.3 比較基于數值的比較基于字符串的比較 1.4 文件操作1.5 其他 2. 循環2.1 foreach方法1方法2方法3方法4 2.2 while 在 CMake 的 CMakeLists.txt 中也可以進行流程控制&#xff0c;也就是說可以像寫 shell 腳本那樣進行條件判斷…

【網絡編程】二、UDP網絡套接字編程詳解

文章目錄 前言Ⅰ. UDP服務端一、服務器創建流程二、創建套接字 -- socketsocket 屬于什么類型的接口???socket 是被誰調用的???socket 底層做了什么???和其函數返回值有沒有什么關系??? 三、綁定對應端口號、IP地址到套接字 -- bind四、數據的發送和接收 -- sendto…

準確--Notepad++ 實用的插件介紹

Notepad 提供了很多實用的插件&#xff0c;可以極大地提升編程和文本編輯的效率。以下是一些常用且有用的插件介紹&#xff1a; 1. NPP Export 功能&#xff1a;可以將打開的文件導出為 HTML 或 RTF 格式&#xff0c;方便生成漂亮的代碼文檔。用途&#xff1a;適合需要將代碼…

[20250507] AI邊緣計算開發板行業調研報告 ??(2024年最新版)?

[20250507] AI邊緣計算開發板行業調研報告 ??(2024年最新版&#xff09;? 一、行業背景?? 隨著物聯網設備激增與AI模型輕量化&#xff0c;邊緣計算成為AI落地核心場景。AI邊緣計算開發板&#xff08;Edge AI Board&#xff09;作為硬件載體&#xff0c;需滿足??低延遲…

傳輸層協議 1.TCP 2.UDP

傳輸層協議 1.TCP 2.UDP TCP協議 回顧內容 傳輸層功能&#xff1a;定義應用層協議數據報文的端口號&#xff0c;流量控制對原始數據進行分段處理 傳輸層所提供服務 傳輸連接服務數據傳輸服務&#xff1a;流量控制、差錯控制、序列控制 一、傳輸層的TCP協議 1.面向連接的…

LVGL -meter的應用

1 meter介紹 lv_meter 是 LVGL v8 引入的一種圖形控件&#xff0c;用于創建儀表盤樣式的用戶界面元素&#xff0c;它可以模擬像速度表、電壓表、溫度表這類模擬表盤。它通過可視化刻度、指針、顏色弧線等來展示數值信息&#xff0c;是一種非常直觀的數據展示控件。 1.1 核心特…

GoFly企業版框架升級2.6.6版本說明(框架在2025-05-06發布了)

前端框架升級說明&#xff1a; 1.vue版本升級到^3.5.4 把"vue": "^3.2.40",升級到"vue": "^3.5.4"&#xff0c;新版插件需要時useTemplateRef,所以框架就對齊進行升級。 2.ArcoDesign升級到2.57.0&#xff08;目前最新2025-02-10&a…

阿里聯合北大開源數字人項目FantasyTalking,輸出內容更加動態化~

簡介 FantasyTalking 的核心目標是從單一靜態圖像、音頻&#xff08;以及可選的文本提示&#xff09;生成高保真、連貫一致的說話肖像。研究表明&#xff0c;現有方法在生成可動畫化頭像時面臨多重挑戰&#xff0c;包括難以捕捉細微的面部表情、整體身體動作以及動態背景的協調…

基于nnom的多選擇器

核心組件 元件類型目的接口STM32F103CB微控制器主處理單元-MPU60506 軸 IMU移動偵測I2C 接口W25Q64 系列閃存信號和配置存儲SPI 系列按鈕用戶輸入模式選擇和激活GPIO &#xff08;通用輸出&#xff09;搭載了LED用戶反饋系統狀態指示GPIO &#xff08;通用輸出&#xff09;RT6…

Redis中6種緩存更新策略

Redis作為一款高性能的內存數據庫&#xff0c;已經成為緩存層的首選解決方案。然而&#xff0c;使用緩存時最大的挑戰在于保證緩存數據與底層數據源的一致性。緩存更新策略直接影響系統的性能、可靠性和數據一致性&#xff0c;選擇合適的策略至關重要。 本文將介紹Redis中6種緩…

項目優先級頻繁變動,如何應對?

項目優先級頻繁變動是許多公司和團隊在工作中常遇到的挑戰。 這種情況通常由業務需求變化、市場壓力或高層決策調整等因素引起&#xff0c;常常讓團隊成員感到困惑和不安。首先&#xff0c;制定明確的優先級管理框架是應對項目優先級變動的基礎&#xff0c; 通過清晰的優先級排…

屏蔽力 | 在復雜世界中從內耗到成長的轉變之道

注&#xff1a;本文為“屏蔽力”相關文章合輯。 略作重排&#xff0c;未全整理。 世上的事再復雜&#xff0c;不外乎這三種 原創 小鹿 讀者 2022 年 12 月 02 日 18 : 27 甘肅 文 / 小鹿 在這世上&#xff0c;每天都有大事小事、瑣事煩事。我們總為世事奔波忙碌&#xff0c;…

[數據處理] 3. 數據集讀取

&#x1f44b; 你好&#xff01;這里有實用干貨與深度分享?? 若有幫助&#xff0c;歡迎&#xff1a;? &#x1f44d; 點贊 | ? 收藏 | &#x1f4ac; 評論 | ? 關注 &#xff0c;解鎖更多精彩&#xff01;? &#x1f4c1; 收藏專欄即可第一時間獲取最新推送&#x1f514;…

IIS配置SSL

打開iis 如果搜不到iis&#xff0c;要先開 再搜就打得開了 cmd中找到本機ip 用http訪問本機ip 把原本的http綁定刪了 再用http訪問本機ip就不行了 只能用https訪問了

RabbitMQ的交換機

一、三種交換機模式 核心區別對比?? ??特性????廣播模式&#xff08;Fanout&#xff09;????路由模式&#xff08;Direct&#xff09;????主題模式&#xff08;Topic&#xff09;????路由規則??無條件復制到所有綁定隊列精確匹配 Routing Key通配符匹配…

(2025,AR,NAR,GAN,Diffusion,模型對比,數據集,評估指標,性能對比)文本到圖像的生成和編輯:綜述

【本文為我在去年完成的綜述&#xff0c;因某些原因未能及時投稿&#xff0c;但本文仍能為想要全面了解文本到圖像的生成和編輯的學習者提供可靠的參考。目前本文已投稿 ACM Computing Surveys。 完整內容可在如下鏈接獲取&#xff0c;或在 Q 群群文件獲取。 中文版為論文初稿&…