|Swift|C++
:-:|:-:|:-:
關鍵字或類型|Error
, throws
, try
, do - catch
, try?
, defer
| throw
, try...catch
C++中的異常處理機制包括:
-
throw
表達式 異常檢測部分使用throw
表達式來表示它遇到了無法處理的問題. 我們說throw
引發(fā)了異常. -
try 語句塊
, 異常處理部分使用try 語句塊
處理異常.try 語句塊
以關鍵字try
開始, 并以一個或多個catch 子句
結束. - 一套異常類, 用于在
throw
表達式和相關的catch 子句
之間傳遞異常的具體信息.
先來介紹 Swift 中的錯誤處理內(nèi)容.
Swift 中你可以使用任意的遵循了Error
協(xié)議的類型來表示錯誤.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
可以使用thow
來拋出一個錯誤并使用thows
來表示一個可以拋出錯誤的函數(shù). 如果在一個函數(shù)中拋出了一個錯誤, 這個函數(shù)會立刻返回并且調(diào)用函數(shù)的代碼會進行錯誤處理.
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
有多種方式用來進行錯誤處理. 一種方式是使用do-catch
.在 do
代碼塊中, 使用 try
來標記可以拋出錯誤的代碼. 在catch
代碼塊中,如果沒有給錯誤命名,則默認為error
:
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
練習: 將 printer name 改為 "Never Has Toner" 使 sendToPrinter(_:) 函數(shù)拋出錯誤。
可以使用多個catch
塊來處理特定的錯誤. 就像寫 switch
中的多個case
一樣:
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
練習: 在 do 代碼塊中添加拋出錯誤的代碼。你需要拋出哪種錯誤來使第一個 catch 塊進行接收?怎么使第二個和第三個 catch 進行接收呢?
另一種處理錯誤的方式是使用try?
將結果轉換為可選的. 如果函數(shù)拋出錯誤,該錯誤會被拋出并且結果為nil
. 否則的話, 結果會是一個包含函數(shù)值的可選值:
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
可以使用defer
代碼塊來表示在函數(shù)返回前, 函數(shù)中最后執(zhí)行的代碼. 無論函數(shù)是否會拋出錯誤, 這段代碼都將執(zhí)行. 使用defer
, 可以把函數(shù)調(diào)用之初就要執(zhí)行的代碼和函數(shù)調(diào)用結束時的掃尾代碼寫在一起,雖然這兩者的執(zhí)行時機截然不同:
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
可以看出** Swift** 的錯誤處理由以下三部分組成:
-
thow
來拋出錯誤 -
do-catch
或try?
來進行錯誤處理. -
Error
錯誤協(xié)議
其中thows
用來標記一個函數(shù)拋出異常的函數(shù), 而do-catch
中do
代碼塊中使用try
來標記可以拋出錯誤的代碼. 而使用defer
來表示不管函數(shù)是否拋出異常都會最后被執(zhí)行的代碼.
在** C++中**也可以使用throw
來拋出異常:
#include <iostream>
using namespace std;
void doSomething(int a) {
if (a) {
cout << "代碼安全:" << a << endl;
}else {
throw "代碼出錯";
}
}
int main() {
doSomething(0);
return 0;
}
如果你運行上面的例子, 你會發(fā)現(xiàn)程序直接崩潰在throw
那行, 這是因為程序發(fā)生了異常但沒有被捕獲,則它將調(diào)用標準庫函數(shù)terminate
終止當前程序.
catch子句可以用來捕獲異常,即 try語句塊
:
#include <iostream>
using namespace std;
void doSomething(int a) {
if (a) {
cout << "代碼安全:" << a << endl;
}else {
throw "參數(shù)出錯";
}
}
int main() {
try {
doSomething(0);
} catch (const char * what) {
cout << "錯誤描述:" << what << endl;
}
return 0;
}
注意到throw
表達式throw "參數(shù)錯誤"
中, "參數(shù)錯誤"
被稱為異常對象, 你可以拋出任意的異常對象,不管是int
類型的還是類類型
.
而關鍵字catch
后面的小括號中的內(nèi)容表示你想要在此 catch 子句
中處理的錯誤類型, 可以帶參數(shù), 那么就可以處理它, 也可以不帶參數(shù),進行統(tǒng)一處理; 當然,你可以寫多個 catch 子句
, 處理不同類型的異常對象; 還可以用...
表示捕獲所有異常:
#include <iostream>
using namespace std;
class ErrorClass {
public:
ErrorClass(int a):code(a) {}
void errorDiscription() {
cout << "錯誤代碼:" << code << endl;
}
int code;
};
void doSomething(int a) {
switch (a) {
case 0:
throw "參數(shù)不能為0";
break;
case -1:
throw -1;
case 1:
throw ErrorClass(a);
case -2:
throw runtime_error("其他異常情況");
default:
cout << "參數(shù)正常: " << a << endl;
break;
}
}
int main() {
try {
doSomething(-2);
cout << "如果拋出了異常, 這里將永遠不會被執(zhí)行" << endl;
} catch (const char * what) {
cout << "錯誤描述:" << what << endl;
} catch (int) {
cout << "int類型的異常對象" << endl;
} catch (ErrorClass error) {
error.errorDiscription();
} catch (...){ // 捕獲所有異常
cout << "默認處理其他的異常" << endl;
}
return 0;
}
可以使用noexcept 說明
指定某個函數(shù)不會拋出異常, 但是如果這個用noexcept
指定的函數(shù)實際上拋出了異常, 則程序會直接調(diào)用 terminate
終止程序:
void something() { // 普通函數(shù), 可能會拋出異常
}
void doSomethingButError() noexcept { // 承諾不會拋出異常
throw exception(); // 違反了異常說明
}
noexcept
說明可以帶 bool實參:
void recoup() noexcept(true); // recoup 不會拋出異常
void alloc(int) noexcept(false); // alloc 可能拋出異常
但更常見的是noexcept 說明
和noexcept 運算符
一起使用, noexcept 運算符
返回一個 bool 類型:
void g();
void f() noexcept(noexcept(g())); // f 和 g 的異常說明一致, g() 可能拋出異常的話, f() 也可能拋出異常; g()不拋出異常, 則 f()也不會拋出異常.
注意, noexcept 有兩層意義: 當跟在函數(shù)參數(shù)列表后面時,它是異常說明符; 而當作為 noexcept 異常說明的 bool 實參出現(xiàn)時, 它是一個運算符.
Swift 中通過遵循Error 協(xié)議來使任意的類型表示錯誤, 而我們知道在 C++中可以使用繼承,多重繼承和虛繼承來實現(xiàn) Swift 中的協(xié)議.
雖然我們可以任意的對象作為異常對象, 當我們也可以繼承便準庫異常類來自定義我們自己的異常類, 然后拋出自定義異常類來達到同樣的目的.
如上圖所示, 在這個標準庫異常類的繼承體系中,層次越低,表示的異常情況就越特殊, 以下我們來設計一個我們自己的異常類:
#include <iostream>
using namespace std;
class my_runtime_error: runtime_error {
public:
// explicit聲明構造函數(shù)時, 它只能使用直接初始化的形式使用,
// 并且編譯器將不會在自動轉換過程中使用該構造函數(shù)(即不會隱式轉換)
explicit my_runtime_error(const std::string s): runtime_error(s), str(s) {}
const char *what() {
return this->str.c_str(); // string 類型轉換成 C 字符串
}
std::string str;
};
class my_logic_error : logic_error {
public:
explicit my_logic_error(const std::string s): logic_error(s){}
my_logic_error(const std::string s, const std::string discription): logic_error(s), logicDiscription(discription) {}
const std::string logicDiscription;
};
// 使用我們自定義的異常類
void initSomething() {
throw my_logic_error("邏輯錯誤:", "此時不應調(diào)用初始化函數(shù)");
}
void runSomething() {
throw my_runtime_error("運行時錯誤: runSomething()");
}
int main() {
try {
// initSomething();
runSomething();
} catch (my_runtime_error error) {
cout << error.what() << endl;
} catch (my_logic_error error) {
cout << error.logicDiscription << endl;
}
return 0;
}