---什么是异常?
异常是面向对象语法处理错误的一种方式。
---C语言传统的处理错误的方式有哪些呢?
1.返回错误码,有些API接口都是把错误码放到errno中。
2.终止程序,比如发生越界等严重问题时,我们也可以主动调用exit(xx)。例如:exit(-1)
---传统的处理错误的缺陷:
a.拿到错误码,需要查找错误码表,才知道是什么错误。
b.如果一个函数是用过返回值拿数据,发生错误时很难处理。
T& operator[](int index)//[]下标运算符不会做越界访问保护
{如果index超出容器范围,如何返回?
}
c.如果调用的函数栈很深,一层层返回错误码,处理很难受
int f1()
{//.....发生错误,返回错误码
}
int f2()
{f1();
}
int f3()
{f2();
}
---面对上面问题,C++用异常来解决:
int x1()//基本使用
{try//检查代码段{vector<int> v = { 1,2,3,4,5,6 };for (int i = 0; i < v.size(); ++i){cout << v.at(i) << " ";//越界访问,抛出异常}cout << endl;}catch (exception& e)//捕获异常,进行处理并输出{cout << e.what() << endl;}return 0;
}
在这里需要了解vector类中,operator [] 下标运算符不会做越界访问保护,不会检查是否越界也不会抛出异常;而at()会进行越界检查,如果越界会抛出out of range的异常,此时我们用try语句来检测它是否抛出异常,并且用catch语句去捕获异常处理它。
---a.需要注意的是,在C++语法中,throw语句抛出的异常是用户自定义的,可以是任意类型。
int ddiv(int n, int m)
{//if (m == 0) throw - 1;//throw可以抛出任意类型的对象if (m == 0){throw "除0异常";//临时拷贝对象,}//可以throw整型/字符串...任意类型return n / m;
}
---b. try语句检测到异常,交给catch语句来捕获处理,是会调用最近的catch语句。
int ff()
{int n, m;cin >> n >> m;try//调用最近的catch{cout << ddiv(n, m) << endl;}catch (const string& x)//会优先调用ff函数内的catch语句{cout << x << endl;}
}
int main()
{try{ff();}catch (const string& x){cout << x << endl;}
}
---c.有多种的catch语句来捕获抛出的不同类型的异常。
int main()
{try{ff();}catch (int x)//捕获抛出的整型异常{cout << x << endl;}catch (const string& x)//捕获抛出的字符串异常{cout << x << endl;}catch (...)//捕获所有类型的异常{cout << "其它异常" << endl;}
}
---C++异常中需要注意的问题:
---a.throw抛出的异常被catch处理后可能会导致内存泄漏,此时catch应该再抛出
double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去。int* array = new int[10];try {int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw;}// ...cout << "delete []" << array << endl;delete[] array;
}
--- b.由于throw可以抛出各种各样的异常,这样就得有各种各样的catch语句去捕获。容易造成混乱,可以通过throw派生类,catch基类--Exception来规范异常处理:你可以自己抛自己的异常,但是必须继承这个基类,但是外层捕获基类就可以。
class Exception
{
public:Exception(const char* errmsg, int errid):_errmsg(errmsg),_errid(errid){}virtual string what()=0;//发生了什么
protected:int _errid;//错误码string _errmsg;//错误描述//stack<string> st;//调用栈帧
};
class SqlException:public Exception
{
public:SqlException(const char* errmsg, int errid):Exception(errmsg, errid){}string what(){return "数据库错误:" + _errmsg;}
};
class Network :public Exception
{
public:Network(const char* errmsg, int errid):Exception(errmsg,errid){}string what(){return "网络错误:" + _errmsg;}
};
void ser()
{//模拟一下出现问题抛异常报错if (rand() % 3 == 0) throw SqlException("数据库启动失败", 1);if (rand() % 7 == 0) throw Network("网络连接失败失败", 3);
}
int main()
{for (size_t i = 0; i < 100; ++i){try{ser();}catch (Exception& e)//用父类对象捕获,抛出的是匿名子类对象,并实现多态->父类对象指向的是子类对象{cout << "未知异常" << endl;}}return 0;
}
---c.异常破坏程序执行流所引发的问题:
//异常带来的问题:new/fopen/lock --func()在此处抛异常-- delete/fclose
//异常规范:异常可能会导致异常安全问题--捕获重新抛出/RAII解决
//函数规范一下,如果抛异常说明,不抛也说明;但是现实中很多人觉得麻烦,不遵守规范,并不实用。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出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 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread(thread&& x) noexcept;
---C++的异常体系:
C++ 提供了一系列标准的异常,定义在库中,我们可以在程序中使用这些标准的异常。它们是以父 子类层次结构组织起来的,如下所示:
总结:
---优点:
//1.清晰的包含错误
//2.面对 T operator[](int i)这样函数越界错误,异常可以很好的解决
//3.多层调用时,里面发生错误,不再需要层层处理,最外层直接捕获即可
//4.很多第三方库使用异常,我们也使用异常可以更好的使用他们。比如:boost\gtest\gmock
---缺点:
//1.异常会导致执行流乱跳。会给我们分析程序Bug带来一些困难。
//2.C++没有GC(资源回收),异常可能导致资源泄露等异常安全问题。需要学会使用RAII来解决。
//3.C++的库里面的异常体系定义不太好用,很多公司会选择自己定义。
//4.C++的异常语言可以抛任意类型的异常,如果项目中没有做很好的规范管理,会非常混乱,所以一般需要定义出继承体系的异常规范。
异常整体而言还是一个利大于弊的东西,所以实际日常练习或小项目,不太使用,公司一般还是会使用异常来处理错误。