Fork me on GitHub

C++ 异常

C++ 异常 exception

《C++ Primer 中文版第五版》 Ch 5.6

1. try 语句块和异常处理

  • 典型的异常
    • 失去数据库连接
    • 遇到意外输入

如果程序的问题是输入无效,则异常处理部分可能会要求用户重新输入正确的数据;

如果丢失了数据库连接,会发出报警信息

  • C++ 中的异常处理包括:
    • throw 表达式(throw expression)
      • 异常检测部分使用throw 表达式来表示它遇到了无法处理的问题。我们说 throw 引发( raise )了异常。
    • try 语句块(try block)
      • 以 关键字 try 开始,并以一个或多个 catch 子句(catch clause) 结束
      • try 语句块中代码抛出的异常通常会被某个 catch 子句处理。因为 catch 子句 “处理” 异常,所以它们也被称作 异常处理代码( exception handler )
    • 一套 异常类(exception class)
      • 用于在 throw 表达式和相关的 catch 子句之间传递异常的具体信息

1.1 throw 表达式

throw + 表达式

表达式类型就是抛出的异常类型

  • 代码示例:

    Sales_item item1, item2;
    cin >> item1 >> item2;
    // 首先检查 item1 和 item2 是否表示同一种书籍
    if (item1.isbn() == item2.isbn())
    {
        cout << item1 + item2 << endl;
        return 0;	// 表示成功
    } else {
        cerr << "Data must refer to same ISBN" << endl;
        return -1;	// 表示失败
    }
    

真实的程序中,应该把对象相加的代码和用户交互的代码分离开来。

改写程序,不在直接输出一条信息,而是抛出一个异常:

// 首先检查两条数据是否是关于同一种书籍的
if (item1.isbn() != item2.isbn())
{
	throw runtime_error("Data must refer to same ISBN");
}
// 如果程序执行到了这里,表示两个 ISBN 是相同的
cout << item1 + item2 << endl;

这里如果 ISBN 不一样就抛出一个异常,该异常是类型 runtime_error 的对象。

抛出异常将终止当前的函数,并把控制权交给能处理该异常的代码

runtime_error标准库异常类型 的一种,定义在 stdexcept 头文件中。必须初始化 runtime_error 的对象,方式是提供给它一个 string 对象或者一个 C 风格的字符串,这个字符串有一些关于异常的辅助信息。

1.2 try 语句块

  • 语法

    try
    {
        program-statements
    } catch (exception-declaration) {
        hadler-statements
    } catch (exception-declaration) {
        hadler-statements
    } // ...
    

    异常声明 exception declaration

1.2.1 编写处理代码

假设执行 Sales_item 对象加法的代码是与用户交互的代码 分离开来的。其中与用户交互的代码负责处理发生的异常,它的形式可能如下:

while (cin >> item1 >> item2)
{
    try 
    {
        // 执行添加两个 Sales_item 对象的代码
        // 如果添加失败,代码抛出一个 runtime_error 异常
    } catch (runtime_error err) {
        // 提醒用户两个 ISBN 必须一致,询问是否重新输入
        cout << err.what()
            << "\nTry Again? Enter y or n" << endl;
        char c;
        cin >> c;
        if (!cin || c == 'n')
            break;		// 跳出 while 循环
    }
}

给用户的提示信息中输出了 err.what() 的返回值。err 的类型是 runtime_error ,因此能推断 what 是 runtime_error 类的一个成员函数。

每个标准库异常类都定义了名为 what 的成员函数,这些函数没有参数,返回值是 C 风格字符串(即 const char*)。其中,runtime_error 的 what 成员返回的是 初始化一个具体对象时所用的 string 对象的副本。如果上一节的代码抛出异常,则本节的 catch 子句输出:

Data must refer to same ISBN
Try Again? Enter y or n

1.2.2 函数在寻找处理代码的过程中退出

一个 try 包含另一个 try,新的 try 可能又包含了另一个 try 语句块的新函数,以此类推

寻找处理代码的过程与此相反

  • 当异常被抛出时,首先搜索抛出该异常的函数。
    • 如果没有找到匹配的 catch 子句
      • 终止该函数,并在调用该函数的函数中继续寻找
      • 如果还是没有找到匹配的 catch 子句,这个新的函数也被终止,继续搜索调用它的函数

以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的 catch 子句为止。

如果最终还是没能找到任何匹配的 catch 子句,程序转到名为 terminate 的标准库函数,执行该函数将导致程序非正常退出

如果一段程序没有 try 语句块且发生了异常,系统会调用 terminate 函数并终止当前程序的执行。

异常中断了程序的正常流程

编写异常安全的代码非常困难,我们必须时刻清除异常何时发生,异常发生后程序应该如何 确保对象有效、资源无泄漏、程序处于合理状态 等等

1.3 标准异常

C++ 标准库定义了一组类,用于报告标准库函数遇到的问题。

4 个头文件:

1.3.1 exception

  • 定义了最通用的异常类 exception
  • 只报告异常的发生,不提供任何额外信息

1.3.2 stdexcept

  • 定义了几种常用的异常类

    定义的异常类
    exception 最常见的问题
    runtime_error 只有在运行时才能检测出的问题
    range_error 运行时错误:生成的结果超出了有意义的值域范围
    overflow_error 运行时错误:计算上溢
    underflow_error 运行时错误:计算下溢
    logic_error 程序逻辑错误
    domain_error 逻辑错误:参数对应的结果值不存在
    invalid_argument 逻辑错误:无效参数
    length_error 逻辑错误:试图创建一个超出该类型最大长度的对象
    out_of_range 逻辑错误:使用一个超出有效范围的值

1.3.3 new

  • 定义了 bad_alloc 异常类型
  • new 申请内存空间如果失败的话抛出这个异常

1.3.4 type_info

  • 定义了 bad_cast 异常类型

1.3.5 初始化

exceptionbad_allocbad_cast 只能默认初始化,也就是无初始值

其他异常类型,应该使用 string 对象或者 C 风格字符串初始化这些类型的对象,创建这类对象时,必须提供初始值

1.3.6 成员函数 what

异常类型之定义了一个名为 what 的成员函数,没有任何参数,返回一个指向 C 风格字符串的 const char*,提供关于异常的一些文本信息。

what 函数返回的 C 风格字符串的内容与异常对象的类型有关。如果异常类型有一个字符串初始值,则 what 返回该字符串。对于其他无初始值的异常类型来说,what 返回的内容由编译器决定。

1.4 练习题

  1. 编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果

    答案: https://ericdai.xyz/archives/di-5-zhang

  • 思路:

    除数不能为0,这里先不用 try-catch 机制,而是直接输出错误信息

  • 题解:

    #include <iostream>
    using namespace std;
    int main()
    {
        int ival1, ival2;
        cin >> ival1 >> ival2;
        if (ival2 == 0)
        {
            cout << "除数不能为 0" << endl;
            return -1;
        }
        cout << "两数相除的结果是:" << ival1 / ival2 << endl;
        return 0;
    }
    

    这里没有考虑输入的数不是 int 类型的情况,这种情况在 1.5 小节中具体实现

  1. 修改你的程序,使得当第二个数是 0 时抛出异常。先不要设定 catch 子句,运行程序并真地为除数输入零,看看会发生什么?

    • 思路:

      通过抛出异常来提示用户的输入错误

    • 题解:

      #include <iostream>
      #include <stdexcept>
      using namespace std;
      int main()
      {
          cout << "Please enter the dividend and the divisor in order:" << endl;
          int ival1, ival2;
          cin >> ival1 >> ival2;
          if (ival2 == 0)
              throw runtime_error("The divisor cannot be zero!");
          cout << "The quotient of two numbers is: " << ival1 / ival2 << endl;
          return 0;
      }
      

      键入 5 0

      结果:

      5 0
      terminate called after throwing an instance of 'std::runtime_error'
        what():  The divisor cannot be zero!
      
      8 4
      The quotient of two numbers is: 2
      
  2. 修改上一题的程序,使用 try 语句块去捕获异常。catch 子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行 try 语句块的内容

    • 题解:

      #include <iostream>
      #include <stdexcept>
      using namespace std;
      int main()
      {
          cout << "Please enter the dividend and the divisor in order: " << endl;
          int ival1, ival2;
          while (cin >> ival1 >> ival2)
          {
              try
              {
                  if (ival2 == 0)
                      throw runtime_error("ERROR! The divisor cannot be zero!");
                  cout << "The quotient of two numbers is: " << ival1 / ival2 << endl;
                  return 0;
              }
              catch (runtime_error err)
              {
                  cout << err.what()
                      << "\nTry Again? Please enter y or n" << endl;
                  char c;
                  cin >> c;
                  if (!cin || c == 'n')
                      break;
              }
          }
          
          return 0;
      }
      

1.5 C++ 数据输入类型异常处理示例

https://blog.csdn.net/weixin_45652508/article/details/108546384

输入一个整数,判断是否为 素数

  • 如果输入的是小数或者其他字符,返回输入错误的提示

  • 主要用到了 atoi 函数,将字符串转换为 整数,如果不是数字,atoi 会返回 0,以此判断输入的是否是 数字

  • atoi 遇到小数不会报错,所以先查找小数点,排除小数的情况

  • 判断素数用到了 sqrt (square root 平方根) 函数,在 库中

#include <iostream>
#include <cstdlib.h>
#include <cmath>

using namespace std;

bool isPrime(int a)
{
    if (a < 0)
    {
        cout << "a is not a prime number" << endl;
        return false;
    }
    for (int i = 2; i <= int(sqrt(a)); ++i)
    {
        if (a % i == 0)
        {
            cout << "a is not a prime number" << endl;
	        return false;
        }
    }
    cout << "a is a prime number!" << endl;
    return true;
}

int main()
{
    string str;
    int a(1);
    cin >> str;
    try
    {
        // 先搜索小数点
        size_t fi = str.find('.');
        // find 函数在找不到的情况下会返回 npos
        if (fi != str.npos)
            throw 1;	// 有小数点,抛出异常 1
        if (str != "0" && str != "1" && str != "2")
        {
            a = atoi(str.c_str());
            if (a == 0)
                throw 2;	// str 无法转换为 int,抛出异常2
            isPrime(a);
        }
        else 
        {
            if (str == "2") cout << "a is a prime number" << endl;
            else cout << "a is not a prime number" << endl;
        }
    }
    catch (int)
    {
        cout << "The input number is illegal" << endl;
        exit(0);
    }
    return 0;
}
posted @ 2023-03-16 14:22  icewalnut  阅读(62)  评论(0编辑  收藏  举报