c++學習之異常

?前言

? 早在c語言的時候,就已經有處理錯誤的方式了,第一種方式太過暴力,就是斷言,程序發生錯誤,直接終止退出,這樣的報錯對于真正開發應用等太過暴力。第二種方式,就是返回errno,其實,我們會在發生錯誤的時候,或者程序結束運行的時候,返回一個錯誤碼,錯誤碼就是一個數字,不同的數字的錯誤碼表示的錯誤信息也不一樣,c語言種就是用errno表示這些錯誤碼。

但這兩種方式在處理錯誤上不是比較好,因此c++就引入了異常,用來處理錯誤。

異常

異常的拋出與捕獲機制

未知異常

拋子類異常捕獲父類異常

?異常的重新拋出

異常的安全問題

異常的規范


異常

異常是一種處理錯誤的方式,當一個函數發現自己無法處理的錯誤時就可以拋出異常,讓函數的
直接或間接的調用者處理這個錯誤。
throw: 當問題出現時,程序會拋出一個異常。這是通過使用 throw 關鍵字來完成的。
catch: 在您想要處理問題的地方,通過異常處理程序捕獲異常 . catch 關鍵字用于捕獲異
常,可以有多個 catch 進行捕獲。
try: try 塊中的代碼標識將被激活的特定異常 , 它后面通常跟著一個或多個 catch 塊。
如果有一個塊拋出一個異常,捕獲異常的方法會使用 try catch 關鍵字。 try 塊中放置可能拋 出異常的代碼,try 塊中的代碼被稱為保護代碼。

例如下面的簡單例子:

double Divsion(int x, int y)
{if (y == 0){//如果分母為零,我們就可以拋異常,這里拋得東西可以是任意類型的對象throw "除零錯誤";}else{return (double)x / y;}}
int main()
{try{int x = 1.1; int y = 0;double ret = Divsion(x,y);}catch (const char * x) //捕獲異常,并打印異常的錯誤信息{cout << x<<endl;}return 0;
}

注意:1.拋異常可以是任意類型的對象,不捕獲異常也會報錯(無具體的錯誤信息).2.可以在任意地方捕獲異常(任何一個函數棧幀),一般是在調用的哪里捕獲。

重點? ? ? ?3.捕獲異常的類型必須與拋出異常的類型一致,否則無法捕獲。

程序在運行的過程中在遇到這里的異常后會立馬就會跑到捕獲異常的代碼處,捕獲之后正常繼續執行, 若沒拋異常則會跳過try catch語句。

異常的拋出與捕獲機制

1.異常的拋出和匹配原則
1. 異常是通過拋出對象而引發的,該對象的類型決定了應該激活哪個catch的處理代碼。
2. 被選中的處理代碼是調用鏈中與該對象類型匹配且離拋出異常位置最近的那一個。
3. 拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一個臨時對象,
所以會生成一個拷貝對象,這個拷貝的臨時對象會在被catch以后銷毀。(這里的處理類似
于函數的傳值返回)
4. catch(...)可以捕獲任意類型的異常,問題是不知道異常錯誤是什么。
5. 實際中拋出和捕獲的匹配原則有個例外,并不都是類型完全匹配,可以拋出的派生類對象, 使用基類捕獲,這個在實際中非常實用。

?

其次在整個函數棧幀中,由于異常的存在,代碼執行優先走異常且采用就近原則。具體如下:
2.在函數調用鏈中異常棧展開匹配原則
1. 首先檢查throw本身是否在try塊內部,如果是再查找匹配的catch語句。如果有匹配的,則
調到catch的地方進行處理。
2. 沒有匹配的catch則退出當前函數棧,繼續在調用函數的棧中進行查找匹配的catch。
3. 如果到達main函數的棧,依舊沒有匹配的,則終止程序。上述這個沿著調用鏈查找匹配的
catch子句的過程稱為棧展開。所以實際中我們最后都要加一個catch(...)捕獲任意類型的異
常,否則當有異常沒捕獲,程序就會直接終止。
4. 找到匹配的catch子句并處理以后,會繼續沿著catch子句后面繼續執行。

?例如:

class Func
{
public:void test(){cout << "Func for test";}
};
double Divsion(int x, int y)
{if (y == 0){//如果分母為零,我們就可以拋異常,這里拋得東西可以是任意類型的對象throw "除零錯誤";}else{return (double)x / y;}//若有拋出異常,則在捕獲異常時Func a;
}
void  Test()
{Func a;//在捕獲時,優先走棧鏈最近的哪一個,這里就走test里的,而不走main中的try{int x = 1.1; int y = 0;double ret = Divsion(x, y);}catch (const char* x){cout << x << endl;}a.test();
};
int main()
{Test();try{int x = 1.1; int y = 0;double ret = Divsion(x,y);}catch (const char * x){cout << x<<endl;}//如果都走到mian這里,且走完了,也沒有發現有捕獲的,依然會報錯return 0;
}

綜上可得:異常的捕獲會引發執行流的跳躍。

?在拋出異常時,會自動生成拋出異常的臨時拷貝,到了捕獲時,傳給他。

未知異常

異常被拋出后一定要進行捕獲,若不捕獲就會坑到自己。但是在實際開發過程中,拋出的異常太多,或者有人拋出異常沒注意說明,總有可能會忘記捕獲異常,之后直接導致程序終止運行,而這太過影響開發效率,因此c++還有提供了未知異常,無論什么異常,在拋出后都或被捕獲。
但實際在運用時,利用catch(...)作為最后一道防線,正常我們要捕獲的異常就正常捕獲,但是在最后設置一道未知異常捕獲的防線,若果程序走到了未知異常,就說明存在有的異常沒有被捕獲,而不是程序直接終止掉。
double Division(int a, int b)
{// 當b == 0時拋出異常if (b == 0){throw "Division by zero condition!";}	elsereturn ((double)a / (double)b);
}
void Test()
{throw string("test");
}
void Func()
{int len, time;cin >> len >> time;cout << Division(len, time) << endl;
}
int main()
{try {Func();}catch (const char* errmsg){cout << errmsg << endl;}try{Test();}catch (...) {cout << "unkown exception" << endl;}return 0;
}

拋子類異常捕獲父類異常

實際服務器開發中的異常體系,首先會寫一個父類,通常包含異常信息message和異常id,之后,在對于運行庫,寄存器,協議的調用中,對應寫一個子類,重寫里面的錯誤信息,設置好異常捕捉,在捕捉時,直接通過父類對象異常即可。

//父類異常
class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}
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 HttpServerException : public Exception
{
public:HttpServerException(const string& errmsg, int id, const string& type):Exception(errmsg, id), _type(type){}virtual string what() const{string str = "HttpServerException:";str += _type;str += ":";str += _errmsg;return str;}
private:const string _type;
};
void SQLMgr()
{srand(time(0));if (rand() % 7 == 0){throw SqlException("權限不足", 100, "select * from name = '張三'");}//throw "xxxxxx";
}
void CacheMgr()
{srand(time(0));if (rand() % 5 == 0){throw CacheException("權限不足", 100);}else if (rand() % 6 == 0){throw CacheException("數據不存在", 101);}SQLMgr();
}
void HttpServer()
{// ...srand(time(0));if (rand() % 3 == 0){throw HttpServerException("請求資源不存在", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("權限不足", 101, "post");}CacheMgr();
}
int main()
{while (1){Sleep(10);try {HttpServer();}catch (const Exception& e) // 這里捕獲父類對象就可以{// 多態cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

?異常的重新拋出

有可能單個的catch不能完全處理一個異常,在進行一些校正處理以后,希望再交給更外層的調用
鏈函數來處理,catch則可以通過重新拋出將異常傳遞給更上層的函數進行處理。
比如我們在堆區開的空間,在末尾處需要釋放,而當我們拋出異常在釋放前,由于異常捕獲的跳躍性,我們的空間得不到釋放,存在內存泄漏的安全問題,這也是異常的一個隱患。
void Func()
{throw string("test");
}
void Test()
{//假設在此之前我們有一個數組int* arry = new int[10];Func();delete[]arry;arry = nullptr;cout << "arry has reliszed";
}
int main()
{try{Test();}catch (string str){cout << str << endl;}
}
一般異常的捕獲我們是放到外層的,但正由于這樣導致跳躍,我們下面的代碼得不到執行,如這里arry無法得到釋放,那我們因給如何避免這樣安全隱患呢?
因此這里就需要異常的重新拋出:
void Func()
{throw string("test");
}
void Test()
{//假設在此之前我們有一個數組int* arry = new int[10];//在該位置,再次進行捕捉,并拋出try{Func();}catch (string str){cout << str << endl;delete[]arry;arry = nullptr;cout << "arry has reliszed";throw;}}
int main()
{try{Test();}catch (string str){cout << str << endl;}
}

異常的安全問題

1.構造函數完成對象的構造和初始化,最好不要在構造函數中拋出異常,否則可能導致對象不
完整或沒有完全初始化。
2.析構函數主要完成資源的清理,最好不要在析構函數內拋出異常,否則可能導致資源泄漏(內
存泄漏、句柄未關閉等)。
3.C++中異常經常會導致資源泄漏的問題,比如在new和delete中拋出了異常,導致內存泄
漏,在lock和unlock之間拋出了異常導致死鎖,C++經常使用RAII來解決以上問題,關于RAII
我們智能指針這節進行講解。

異常的規范

對于需要拋異常的,我們在函數后面列出可能會拋出的類型。

對于不用拋異常的,c++提供了關鍵字noexcept,在函數體后面添加noexcept表示不會有異常,但我們要確保沒有異常。

若函數體沒有任何聲明,則可能會拋出各種異常。

事實上在這里聲明可能會拋出的異常的類型,并不是很準確的,我們聲明只有一種類型,但也可以拋別的異常,不過c++規范我們去使用異常,確信會拋出哪些異常,我們就在函數后面聲明。

// 這里表示這個函數會拋出列出中的某種類型的異常
void fun() throw(A,B,C,D);// 這里表示這個函數只會拋出bad_alloc的異常
void* operator new (std::size_t size) throw (std::bad_alloc);// C++11 中新增的noexcept,表示不會拋異常
thread() noexcept;
thread (thread&& x) noexcept;

異常的使用有缺點也有優點,但運用起來利大于弊。

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

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

相關文章

Latex公式中矩陣的方括號和圓括號表示方法

一、背景 在使用Latex寫論文時&#xff0c;不可避免的涉及到矩陣公式。有的期刊要求矩陣用方括號&#xff0c;有的期刊要求矩陣用圓括號。因此&#xff0c;特記錄一下Latex源碼在兩種表示方法上的區別&#xff0c;以及數組和方程組的擴展。 二、矩陣的方括號表示 首先所有的…

OpenGLES:glReadPixels()獲取相機GLSurfaceView預覽數據并保存

Android現行的Camera API2機制可以通過onImageAvailable(ImageReader reader)回調從底層獲取到Jpeg、Yuv和Raw三種格式的Image&#xff0c;然后通過保存Image實現拍照功能&#xff0c;但是卻并沒有Api能直接在上層直接拿到實時預覽的數據。 Android Camera預覽的實現是上層下發…

Java學習筆記——instanceof關鍵字

instanceof關鍵字&#xff1a; 作用&#xff1a;保證對象向下轉型的安全性在對象向下轉型前判斷某一對象實例是否屬于某個類 判斷時&#xff0c;如果對象是null&#xff0c;則 instanceof 判斷結果為 false

Spring Boot 整合kafka:生產者ack機制和消費者AckMode消費模式、手動提交ACK

目錄 生產者ack機制消費者ack模式手動提交ACK 生產者ack機制 Kafka 生產者的 ACK 機制指的是生產者在發送消息后&#xff0c;對消息副本的確認機制。ACK 機制可以幫助生產者確保消息被成功寫入 Kafka 集群中的多個副本&#xff0c;并在需要時獲取確認信息。 Kafka 提供了三種…

ei源刊和ei會議的幾個區別

1、含義不同 公開發表論文&#xff0c;可以在期刊上刊登&#xff0c;也可以在會議上宣讀。ei源刊對應的是期刊&#xff0c;是指被ei檢索收錄的工程類的期刊。ei會議對應的是會議&#xff0c;是指被ei檢索收錄的會議。 2、檢索類型不同 期刊和會議都能被ei檢索&#xff0c;但…

Tr0ll

信息收集 探測主機存活信息&#xff1a; nmap -sn --min-rate 10000 192.168.182.0/24Starting Nmap 7.94 ( https://nmap.org ) at 2023-11-14 15:45 CST Nmap scan report for 192.168.182.1 Host is up (0.00026s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap…

qt 雙緩沖機制

在圖形編程中&#xff0c;雙緩沖機制是一種常用的技術&#xff0c;用于減少圖形繪制時的閃爍和抖動。它的基本思想是將圖形繪制到一個后臺緩沖中&#xff0c;然后一次性將后臺緩沖的內容顯示到屏幕上。 在 Qt 中&#xff0c;雙緩沖機制可以通過QPainter的begin()和end()方法來實…

Linux環境下socket本地通信

最近項目有用到了socket本地通信&#xff0c;故復習一下。之前都是基于本地虛擬機的ip地址通信的&#xff0c;現在項目&#xff0c;Linux單板上面有2個進程需要通信&#xff0c;故用到了本地socket通信&#xff0c;主要其實就是用了sockfd,文件描述符&#xff0c;也叫句柄。 服…

java接入gpt開發

前情提要 本次文章使用編譯器為IDEA2020 使用GPT模型為百度旗下的千帆大模型 如果是個人用或者不流傳出去&#xff0c;可以無腦入&#xff0c;因為會免費送20塊錢&#xff08;夠用上萬次&#xff09; 代金卷查看 正式教程&#xff1a; 百度智能云控制臺 (baidu.com) 按照步…

JMS(Java Message Service)使用指南

介紹 JMS即Java消息服務&#xff08;Java Message Service&#xff09;應用程序接口&#xff0c;是一個Java平臺中關于面向消息中間件&#xff08;MOM&#xff09;的API&#xff0c;用于在兩個應用程序之間&#xff0c;或分布式系統中發送消息&#xff0c;進行異步通信。它是一…

基于單片機智能病床呼叫系統設計

**單片機設計介紹&#xff0c;基于單片機智能病床呼叫系統設計 文章目錄 一 概要二、功能設計設計思路 三、 軟件設計原理圖 五、 程序六、 文章目錄 一 概要 基于單片機的智能病床呼叫系統是一種利用單片機技術設計的醫療設備&#xff0c;它能夠幫助病人在住院期間快速、方便…

國內大廠機器人賽道產品

大疆 大疆無人機自然不必說&#xff0c;除此之外大疆搞機甲大師&#xff0c;教育機器人。 字節 當前字節在機器人領域只是初步探索階段&#xff0c;目前尚未發布相關產品&#xff08;截止至23.12&#xff09;。 管理層想法&#xff1a; 跟已有業務做結合&#xff0c;服務好…

Java設計模式分類

java的設計模式大體上分為三大類&#xff1a; 創建型模式&#xff08;5種&#xff09;&#xff1a;工廠方法模式&#xff0c;抽象工廠模式&#xff0c;單例模式&#xff0c;建造者模式&#xff0c;原型模式。 結構型模式&#xff08;7種&#xff09;&#xff1a;適配器模式&am…

傳感器(一) :IMU / 陀螺儀模塊

IMU / 陀螺儀模塊 一、概述二、注意參數2.1 陀螺儀芯片標準&#xff08;MPU6050)2.2 參數說明 三、IMU模式使用注意事項3.1 IMU模塊安裝注意事項3.2 為什么IMU要安裝在機器中心位置 四、常見陀螺儀芯片品牌 一、概述 IMU全稱為慣性測量單元&#xff0c;可以通過測量物體在三維空…

Linux實用操作

一、各類小技巧&#xff08;快捷鍵&#xff09; 1.1 ctrl c 強制停止 Linux某些程序的運行&#xff0c;如果想要強制停止它&#xff0c;可以使用快捷鍵ctrl c 命令輸入錯誤&#xff0c;也可以通過快捷鍵ctrl c&#xff0c;退出當前輸入&#xff0c;重新輸入 1.2 ctrl d…

Leetcode刷題筆記——摩爾投票法

摩爾投票法的核心思想為對拼消耗。 摩你媽&#xff0c;學不會&#xff01;&#xff01;&#xff01;&#xff01; 229. 多數元素 II - 力扣&#xff08;LeetCode&#xff09;

ReLU(Rectified Linear Unit)和Sigmoid激活函數

ReLU&#xff08;Rectified Linear Unit&#xff09;和Sigmoid都是神經網絡中常用的激活函數。 特點&#xff1a; ReLU是一種簡單而有效的激活函數。它對于正數部分直接返回輸入&#xff0c;對于負數部分返回零。這種非線性轉換有助于網絡學習更復雜的表示。ReLU在許多深度學習…

自治調優!人大金倉解放DBA雙手

數據庫系統的性能是確保整個應用系統高效運轉的關鍵因素&#xff0c;因此數據庫性能調優工作至關重要。KingbaseES通過將人工調優過程內化為數據庫內核&#xff0c;成功實現了自治調優。這種創新的調優方案為DBA提供了更高效且準確的性能調優途徑&#xff0c;同時也顯著降低了數…

23秋 操作系統真題回憶

總結&#xff1a; 量大 綜合性強 結合實驗很緊密具體的題目 不是很記得了 只記了大概希望有人可以一起參與把這個題的答案做出來&#xff0c;有可以的 可以私信我謝謝 需要你們的想法因為可能涉及學校內部的試題&#xff0c;禁止轉載 2013題目 真題 2023題目 進程 代碼執…

構建VREP和MATLAB聯合仿真實驗平臺,控制機械臂末端按照固定軌跡移動

構建VREP和MATLAB聯合仿真實驗平臺&#xff0c;控制機械臂末端按照固定軌跡移動。主要工作如下&#xff1a; &#xff08;1&#xff09;solidworks構建機械臂模型&#xff1b; &#xff08;2&#xff09;將solidworks中構建的模型導入VREP中建立機械臂的多體動力學模型&#xf…