首页 > 编程语言 >C++ 异常处理

C++ 异常处理

时间:2024-03-04 15:47:29浏览次数:25  
标签:语句 处理 抛出 代码 C++ try catch 异常

菜鸟教程

C语言中文网

程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:
1) 语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。

2) 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。

3) 运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。

运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。C++ 提供了异常(Exception)机制,让我们能够捕获运行时错误,给程序一次“起死回生”的机会,或者至少告诉用户发生了什么再终止程序。

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。

异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
#include <iostream>
#include <string>
using namespace std;
int main(){
    string str = "http://c.biancheng.net";
    char ch1 = str[100];  //下标越界,ch1为垃圾值
    cout<<ch1<<endl;
    char ch2 = str.at(100);  //下标越界,抛出异常
    cout<<ch2<<endl;
    return 0;
}

运行代码,在控制台输出 ch1 的值后程序崩溃。下面我们来分析一下原因。

at() 是 string 类的一个成员函数,它会根据下标来返回字符串的一个字符。与[ ]不同,at() 会检查下标是否越界,如果越界就抛出一个异常;而[ ]不做检查,不管下标是多少都会照常访问。

所谓抛出异常,就是报告一个运行时错误,程序员可以根据错误信息来进一步处理。

上面的代码中,下标 100 显然超出了字符串 str 的长度。由于第 6 行代码不会检查下标越界,虽然有逻辑错误,但是程序能够正常运行。而第 8 行代码则不同,at() 函数检测到下标越界会抛出一个异常,这个异常可以由程序员处理,但是我们在代码中并没有处理,所以系统只能执行默认的操作,也即终止程序执行。

 

 

如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:

try
{
   // 保护代码
}catch( ExceptionName e1 )
{
   // catch 块
}catch( ExceptionName e2 )
{
   // catch 块
}catch( ExceptionName eN )
{
   // catch 块
}

如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。

抛出异常

您可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。

double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}

捕获异常

我们可以借助 C++ 异常机制来捕获上面的异常,避免程序崩溃。捕获异常的语法为:

try{
    // 可能抛出异常的语句
}catch(exceptionType variable){
    // 处理异常的语句
}

trycatch都是 C++ 中的关键字,后跟语句块,不能省略{ }。try 中包含可能会抛出异常的语句,一旦有异常抛出就会被后面的 catch 捕获。从 try 的意思可以看出,它只是“检测”语句块有没有异常,如果没有发生异常,它就“检测”不到。catch 是“抓住”的意思,用来捕获并处理 try 检测到的异常;如果 try 语句块没有检测到异常(没有异常抛出),那么就不会执行 catch 中的语句。

这就好比,catch 告诉 try:你去检测一下程序有没有错误,有错误的话就告诉我,我来处理,没有的话就不要理我!

catch 关键字后面的exceptionType variable指明了当前 catch 可以处理的异常类型,以及具体的出错信息。我们稍后再对异常类型展开讲解,当务之急是演示一下 try-catch 的用法,先让读者有一个整体上的认识。

#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main(){
    string str = "http://c.biancheng.net";
  
    try{
        char ch1 = str[100];
        cout<<ch1<<endl;
    }catch(exception e){
        cout<<"[1]out of bound!"<<endl;
    }
    try{
        char ch2 = str.at(100);
        cout<<ch2<<endl;
    }catch(exception &e){  //exception类位于<exception>头文件中
        cout<<"[2]out of bound!"<<endl;
    }
    return 0;
}

结果

(
[2]out of bound!

第一个 try 没有捕获到异常,输出了一个没有意义的字符(垃圾值)。因为[ ]不会检查下标越界,不会抛出异常,所以即使有错误,try 也检测不到。换句话说,发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到。所谓抛出异常,就是明确地告诉程序发生了什么错误。

第二个 try 检测到了异常,并交给 catch 处理,执行 catch 中的语句。需要说明的是,异常一旦抛出,会立刻被 try 检测到,并且不会再执行异常点(异常发生位置)后面的语句。本例中抛出异常的位置是第 17 行的 at() 函数,它后面的 cout 语句就不会再被执行,所以看不到它的输出。

说得直接一点,检测到异常后程序的执行流会发生跳转,从异常点跳转到 catch 所在的位置,位于异常点之后的、并且在当前 try 块内的语句就都不会再执行了;即使 catch 语句成功地处理了错误,程序的执行流也不会再回退到异常点,所以这些语句永远都没有执行的机会了。本例中,第 18 行代码就是被跳过的代码。

执行完 catch 块所包含的代码后,程序会继续执行 catch 块后面的代码,就恢复了正常的执行流。

为了演示「不明确地抛出异常就检测不到异常」,大家不妨将第 10 行代码改为char ch1 = str[100000000];,访问第 100 个字符可能不会发生异常,但是访问第 1 亿个字符肯定会发生异常了,这个异常就是内存访问错误。运行更改后的程序,会发现第 10 行代码产生了异常,导致程序崩溃了,这说明 try-catch 并没有捕获到这个异常。

关于「如何抛出异常」,我们将在下节讲解,这里重点是让大家明白异常的处理流程:

抛出(Throw)--> 检测(Try) --> 捕获(Catch)

catch 块跟在 try 块后面,用于捕获异常。可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。

try
{
   // 保护代码
}catch( ExceptionName e )
{
  // 处理 ExceptionName 异常的代码
}

上面的代码会捕获一个类型为 ExceptionName 的异常。如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 ...,如下所示:

try
{
   // 保护代码
}catch(...)
{
  // 能处理任何异常的代码
}

下面是一个实例

 1 #include <iostream>
 2 using namespace std;
 3 double division(int a, int b)
 4 {
 5    if( b == 0 )
 6    {
 7       throw "Division by zero condition!";
 8    }
 9    return (a/b);
10 }
11 int main ()
12 {
13    int x = 50;
14    int y = 0;
15    double z = 0;
16  
17    try 
18    {
19      z = division(x, y);
20      cout << z << endl;
21    }
22    catch (const char* msg)     //cerr应该是固定的关键字?
23    {       
24      cerr << msg << endl;
25    }
26  
27    return 0;
28 }

由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:

Division by zero condition!

try 块中调用了某个函数,该函数又调用了另外的一个函数,这个另外的函数抛出了异常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func_inner(){
    throw "Unknown Exception";  //抛出异常
    cout<<"[1]This statement will not be executed."<<endl;
}
void func_outer(){
    func_inner();
    cout<<"[2]This statement will not be executed."<<endl;
}
int main(){
    try{
        func_outer();
        cout<<"[3]This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}

结果:
Unknown Exception

发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇见 try 才停止。在这个回退过程中,调用链中剩下的代码(所有函数中未被执行的代码)都会被跳过,没有执行的机会了。

 

 

C++ 标准的异常

C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的

定义新的异常

可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:

#include <iostream>
#include <exception>
using namespace std;
 
struct MyException : public exception
{
  const char * what () const throw ()
  {
    return "C++ Exception";
  }
};
 
int main()
{
  try
  {
    cout << "---------------------" << endl;
    throw MyException();
    cout << "---------------------" << endl;
  }
  catch(MyException& e)
  {
    std::cout << "MyException caught" << std::endl;
    std::cout << e.what() << std::endl;
  }
  catch(std::exception& e)
  {
    //其他的错误
  }
}

结果

---------------------
MyException caught
C++ Exception

what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。

 

标签:语句,处理,抛出,代码,C++,try,catch,异常
From: https://www.cnblogs.com/uacs2024/p/18051516

相关文章

  • 关于SAP-APP机器-R3trans -d报错-R3trans: /lib64/libstdc++.so.6: version `GLIBCXX_
    在SAP-应用-APP-机器上执行如下命令报错awpxxx03:prdadm270>R3trans-dR3trans:/lib64/libstdc++.so.6:version`GLIBCXX_3.4.26'notfound(requiredbyR3trans) 其实之前,使用过一种方法解决这个问题,可以参考笔者另一篇文章《关于Redhat-Linux中-compat-sap-c++的说......
  • 对象不能从 DBNull 转换为其他类型,数据库空数据映射实体类的时候如何处理数据
    场景是这样的数据库有几个字段是可以为空的、即插入的时候可以不插这些数据,当一条有‘缺口’的数据回到后端映射实体类的时候,会导致对象不能从DBNull转换为其他类型的错误此时可以编写一个通用的方法来处理这种转换publicstaticTConvertDBNull<T>(objectvalue,Tdefaul......
  • c#之asp.net的管道处理模式pipeline
    管道在进入管道之前首先要知道asp.netisApi所有的请求是由IIS转发给asp.netisApiHttpWorkerRequest监听asp.netisApi中的队列,HttpWorkerRequest是管道的执行入口 注册中间件的过程在响应请求的时候会触发事件,需要Config中配置以下是HtttpAplacation中的部分事件,在请求进......
  • JSON.parse解析字符串报错-SyntaxError: Unexpected token ‘ in JSON at position 报
    “SyntaxError:Unexpectedtoken’inJSONatposition”报错原因是因为解析的字符串对象中,JSON.parse无法识别;JSON.parse可以将标准的json类型数据转换为JavaScript对象,如果数据不是正确的json类型的数据则会控制台报错,可能会阻断代码的正常运行我们可以写一个函数来......
  • AIGC下一步:如何用AI再度重构或优化媒体处理?
    让媒资中“沉默的大多数”再次焕发光彩。邹娟|演讲者编者按AIGC时代下,媒体内容生产领域随着AI的出现也涌现出更多的变化与挑战。面对AI的巨大冲击,如何优化或重构媒体内容生产技术架构?在多样的应用场景中媒体内容生产技术又有着怎样的实践效果?LiveVideoStackCon2023深圳站邀请......
  • 【代码更新】传感器数字信号捕获——数字信号处理
     【代码更新】时序——数字信号处理       捕获数字信号整体代码如下:1//2//3//`definereal_data_source45//`ifdefreal_data_source6//7//5438400一帧DCK8//320*15=4800象元数据9//323*15=4845象元......
  • 数据挖掘概念与技术 - chapter 3 - 数据预处理
    今世界数据库容易受到噪声、缺失值、不一致数据困扰,低质量数据会导致低质量的挖掘结果。数据集成将数据由多个数据源合并成一个一致的数据存储数据规约可以通过将聚集删除荣誉特征或者聚类来降低数据大规模。 数据质量:数据的准确性、完整性、一致性、时效性、可信性、可解释......
  • 【C++ STL容器set 】std::set 的全方位解析
    装载自知乎(虽然有AI辅助操作,但是确实写得好好):【C++STL容器set】std::set的全方位解析-知乎(zhihu.com)<imgsrc="https://pic3.zhimg.com/v2-cc8068b8931c7f65e9a89717e2ab404e_b.jpg"data-size="normal"data-rawwidth="1024"data-rawheight="1024......
  • C++编译过程
     第一步:.cpp→.i第二步:.i→.s  是汇编文件第三步:.s→.o 此时已经算是目标文件的最终形态了,完整后缀是.obj第四步:通过链接器将用的到的库,如.dll和.o文件连接起来,执行.exe就是相当于执行前面连接起来的这堆文件 详细过程和G++调用参考:https://zhuanlan.zhihu.c......
  • C++ map用法总结(整理)
    (转载补充)原文链接:https://blog.csdn.net/sevenjoin/article/details/819438641,map简介map是STL的一个关联容器,它提供一对一的hash。第一个可以称为关键字(key),每个关键字只能在map中出现一次;第二个可能称为该关键字的值(value);map以模板(泛型)方式实现,可以存储任意类型的数......