可以跳过直接看总结
目录
1、基本异常语法
throw ,然后对应的try.....catch ...
2、异常向上抛,一直到顶层
异常会一层一层的向上抛,如果异常抛到顶层还没有被处理,那么此时程序会使用terminate()去终止程序,c++的异常处理机制是跨函数的。
3、栈解旋
所以throw很类似return,其实就是局部变量离开它的工作空间,会自动的调用析构函数
4、异常接口声明
在函数上声明throw只会抛出指定的数据类型,如果是其他的数据类型会报错。
但是下面在vs2022没有报错
//声明这个函数只会抛出int float char三种类型的异常,抛出其他的就会报错
void func()throw (int, float, char) {
throw "abc";
}
//不能抛出任何异常
void func02()throw() {
throw -1;
}
int main() {
try{
func02();
}
catch (char* str) {
cout << str << endl;
}
catch (int e) {
cout << "异常" << endl;
}
catch (...) {//捕获所有异常
cout << "未知类型异常" << endl;
}
}
5、抛出异常对象
- throw的异常是有类型的,可以是数字、字符串、类对象
- throw的异常是有类型的,catch需严格匹配异常类型
class MyExcept {
public :
void what() {
cout << "抛出一个异常";
}
};
void func03() {
throw MyExcept();//抛出了一个匿名对象
}
int main() {
try{
func03();
}
catch (MyExcept e) {
e.what();
}
}
下面是对对象加了点东西
上面内存泄露了
需要加上析构函数~MyExcept(){if(error!=NULL)delete[]error;}。而且拷贝的时候也会出错
因此正常代码如下
class MyExcept {
public:
char* error;
public :
MyExcept(char* str) {
error = new char[strlen(str) + 1];
strcpy(error, str);
}
MyExcept(const MyExcept& ex) {
this->error = new char[strlen(ex.error) + 1];
strcpy(this->error, ex.error);
}
MyExcept& operator=(const MyExcept& ex) {
if (this->error != NULL) {
delete[]this->error;
this->error = NULL;
}
this->error = new char[strlen(ex.error) + 1];
strcpy(this->error, ex.error);
}
~MyExcept() {
if (error != NULL)
delete[]error;
}
void what() {
cout << error << endl;
}
};
void func03() {
char* str ;
strcpy(str, "这是一个异常");
throw MyExcept(str);//抛出了一个异常对象
}
int main() {
try{
func03();
}
catch (MyExcept e) {
e.what();
}
}
5、异常类型和异常变量的声明周期
上一个小段的生命周期为
throw的匿名函数会首先调用异常对象的构造函数,然后被catch捕获会调用拷贝函数,
这里普通元素异常函数处理完成后就会被析构
将上面catch改为引用,引用也是catch处理完成后调用析构函数
换成指针
既然不能用匿名函数,那么可以new一个,然后在处理完成后再析构
6、异常标准类
参考c++标准库异常以及编写自己的异常类的写法_xujianjun229的博客-CSDN博客
由于标准库的异常是有限的,因此需要编写自己的异常类
但是编写异常类需要注意以下几点
① 建议自己的异常类要继承标准异常类。因为 C++中可以抛出任何类型的异常,所以我们 的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开 发时。
② 当继承标准异常类时,应该重载父类的 what 函数和虚析构函数。
③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供 自己的复制构造函数
下面是使用标准库异常的例子
这里使用上图的out_of_range,可以看到属于<stdexcept>,因此包含那个头文件
下面写自己的异常类,这个异常类继承于exception
7、继承在异常中的应用
这里写个能处理各种异常的情况
注意析构函数必须定义而不能仅仅是声明,因此加上{}
class BaseMyException {
public:
virtual void what() = 0;
virtual ~BaseMyException() {};
};
class sourceException:public BaseMyException {
public:
virtual void what() {
cout << "目标空间为空" << endl;
}
virtual ~sourceException() {}
};
class tagetException :public BaseMyException {
public:
virtual void what() {
cout << "目标空间为空" << endl;
}
virtual ~tagetException() {}
};
void copy_str(char* taget,const char* source) {
if (taget == NULL)throw sourceException();
if (source == NULL)throw tagetException();
while (*source != '\0') {
*taget = *source;
taget++;
source++;
}
*taget = '\0';
}
int main() {
const char* source = "abncdedf";
char buf[1024] = { 0 };
try
{
copy_str(NULL, source);
}
catch (BaseMyException& e)
{
e.what();
}
cout << buf << endl;
}
8、总结
1、异常向上抛,一直到顶层,到顶层还不处理会终止程序
2、栈解旋,throw很类似return,函数里进行throw后,函数定义的局部变量会被析构
3、异常接口声明,void func()throw (int, float, char) {...}声明以后,函数里只能抛出这三种异常,但是在windows下貌似不起作用
4、throw的异常是有类型的,可以是数字、字符串、类对象,这个类对象可以是自己写的一个类,比如类里面有个string变量存放消息,throw后使用构造函数给这个变量赋值,然后catch调用打印函数,这个类也可以是继承标准异常类。
5、对于标准库异常类的使用有两种方法,标准库也和第四条一样有一个变量,以out_of_range类为例,第一种是扔出一个out_of_range对象,即throw out_of_range(“这里给变量赋值”);第二种方法是写一个类继承标准库异常类,定义一个存放信息的变量,写自己的构造方法给变量赋值,重写what()方法,重写析构函数,然后catch时用基类统一接收
6、异常处理完成后对象就会被析构,即异常对象的生命周期在catch后结束
7、写各种情况的异常类,这些类继承于一个基类,在catch时只用基类接收就可以