ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C++——异常

2026/9/10 18:06:56 拓冰建站 浏览量
C++——异常


异常

C语言中的异常

在C语言里,我们传统的处理错误的方式只有两种:

  1. 手动条件判断并终止程序,比如assert断言;
  2. 返回错误码, 然后对照错误码找错误。

但是,无论是哪种方法,最后都会面临一种问题:

C语言中的错误码都只是告诉你大致错误的类型,却不会告诉你到底哪里出错了,而且也不会告诉特别详细的错误,所以让修bug的人往往直接开摆

但是,C++中便有了新的处理错误的方式:抛异常

C++中的异常

在C++里,如果某一个函数有自身无法处理的错误,便可以进行抛异常。抛出异常后,该函数立即终止,返回到上一层函数中;如果上一个函数也没办法处理这个异常,则接着抛出到更外一层,一直到能接收这个异常为止。


C++处理异常的方法

异常的抛出和捕获

在C++中,有三个处理异常的关键字:

  • throw:当问题出现时,便会通过throw抛出异常,结束该函数并将异常抛到上一层函数
  • catch:捕获异常,当异常被抛出时,catch可以接收到异常,并判断是否可以被捕获
  • try:try是catch的先制条件,catch只可以捕获try中的异常

直接看概念非常抽象,不妨看一个例子:

void push_back(char* a,char ins, int num, int size)
{//num代表数组元素个数,size代表数组大小//如果数组元素个数等于数组大小,则表示数组已满,抛出异常if (num == size){throw "数组已满";}//如果没有抛出异常,则程序正常运行//如果抛出了异常,则throw相当于return,往后不会继续运行a[num] = ins;
}void test()
{char a[1];try {push_back(a, 'c', 0, 1);}//catch只会捕获try中的异常,然后将异常捕获到s中//这里函数没有抛出异常,catch捕获了个寂寞,所以不走catch语句catch (const char* s){cout << s << endl;}try {push_back(a, 'c', 1, 1);}//这里函数抛出了异常,catch捕获到异常:“数组已满”,并将异常存储到s中//于是程序输出“数组已满”,然后该test函数继续运行catch (const char* s){cout << s << endl;}}int main()
{test();
}

但是可能会出现一种情况:catch中捕获的变量和抛出异常的变量类型不一样,那又是个啥效果?

void push_back(char* a, char ins, int num, int size)
{if (num == size){throw "数组已满";}a[num] = ins;
}void test()
{char a[1] = { 'c' };try {push_back(a, 'c', 1, 1);}//抛出和捕获的不一样catch (int i){cout << i << endl;}
}int main()
{test();
}

但是,是不是只要捕获的类型和抛出的类型不一样,程序就会报错呢?也不是

void push_back(char* a, char ins, int num, int size)
{if (num == size){throw "数组已满";}a[num] = ins;
}void test()
{char a[1] = { 'c' };try {push_back(a, 'c', 1, 1);}//抛出和捕获的不一样catch (int* i){cout << i << endl;}//当该函数无法处理这个异常(即捕获的类型和抛出的类型不一样)//则函数也会立即结束,抛出异常cout << "test" << endl;
}int main()
{//在main函数再捕获一次try {test();}//一直到最后一个函数也无法处理这个错误,程序才会终止并报错catch (const char* s){cout << s << endl;}
}

当本函数无法处理的时候,则会抛出异常到再外一层,然后本函数立即终止, 往复循环,一直到能完美捕获到这个异常为止

总结以上:假设我们有函数1,2,3,栈帧调用顺序为1->2->3,运行到了函数3

  1. 如果没有达到异常条件,没有抛出异常,则函数和程序都正常运行
  2. 如果3达到异常条件,抛出了异常,则该throw相当于return,函数3立即销毁,并将异常信息返回到函数2try对应的catch中。
  3. 如果函数2的catch类型可以匹配,则运行catch中的内容,然后程序再继续正常运行。
  4. 如果函数2的catch类型无法匹配,则函数2立即销毁,将该异常信息继续向函数1抛出。
  5. 如果函数1的catch类型可以匹配,则函数1运行catch中的内容,然后函数1继续正常运行
  6. 如果函数1的catch类型无法匹配,则函数1立即销毁。如果函数1是主函数,则代表该异常无法被捕获到,程序报错

同时,为了避免一个try可能收到多种异常类型,catch也可以根据类型写出不同的情况:

void test()
{try{//可能会返回多种异常类型func();}catch (int i){//...}catch (string s){//...}catch (char c){//...}//表示其他的所有情况catch (...){//...}
}

 异常的再抛出

catch在捕获到异常时,有时该函数并不会处理异常,而是在将该异常信息进行修正后,再去将异常抛到下一层,起到传球的作用

void throw_error()
{string s="error";throw s;
}void func1()
{try {throw_error();}//修正异常信息,表示异常来自于函数func1//一定要用引用,抛出的是最初的异常信息catch (string& s){s += "_from_func1";//再抛出throw;}
}void func2()
{try {throw_error();}//修正异常信息,表示异常来自于函数func2catch (string& s){s += "_from_func2";//再抛出throw;}
}int main()
{try {func1();func2();}catch(const string& s){cout << s << endl;}
}

异常的安全

异常的一大缺点便是——会产生内存泄漏的问题

void func1()
{int* a = new int[10];if (1){throw "error";}//如果产生异常,则a的空间不会被释放delete[]a;
}

 如果程序中产生了异常,则后面的代码段不会继续运行。一般情况下,不会出什么问题,但是如果后面进行了delete或者free,那就会出大问题——内存泄漏了。

一般来说,我们只能通过RAII来解决这一问题。但是RAII是什么?别急,在智能指针中马上就会有讲解。