C++:6.3异常处理
(努力敲...)
(兴奋...)
是否保存?
1、C++异常处理入门(try和catch)
编译器能够保证代码的语法是正确的,但是对逻辑错误和运行时错误却无能为力,例如除数为 0、内存分配失败、数组越界等。这些错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。
string str = "c plus plus";
char ch1 = str[100]; //下标越界,ch1为垃圾值
cout<<ch1<<endl;
char ch2 = str.at(100); //下标越界,抛出异常
cout<<ch2<<endl;
at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与“[ ]”不同,at() 会检查下标是否越界,如果越界就抛出一个异常(错误);而“[ ]”不做检查,不管下标是多少都会照常访问。
在C++中,我们可以捕获上面的异常,避免程序崩溃。捕获异常的语法为:
try{
// 可能抛出异常的语句
}catch(异常类型){
// 处理异常的语句
}
2、
异常既然是一份数据,那么就应该有数据类型。C++规定,异常类型可以是基本类型,也可以是标准库中类的类型,还可以是自定义类的类型。C++语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的类型。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象。
异常被捕获后,会和 catch 所能处理的类型对比,如果正好和 catch 类型匹配,或者是它的子类,那么就交给当前 catch 块处理。catch 后面的括号中给出的类型就是它所能处理的异常类型。上面例子中,catch 所能处理的异常类型是 exception,at() 函数抛出的类型是 out_of_range,out_of_range 是 exception 的子类,所以就交给这个 catch 块处理。
catch 后面的exception e可以分为两部分:exception 为异常类型,e 为 exception 类的对象。异常抛出时,系统会创建 out_of_range 对象,然后将该对象作为“实参”,像函数一样传递给“形参”e,这样,在 catch 块中就可以使用 e 了。
3、
一个 try 后面可以跟多个 catch,异常被捕获时,先和 exception_type_1 作比较,如果异常类型是 exception_type_1 或其子类,那么执行当前 catch 中的代码;如果不是,再和 exception_type_2 作比较……依此类推,直到 exception_type_n。如果最终也没有找到匹配的类型,就只能交给系统处理,终止程序。
4、throw:抛出自己的异常
throw 是C++中的关键字,用来抛出异常。如果不使用 throw 关键字,try 就什么也捕获不到;上节提到的 at() 函数在内部也使用了 throw 关键字来抛出异常。
throw 既可以用在标准库中,也可以用在自定义的函数中,抛出我们期望的异常。throw 关键字语法为:
throw exceptionData;
exceptionData 是“异常数据”的意思,它既可以是一个普通变量,也可以是一个对象,只要能在 catch 中匹配就可以。
5、exception类
C++语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。你可以通过下面的语句来匹配所有标准异常:
try{
//可能抛出异常的语句
}catch(exception &e){
//处理异常的语句
}
之所以使用引用,是为了提高效率。如果不使用引用,就要经历一次对象拷贝(拷贝对象时要调用拷贝构造函数)的过程。
6、 exception 类的直接派生类
logic_error 逻辑错误。
runtime_error 运行时错误。
bad_alloc 使用 new 或 new[ ] 分配内存失败时抛出的异常。
bad_typeid 使用 typeid 操作一个 NULL 指针,而且该指针是带有虚函数的类,这时抛出 bad_typeid 异常。
bad_cast 使用 dynamic_cast 转换失败时抛出的异常。
ios_base::failure io 过程中出现的异常。
bad_exception 这是个特殊的异常,如果函数的异常列表里声明了bad_exception 异常,当函数内部抛出了异常列表中没有的异常时,如果调用的 unexpected() 函数中抛出了异常,不论什么类型,都会被替换为 bad_exception 类型。
7、logic_error 的派生类
异常名称 说 明
length_error 试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作。
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数。
out_of_range 超出有效范围。
invalid_argument 参数不合适。在标准库中,当利用string对象构造 bitset 时,而 string 中的字符不是 ’0’ 或 ’1’ 的时候,抛出该异常。
8、runtime_error 的派生类
range_error 计算结果超出了有意义的值域范围。
overflow_error 算术计算上溢。
underflow_error 算术计算下溢。
----------------------------------------------
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <string>
class myexception : public std::exception {
public:
myexception(std::string s) : exception(s.c_str()) {
}
virtual ~myexception() {
printf("myexception's destructor\n");
}
virtual const char *what() const {
return exception::what();
}
public:
std::string _s;
};
class A {
public:
~A() {
printf("A's destructor\n");
}
};
class B {
public:
~B() {
printf("B's destructor\n");
}
};
void Func() {
A a;
printf("dasd\n");
myexception e("p is NULL");
e._s = "haha";
try{
int *p = NULL;
if (p == NULL) {
throw e;
}
}
catch (myexception &e) {
e._s = "heiheihei";
printf("%s\n", e.what());
throw;
}
printf("%s\n", e._s.c_str());
B b;
}
void Func2() {
try {
Func();
}
catch (myexception &e) {
printf("Func2 : %s\n", e._s.c_str());
throw;
}
}
int main() {
try {
Func2();
}
catch (myexception &e) {
printf("main: %s\n", e._s.c_str());
}
printf("hahah\n");
system("PAUSE");
return 0;
}
这个函数输出为:
dasd
p is NULL
myexception's destructor
A's destructor
Func2 : heiheihei
main: heiheihei
myexception's destructor
hahah
函数说明:
main函数起,调用fun2(),进入fun2(),调用fun(),进入fun()后, 先创建一个A类对象a,
★输出dasd;
再创建一个异常并初始化e(p is NULL),然后给_s赋值haha ;
扔异常,在fun()的catch中接到,类型匹配,重新给_s赋值heiheihei;
★输出what,也就是“p is NULL”;
扔异常,fun()里面没得接,要回到函数调用处,也就是fun2();
回之前,要把局部变量销毁,也就是a 和 e;----------因为局部变量在栈区,所以先销毁e,在销毁a。(先来后出)所以输出★
myexception's destructor
A's destructor
回到fun2();类型匹配,有得接,★打印_s---heiheihei;
再扔,回到main 。因为在fun2()是直接引用的异常,所以不会销毁,也就不会析构。
类型匹配,有得接,打印heiheihei,不扔了,后面没有catch接了,交给系统处理,终止程序。析构。★打印myexception's destructor
★打印hahaha,

(兴奋...)
是否保存?
(思考ing)
?????1、C++异常处理入门(try和catch)
编译器能够保证代码的语法是正确的,但是对逻辑错误和运行时错误却无能为力,例如除数为 0、内存分配失败、数组越界等。这些错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。
string str = "c plus plus";
char ch1 = str[100]; //下标越界,ch1为垃圾值
cout<<ch1<<endl;
char ch2 = str.at(100); //下标越界,抛出异常
cout<<ch2<<endl;
at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与“[ ]”不同,at() 会检查下标是否越界,如果越界就抛出一个异常(错误);而“[ ]”不做检查,不管下标是多少都会照常访问。
在C++中,我们可以捕获上面的异常,避免程序崩溃。捕获异常的语法为:
try{
// 可能抛出异常的语句
}catch(异常类型){
// 处理异常的语句
}
2、
异常既然是一份数据,那么就应该有数据类型。C++规定,异常类型可以是基本类型,也可以是标准库中类的类型,还可以是自定义类的类型。C++语言本身以及标准库中的函数抛出的异常,都是 exception 类或其子类的类型。也就是说,抛出异常时,会创建一个 exception 类或其子类的对象。
异常被捕获后,会和 catch 所能处理的类型对比,如果正好和 catch 类型匹配,或者是它的子类,那么就交给当前 catch 块处理。catch 后面的括号中给出的类型就是它所能处理的异常类型。上面例子中,catch 所能处理的异常类型是 exception,at() 函数抛出的类型是 out_of_range,out_of_range 是 exception 的子类,所以就交给这个 catch 块处理。
catch 后面的exception e可以分为两部分:exception 为异常类型,e 为 exception 类的对象。异常抛出时,系统会创建 out_of_range 对象,然后将该对象作为“实参”,像函数一样传递给“形参”e,这样,在 catch 块中就可以使用 e 了。
3、
一个 try 后面可以跟多个 catch,异常被捕获时,先和 exception_type_1 作比较,如果异常类型是 exception_type_1 或其子类,那么执行当前 catch 中的代码;如果不是,再和 exception_type_2 作比较……依此类推,直到 exception_type_n。如果最终也没有找到匹配的类型,就只能交给系统处理,终止程序。
4、throw:抛出自己的异常
throw 是C++中的关键字,用来抛出异常。如果不使用 throw 关键字,try 就什么也捕获不到;上节提到的 at() 函数在内部也使用了 throw 关键字来抛出异常。
throw 既可以用在标准库中,也可以用在自定义的函数中,抛出我们期望的异常。throw 关键字语法为:
throw exceptionData;
exceptionData 是“异常数据”的意思,它既可以是一个普通变量,也可以是一个对象,只要能在 catch 中匹配就可以。
5、exception类
C++语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception)。你可以通过下面的语句来匹配所有标准异常:
try{
//可能抛出异常的语句
}catch(exception &e){
//处理异常的语句
}
之所以使用引用,是为了提高效率。如果不使用引用,就要经历一次对象拷贝(拷贝对象时要调用拷贝构造函数)的过程。
6、 exception 类的直接派生类
logic_error 逻辑错误。
runtime_error 运行时错误。
bad_alloc 使用 new 或 new[ ] 分配内存失败时抛出的异常。
bad_typeid 使用 typeid 操作一个 NULL 指针,而且该指针是带有虚函数的类,这时抛出 bad_typeid 异常。
bad_cast 使用 dynamic_cast 转换失败时抛出的异常。
ios_base::failure io 过程中出现的异常。
bad_exception 这是个特殊的异常,如果函数的异常列表里声明了bad_exception 异常,当函数内部抛出了异常列表中没有的异常时,如果调用的 unexpected() 函数中抛出了异常,不论什么类型,都会被替换为 bad_exception 类型。
7、logic_error 的派生类
异常名称 说 明
length_error 试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作。
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数。
out_of_range 超出有效范围。
invalid_argument 参数不合适。在标准库中,当利用string对象构造 bitset 时,而 string 中的字符不是 ’0’ 或 ’1’ 的时候,抛出该异常。
8、runtime_error 的派生类
range_error 计算结果超出了有意义的值域范围。
overflow_error 算术计算上溢。
underflow_error 算术计算下溢。
----------------------------------------------
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <string>
class myexception : public std::exception {
public:
myexception(std::string s) : exception(s.c_str()) {
}
virtual ~myexception() {
printf("myexception's destructor\n");
}
virtual const char *what() const {
return exception::what();
}
public:
std::string _s;
};
class A {
public:
~A() {
printf("A's destructor\n");
}
};
class B {
public:
~B() {
printf("B's destructor\n");
}
};
void Func() {
A a;
printf("dasd\n");
myexception e("p is NULL");
e._s = "haha";
try{
int *p = NULL;
if (p == NULL) {
throw e;
}
}
catch (myexception &e) {
e._s = "heiheihei";
printf("%s\n", e.what());
throw;
}
printf("%s\n", e._s.c_str());
B b;
}
void Func2() {
try {
Func();
}
catch (myexception &e) {
printf("Func2 : %s\n", e._s.c_str());
throw;
}
}
int main() {
try {
Func2();
}
catch (myexception &e) {
printf("main: %s\n", e._s.c_str());
}
printf("hahah\n");
system("PAUSE");
return 0;
}
这个函数输出为:
dasd
p is NULL
myexception's destructor
A's destructor
Func2 : heiheihei
main: heiheihei
myexception's destructor
hahah
函数说明:
main函数起,调用fun2(),进入fun2(),调用fun(),进入fun()后, 先创建一个A类对象a,
★输出dasd;
再创建一个异常并初始化e(p is NULL),然后给_s赋值haha ;
扔异常,在fun()的catch中接到,类型匹配,重新给_s赋值heiheihei;
★输出what,也就是“p is NULL”;
扔异常,fun()里面没得接,要回到函数调用处,也就是fun2();
回之前,要把局部变量销毁,也就是a 和 e;----------因为局部变量在栈区,所以先销毁e,在销毁a。(先来后出)所以输出★
myexception's destructor
A's destructor
回到fun2();类型匹配,有得接,★打印_s---heiheihei;
再扔,回到main 。因为在fun2()是直接引用的异常,所以不会销毁,也就不会析构。
类型匹配,有得接,打印heiheihei,不扔了,后面没有catch接了,交给系统处理,终止程序。析构。★打印myexception's destructor
★打印hahaha,