C語言異常處理
異常的概念
- 異常的說明
- 程序在運(yùn)行過程中可能產(chǎn)生異常
- 異常(Exception)與Bug的區(qū)別
- 異常是程序運(yùn)行時(shí)可預(yù)料的執(zhí)行分支
- Bug是程序中的錯(cuò)誤专挪,是不被預(yù)期的運(yùn)行方式
- 異常(Exception)和Bug的對(duì)比:
- 異常
- 運(yùn)行時(shí)產(chǎn)生除0的情況
- 需要打開的外部文件不存在
- 數(shù)組訪問時(shí)越界
- Bug
- 使用野指針
- 堆數(shù)組使用結(jié)束后未釋放
- 選擇排序無法處理長度為0的數(shù)組
- 異常
異常處理
C語言經(jīng)典處理方式:if ... else ...
void func()
{
if(判斷是否產(chǎn)生異常)
{
正常情況代碼邏輯真友;
}
else
{
異常情況代碼邏輯尖奔;
}
}
這里舉一個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
//除法判斷
double divide(double a, double b, int* valid)
{
const double delta = 0.000000000000001;
double ret = 0;
//強(qiáng)制判斷
if( !((-delta < b) && (b < delta)) )
{//正常情況
ret = a / b;
*valid = 1;
}
else
{//異常情況
*valid = 0;
}
return ret;
}
int main(int argc, char *argv[])
{
int valid = 0;
double r = divide(1, 0, &valid);
if( valid )
{
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
運(yùn)行結(jié)果為:
Divided by zero...
這種做法也有缺陷:
- divide函數(shù)有3個(gè)參數(shù),難以理解其用法
- divide函數(shù)調(diào)用后必須判斷valid代表的結(jié)果
- 當(dāng)valid為true時(shí)对省,運(yùn)算結(jié)果正常
- 當(dāng)valid為false時(shí)吭练,運(yùn)算過程出現(xiàn)異常
所以這里再介紹另外一種方法:
- 通過setjmp( ) 和 longjmp( ) 進(jìn)行優(yōu)化
- int setjmp(jmp_buf env)
- 將當(dāng)前上下文保存在jmp_buf結(jié)構(gòu)體中
- void longjmp(jmp_buf env, int val)
- 從jmp_buf 結(jié)構(gòu)體中恢復(fù)setjmp( )保存的上下文
- 最終從setjmp函數(shù)調(diào)用點(diǎn)返回,返回值為val
- int setjmp(jmp_buf env)
舉一個(gè)例子:
#include <iostream>
#include <string>
#include <csetjmp>
using namespace std;
//創(chuàng)建一個(gè)靜態(tài)的結(jié)構(gòu)體
static jmp_buf env;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
//假如程序執(zhí)行到這里伤极,就恢復(fù)setjmp保存的上下文,然后返回1
longjmp(env, 1);
}
return ret;
}
int main(int argc, char *argv[])
{
//將當(dāng)前的上下文保存到env結(jié)構(gòu)體中
if( setjmp(env) == 0 )
{
double r = divide(1, 0);
cout << "r = " << r << endl;
}
else
{
//返回1以后姨伤,與0不相等哨坪,就執(zhí)行到這里
cout << "Divided by zero..." << endl;
}
return 0;
}
執(zhí)行結(jié)果為:
Divided by zero...
這種方法的缺陷是:
- setjmp( )和longjmp( )的引入
- 必然涉及到使用全局變量
- 暴力跳轉(zhuǎn)導(dǎo)致代碼可讀性降低
- 本質(zhì)還是if ... else ... 異常處理方式
最后再介紹一種方法,還是用 if ... else ... 來做乍楚,但是使用宏定義來說明語義:
#include <iostream>
#include <string>
using namespace std;
#define SUCCESS 0
#define INVALID_POINTER -1
#define INVALID_LENGTH -2
#define INVALID_PARAMETER -3
int MemSet(void* dest, unsigned int length, unsigned char v)
{
if( dest == NULL )
{
return INVALID_POINTER;
}
if( length < 4 )
{
return INVALID_LENGTH;
}
if( (v < 0) || (v > 9) )
{
return INVALID_PARAMETER;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; i<length; i++)
{
p[i] = v;
}
return SUCCESS;
}
int main(int argc, char *argv[])
{
int ai[5];
int ret = MemSet(ai, sizeof(ai), 0);
if( ret == SUCCESS )
{
}
else if( ret == INVALID_POINTER )
{
}
else if( ret == INVALID_LENGTH )
{
}
else if( ret == INVALID_PARAMETER )
{
}
return ret;
}
小結(jié)
- 程序中不可避免的會(huì)發(fā)生異常
- 異常是在開發(fā)階段就可以預(yù)見的運(yùn)行時(shí)問題
- C語言中通過經(jīng)典的 if ... else ... 方式處理異常
- C++中存在更好的異常處理方式
C++的異常處理
異常處理介紹
C++內(nèi)置了異常處理的語法元素 try ... catch ...
- try語句處理正常代碼邏輯
- catch語句處理異常情況
- try語句中的異常由對(duì)應(yīng)的catch語句處理
- 語法:
try
{
double r = divide(1, 0);
}
catch(...)
{
cout << "Divided by zero..." << endl;
}
- C++通過throw語句拋出異常信息
double divide(double a, double b)
{
const double delta = 0.0000000000001;
double ret = 0;
if(!((-delta < b) && (b < delta)))
{
ret = a / b;
}
else
{
//產(chǎn)生除0異常
throw 0;
}
return ret;
}
異常處理分析
- 這里對(duì)C++異常處理分析一下:
- throw拋出的異常必須被catch處理
- 當(dāng)前函數(shù)能夠處理異常当编,程序繼續(xù)往下執(zhí)行
- 當(dāng)前函數(shù)無法處理異常,則函數(shù)停止執(zhí)行徒溪,并返回
- throw拋出的異常必須被catch處理
未被處理的異常會(huì)順著函數(shù)調(diào)用棧向上傳播忿偷,知道被處理為止,否則程序?qū)⑼V箞?zhí)行臊泌。
function1 ==> function2 ==> function3
<== <== throw1;
這里舉一個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
//用throw拋出異常信息
throw 0;
}
return ret;
}
int main(int argc, char *argv[])
{
try
{
double r = divide(1, 0);
cout << "r = " << r << endl;
}
catch(...)
{
//try語句中拋出的異常在這里接受并處理
cout << "Divided by zero..." << endl;
}
return 0;
}
繼續(xù)學(xué)習(xí) try... catch ...的知識(shí)點(diǎn):
-
同一個(gè)try 語句可以跟上多個(gè)catch語句
- catch語句可以定義具體處理的異常類型
- 不同類型的異常由不同的catch語句負(fù)責(zé)處理
- try語句中可以拋出任何類型的異常
- catch( ... ) 用于處理所有類型的異常
- 任何異常都只能被捕獲(catch)一次
異常處理的匹配規(guī)則:
//異常處理匹配時(shí)牵舱,不進(jìn)行任何的類型轉(zhuǎn)換
| try
| {
| throw 1;
| }
| catch(Type1 t1)
| {
| }
| catch(Type2 t2)
| {
| }
| catch(TypeN tn)
| {
| }
v
異常拋出后,自上而下嚴(yán)格匹配每一個(gè)catch語句處理的類型
這里用一個(gè)例子來試驗(yàn)一下匹配規(guī)則:
#include <iostream>
#include <string>
using namespace std;
void Demo1()
{
try
{
//這里拋出一個(gè)字符
throw 'c';
}
catch(char c)
{
cout << "catch(char c)" << endl;
}
catch(short c)
{
cout << "catch(short c)" << endl;
}
catch(double c)
{
cout << "catch(double c)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
}
void Demo2()
{
//這里拋出string類字符串
throw string("D.T.Software");
}
int main(int argc, char *argv[])
{
Demo1();
try
{
Demo2();
}
catch(char* s)
{
cout << "catch(char *s)" << endl;
}
catch(const char* cs)
{
cout << "catch(const char *cs)" << endl;
}
catch(string ss)
{
cout << "catch(string ss)" << endl;
}
return 0;
}
執(zhí)行結(jié)果如下:
catch(char c)
catch(string ss)
catch再拋出異常
在try ... catch ... 語句中缺虐,catch語句塊中可以拋出異常
try
{
func();
}
catch(int i)
{
//將捕獲的異常重新拋出
throw i;
}
catch(...)
{
//將捕獲的異常重新拋出
throw;
}
//catch中拋出的異常需要外層的try ... catch ...捕獲
可是為什么要重新拋出異常呢?因?yàn)椋?/p>
catch中捕獲的異辰阜玻可以被重新解釋后拋出高氮,這樣就可以在工程開發(fā)中使用這樣的方式統(tǒng)一異常類型。
//工程開發(fā)
通過調(diào)用 MyFunc 獲得 func函數(shù)的功能和統(tǒng)一的異常信息
|
| 調(diào)用
V
//私有庫
void MyFunc(int i);
/*異常類型為Exception*/
|
| 封裝
V
//第三方庫
void func(int i);
/*異常類型為int*/
這里舉一個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
void Demo()
{
try
{
try
{
//這里拋出的異常由內(nèi)層try ... catch ...來捕獲處理
throw 'c';
}
catch(int i)
{
cout << "Inner: catch(int i)" << endl;
//這里再次拋出的異常由外層來捕獲并處理
throw i;
}
catch(...)
{
cout << "Inner: catch(...)" << endl;
//這里再次拋出的異常由外層來捕獲并處理
throw;
}
}
catch(...)
{
cout << "Outer: catch(...)" << endl;
}
}
/*
假設(shè): 當(dāng)前的函數(shù)式第三方庫中的函數(shù)顷牌,因此剪芍,我們無法修改源代碼
函數(shù)名: void func(int i)
拋出異常的類型: int
-1 ==》 參數(shù)異常
-2 ==》 運(yùn)行異常
-3 ==》 超時(shí)異常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw "Invalid Parameter";
break;
case -2:
throw "Runtime Exception";
break;
case -3:
throw "Timeout Exception";
break;
}
}
}
int main(int argc, char *argv[])
{
Demo();
try
{
MyFunc(11);
}
catch(const char* cs)
{
cout << "Exception Info: " << cs << endl;
}
return 0;
}
運(yùn)行結(jié)果為:
Inner: catch(...)
Outer: catch(...)
Exception Info: Timeout Exception
自定義異常類型
自定義類類型及匹配:
- 異常的類型可以是自定義類類型
- 對(duì)于類類型異常的匹配依舊是自上而下嚴(yán)格匹配
- 賦值兼容性原則在異常匹配中依然適用
- 一般而言
- 匹配子類異常的catch放在上部
- 匹配父類異常的catch放在下部
工程中的異常類:
- 在工程中會(huì)定義一系列的異常類
- 每個(gè)類代表工程中可能出現(xiàn)的一種異常類型
- 代碼復(fù)用時(shí)可能需要重解釋不同的異常類
- 在定義 catch語句塊時(shí)推薦使用引用作為參數(shù)
這里舉一個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
class Base
{
};
class Exception : public Base
{
int m_id;
string m_desc;
public:
Exception(int id, string desc)
{
m_id = id;
m_desc = desc;
}
int id() const
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假設(shè): 當(dāng)前的函數(shù)式第三方庫中的函數(shù),因此窟蓝,我們無法修改源代碼
函數(shù)名: void func(int i)
拋出異常的類型: int
-1 ==》 參數(shù)異常
-2 ==》 運(yùn)行異常
-3 ==》 超時(shí)異常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
//這里直接拋出一個(gè)類
throw Exception(-1, "Invalid Parameter");
break;
case -2:
//這里直接拋出一個(gè)類
throw Exception(-2, "Runtime Exception");
break;
case -3:
//這里直接拋出一個(gè)類
throw Exception(-3, "Timeout Exception");
break;
}
}
}
int main(int argc, char *argv[])
{
try
{
MyFunc(11);
}
//接受到的時(shí)候 判斷為引用類型
catch(const Exception& e)
{
cout << "Exception Info: " << endl;
cout << " ID: " << e.id() << endl;
cout << " Description: " << e.description() << endl;
}
//接受到的時(shí)候 判斷為引用類型
catch(const Base& e)
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
運(yùn)行結(jié)果為:
Exception Info:
ID: -3
Description: Timeout Exception
C++標(biāo)準(zhǔn)庫的異常類族
在C++標(biāo)準(zhǔn)庫中提供了實(shí)用異常類族
- 標(biāo)準(zhǔn)庫中的異常都是從 exception 類派生的
- exception 類有兩個(gè)主要的分支
- logic_error
- 常用于程序中的可避免邏輯錯(cuò)誤
- runtime_error
- 常用于程序中無法避免的惡性錯(cuò)誤
- logic_error

這里演示一下如何使用:
#include <iostream>
#include <string>
#include "Array.h"
#include "HeapArray.h"
using namespace std;
void TestArray()
{
Array<int, 5> a;
/*
這里如果越界會(huì)拋出異常:
throw out_of_range("T& Array<T, N>::operator[] (int index)");
throw out_of_range("T Array<T, N>::operator[] (int index) const");
*/
for(int i=0; i<a.length(); i++)
{
a[i] = i;
}
for(int i=0; i<a.length(); i++)
{
cout << a[i] << endl;
}
}
void TestHeapArray()
{
HeapArray<double>* pa = HeapArray<double>::NewInstance(5);
if( pa != NULL )
{
HeapArray<double>& array = pa->self();
/*
這里如果越界會(huì)拋出異常:
throw out_of_range("T& HeapArray<T>::operator [] (int index)");
throw out_of_range("T HeapArray<T>::operator [] (int index) const");
*/
for(int i=0; i<array.length(); i++)
{
array[i] = i;
}
for(int i=0; i<array.length(); i++)
{
cout << array[i] << endl;
}
}
delete pa;
}
int main(int argc, char *argv[])
{
try
{
TestArray();
cout << endl;
TestHeapArray();
}
catch(...)
{
cout << "Exception" << endl;
}
return 0;
}
小結(jié)
- C++中直接支持異常處理的概念
- try ... catch ...是C++中異常處理的專用語句
- try 語句處理正常代碼邏輯罪裹,catch 語句處理異常情況
- 同一個(gè) try 語句可以跟上多個(gè) catch 語句
- 異常處理必須嚴(yán)格匹配,不進(jìn)行任何的類型轉(zhuǎn)換
- catch語句塊中可以拋出異常
- 異常的類型可以是自定義類類型
- 賦值兼容性原則在異常匹配中依然適用
- 標(biāo)準(zhǔn)庫中的異常都是從exception類派生的