【C/C++】详解 assert() 断言(什么是assert? assert有什么作用?) 异常处理

C++异常处理详解 看这一篇就够了

什么是异常? 异常就是在程序执行过程中发生的一些不正常的情况,比如除以0、数组越界等。这些情况会让程序无法继续正常执行,所以我们需要一种机制来处理这些异常。

如何处理异常? 在C++中,我们使用try和catch来处理异常。

try块:把可能产生异常的代码放在try块中。 catch块:如果try块中的代码产生了异常,那么程序会跳转到catch块中执行。 举个例子 假设我们有一个简单的程序,它会让用户输入一个数字,然后计算这个数字的倒数。如果用户输入了0,那么程序就会产生一个异常,因为0没有倒数。

#include <iostream>  
  
int main() {  
    int number;  
    std::cout << "请输入一个数字:";  
    std::cin >> number;  
    try {  
        if (number == 0) {  
            throw "不能除以0!"; // 抛出一个异常  
        }  
        std::cout << "数字的倒数是:" << 1.0 / number << std::endl;  
    } catch (const char* msg) { // 捕获异常  
        std::cout << "发生异常:" << msg << std::endl;  
    }  
  
    return 0;  
}

在这个例子中,如果用户输入了0,程序就会抛出一个异常("不能除以0!"),然后跳转到catch块中执行,输出"发生异常:不能除以0!"。如果用户输入了一个非0的数字,程序就会正常计算并输出这个数字的倒数。

总结 异常处理就是用来处理程序中可能发生的不正常情况的一种机制。在C++中,我们使用try和catch来处理异常。把可能产生异常的代码放在try块中,然后在catch块中处理这些异常。

9 条评论

  • @ 2025-4-10 20:50:48

    C++中除了runtime_error ,还有许多常用的标准库异常类,主要分为以下几类:

    表示逻辑错误

    这类错误理论上可通过阅读代码检测出来,通常由程序员的错误设计导致,在运行时可预测 。

    • std::logic_error:是逻辑错误异常类的基类 。本身一般不直接抛出,主要作为其他逻辑错误异常类的父类存在。
    • std::invalid_argument:当传递给函数的参数无效时抛出。比如编写一个计算圆面积的函数,要求半径不能为负,若传入负数半径,就可抛出该异常。示例代码:
    #include <stdexcept>
    double circleArea(double radius) {
        if (radius < 0) {
            throw std::invalid_argument("Radius must be non - negative");
        }
        // 正常计算逻辑
        return 3.14 * radius * radius;
    }
    
    • std::domain_error:表示值超出函数的定义域范围,常用于数学函数。例如对负数计算平方根时,可抛出该异常。示例代码:
    #include <stdexcept>
    double squareRoot(double num) {
        if (num < 0) {
            throw std::domain_error("Cannot calculate square root of negative number");
        }
        // 正常计算逻辑
        return std::sqrt(num);
    }
    
    • std::length_error:当尝试创建大小超出最大限制的容器时抛出。比如创建一个std::string ,但指定的长度超过了系统允许的最大值,就会抛出此异常 。虽然实际中较难触发,但在一些特殊的容器操作场景下可能出现。
    • std::out_of_range:用于表示容器的访问超出范围。如访问数组或std::vector 等容器中不存在的索引时会触发。示例代码:
    #include <iostream>
    #include <vector>
    #include <stdexcept>
    int main() {
        std::vector<int> v = {1, 2, 3};
        try {
            // 尝试访问超出范围的索引
            std::cout << v[10] << std::endl; 
        } catch (const std::out_of_range& e) {
            std::cerr << "Caught out_of_range exception: " << e.what() << std::endl;
        }
        return 0;
    }
    

    表示运行时错误

    这类错误通常由不可预测的问题导致。

    • std::overflow_error:表示数值溢出错误,如整数溢出或浮点溢出。例如两个较大整数相乘结果超出了整数类型的表示范围,就会抛出该异常。示例代码:
    #include <stdexcept>
    int multiply(int a, int b) {
        int result = a * b;
        if (a != 0 && result / a != b) {
            throw std::overflow_error("Integer multiplication overflow");
        }
        return result;
    }
    
    • std::underflow_error:表示数值下溢错误,常见于浮点数运算。当浮点数运算结果太小,超出了所能表示的范围时抛出 。
    • std::range_error:表示结果超出有效范围,通常在数学或范围验证时触发 。

    与内存管理相关

    • std::bad_alloc:当使用new 操作符分配内存失败时抛出。比如系统内存不足,无法满足程序申请的内存空间时会出现。示例代码:
    #include <iostream>
    #include <new>
    int main() {
        try {
            // 尝试分配大量内存,可能导致失败
            int* ptr = new int[10000000000]; 
        } catch (const std::bad_alloc& e) {
            std::cerr << "Caught bad_alloc exception: " << e.what() << std::endl;
        }
        return 0;
    }
    

    与类型系统相关

    • std::bad_cast:当使用dynamic_cast 进行动态类型转换失败时抛出。例如将一个基类指针转换为不相关的派生类指针时会触发。示例代码:
    #include <iostream>
    #include <typeinfo>
    class Base {};
    class Derived : public Base {};
    int main() {
        Base* basePtr = new Base;
        try {
            // 尝试将Base指针转换为Derived指针,会失败
            Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); 
        } catch (const std::bad_cast& e) {
            std::cerr << "Caught bad_cast exception: " << e.what() << std::endl;
        }
        return 0;
    }
    
    • std::bad_typeid:通常在对空指针调用typeid 时触发。示例代码:
    #include <iostream>
    #include <typeinfo>
    int main() {
        try {
            int* ptr = nullptr;
            // 对空指针调用typeid,会抛出异常
            const std::type_info& info = typeid(*ptr); 
        } catch (const std::bad_typeid& e) {
            std::cerr << "Caught bad_typeid exception: " << e.what() << std::endl;
        }
        return 0;
    }
    

    与I/O操作相关

    • std::ios_base::failure:在输入/输出流操作(如文件流 )失败时抛出。比如打开一个不存在的文件用于读取,或者写入文件时磁盘空间不足等情况。示例代码:
    #include <iostream>
    #include <fstream>
    int main() {
        try {
            std::ifstream file("nonexistent_file.txt");
            if (!file) {
                throw std::ios_base::failure("Failed to open file");
            }
        } catch (const std::ios_base::failure& e) {
            std::cerr << "Caught ios_base::failure exception: " << e.what() << std::endl;
        }
        return 0;
    }
    

    其他

    • std::bad_exception:表示程序异常处理机制无法捕获的异常,通常不会显式抛出,由异常处理机制触发 。在某些特殊的异常传播和处理场景下可能涉及。
    • std::system_error:表示与操作系统相关的错误,通常与低级别操作系统调用失败有关。比如创建进程失败、网络连接失败等与系统底层操作相关的错误情况 。
    • @ 2025-4-10 20:50:13

      1. 什么是 runtime_error

      在C++中,runtime_error 属于标准库异常类的一种,它被定义在 <stdexcept> 头文件里。runtime_error 主要用于表示那些在程序运行时才能检测到的错误,像除零错误、数组越界(如果没有在编译期被检测出来 )、无效的类型转换等。当这类运行时错误发生时,我们可以抛出 runtime_error 异常来让程序进行错误处理。

      2. 为什么要用 runtime_error

      在编写程序时,我们很难提前预知所有可能出现的运行时问题。比如用户输入了非法数据,或者程序在运行过程中遇到了不符合预期的情况。使用 runtime_error 可以让我们在发现这些问题时,及时中断当前错误的执行路径,并通过异常处理机制来进行适当的错误处理,比如提示用户错误信息、记录错误日志,或者让程序优雅地结束等,而不是让程序出现不可控的崩溃情况。

      3. 如何使用 runtime_error

      3.1 包含头文件

      首先,要使用 runtime_error ,必须包含 <stdexcept> 头文件:

      #include <stdexcept>
      

      3.2 抛出 runtime_error 异常

      在代码中,当检测到运行时错误时,就可以使用 throw 语句抛出 runtime_error 异常。例如,编写一个除法函数,当除数为0时抛出异常:

      double divide(double numerator, double denominator) {
          if (denominator == 0) {
              // 抛出runtime_error异常,并附带错误信息
              throw std::runtime_error("Division by zero is not allowed."); 
          }
          return numerator / denominator;
      }
      

      这里通过 throw std::runtime_error("Division by zero is not allowed."); 抛出了一个 runtime_error 异常,异常信息为 "Division by zero is not allowed." ,它描述了发生错误的原因。

      3.3 捕获并处理 runtime_error 异常

      在调用可能抛出 runtime_error 异常的函数时,需要使用 try - catch 块来捕获并处理异常:

      #include <iostream>
      #include <stdexcept>
      
      double divide(double numerator, double denominator);
      
      int main() {
          double num1 = 10, num2 = 0;
          try {
              double result = divide(num1, num2);
              std::cout << "Result: " << result << std::endl;
          } catch (const std::runtime_error& e) {
              // 捕获runtime_error异常
              std::cerr << "Error: " << e.what() << std::endl; 
          }
          return 0;
      }
      
      double divide(double numerator, double denominator) {
          if (denominator == 0) {
              throw std::runtime_error("Division by zero is not allowed.");
          }
          return numerator / denominator;
      }
      

      在这个例子中:

      • main 函数的 try 块中调用了 divide 函数。
      • divide 函数抛出 runtime_error 异常时,catch 块(catch (const std::runtime_error& e) )会捕获这个异常。
      • catch 块中,通过 e.what() 可以获取到异常的错误信息并输出,这里会输出 "Error: Division by zero is not allowed."

      4. runtime_error 与其他异常类的关系

      C++标准库中有一个异常类层次结构,runtime_errorexception 类的派生类。exception 类是所有标准库异常类的基类,它定义了一个纯虚函数 what() ,用于返回异常的描述信息。runtime_error 继承了 exception 类,并实现了 what() 函数,以便我们可以获取到具体的错误描述。

      5. 实际应用场景

      • 输入验证:当程序接收用户输入时,比如要求用户输入一个正整数作为年龄。如果用户输入了负数或者非数字字符,就可以抛出 runtime_error 异常来提示输入错误。
      • 资源管理:在分配和使用资源(如文件句柄、网络连接等 )时,如果出现无法正常获取资源或者资源使用过程中出错的情况,例如无法打开指定文件,就可以抛出 runtime_error 异常。
      • @ 2025-4-10 20:46:54

        GESP四级的C++异常处理主要考察对异常处理机制的理解和运用,以下是相关教程:

        异常处理基础

        • 异常的概念:异常是程序在运行期间,由于系统环境或者用户操作不当等原因引起的问题,比如除数为0、内存分配失败、文件不存在等。
        • 相关关键字
          • try:后面的复合语句包含的是有可能发生异常的代码。
          • catch:用于捕获抛出的异常,catch关键字后面需要定义异常的类型,当捕获的异常类型匹配时,则会执行大括号中异常处理的代码。
          • throw:用于在程序中抛出异常,可以抛出基本数据类型,也可以抛出自定义的类对象。

        异常处理流程

        • 首先执行try语句中的代码块。
        • 如果发生了异常,就使用throw关键字抛出这个异常。
        • 后面的catch语句会捕获异常,检查异常类型是否匹配。
        • 若类型匹配,则执行对应的异常处理代码,然后会继续执行catch后面的代码,程序不会终止。
        • 如果try中的代码未发生异常,整个catch语句都会被跳过,继续执行try - catch后的代码。

        示例代码

        下面是一个简单的示例,演示了如何处理除数为0的异常:

        #include <iostream>
        using namespace std;
        
        int main() {
            int num1 = 10, num2 = 0;
            try {
                if (num2 == 0) {
                    // 抛出字符串类型的异常
                    throw "除数不能为0"; 
                }
                int result = num1 / num2;
                cout << "结果是:" << result << endl;
            } catch (const char* e) {
                // 捕获字符串类型的异常
                cerr << "错误:" << e << endl; 
            }
            return 0;
        }
        

        函数中的异常处理

        当某段程序(函数)不能处理异常时,可以将异常抛给上一级调用者进行处理。如果函数执行时抛出异常,函数自身没有对异常的处理,则返回上层调用处;如果函数自身有对异常的处理,则先进行处理,不能处理的再返回上层调用处处理。

        例如:

        #include <iostream>
        using namespace std;
        
        // 定义一个可能抛出异常的函数
        void divide(int a, int b) {
            if (b == 0) {
                throw "除数为0异常";
            }
            cout << "两数相除的结果是:" << a / b << endl;
        }
        
        int main() {
            int num1 = 10, num2 = 0;
            try {
                divide(num1, num2);
            } catch (const char* e) {
                cerr << "捕获到异常:" << e << endl;
            }
            return 0;
        }
        

        在这个例子中,divide函数内部抛出了异常,在main函数中进行了捕获和处理。

        自定义异常类型

        C++允许自定义异常类型,通常通过继承std::exception类或者其子类来创建。

        例如,定义一个表示除以零情况的自定义异常类:

        #include <iostream>
        #include <exception>
        using namespace std;
        
        // 自定义异常类,继承自std::exception
        class DivideByZeroException : public std::exception {
        public:
            const char* what() const override {
                return "除数为零异常";
            }
        };
        
        int main() {
            int num1 = 10, num2 = 0;
            try {
                if (num2 == 0) {
                    // 抛出自定义异常类的对象
                    throw DivideByZeroException(); 
                }
                int result = num1 / num2;
                cout << "结果是:" << result << endl;
            } catch (const DivideByZeroException& e) {
                // 捕获自定义异常类的对象
                cerr << "捕获到异常:" << e.what() << endl; 
            }
            return 0;
        }
        

        这样可以更清晰地表示不同类型的异常情况,使代码的可读性和可维护性更好。

        多个catch

        一个try块后面可以跟多个catch块,用于捕获不同类型的异常。当try块中抛出异常时,程序会按顺序检查每个catch块,找到第一个匹配的catch块并执行其代码。

        例如:

        #include <iostream>
        using namespace std;
        
        void testFunction(int choice) {
            if (choice == 1) {
                throw 1;  // 抛出int类型异常
            } else if (choice == 2) {
                throw "错误信息";  // 抛出char*类型异常
            }
        }
        
        int main() {
            int choice = 1;
            try {
                testFunction(choice);
            } catch (int e) {
                cerr << "捕获到int类型异常:" << e << endl;
            } catch (const char* e) {
                cerr << "捕获到char*类型异常:" << e << endl;
            }
            return 0;
        }
        

        在这个例子中,testFunction函数根据choice的值抛出不同类型的异常,main函数中有两个catch块,分别用于捕获int类型和char*类型的异常。

        异常的传播

        如果在try块中调用的函数抛出异常,而当前try块没有匹配的catch块,异常会向上传播到调用该函数的上一级try块(如果有的话),继续寻找匹配的catch块。如果一直没有找到匹配的catch块,最终会调用std::terminate函数终止程序。

        例如:

        #include <iostream>
        using namespace std;
        
        void innerFunction() {
            throw 1;  // 抛出int类型异常
        }
        
        void outerFunction() {
            innerFunction();
        }
        
        int main() {
            try {
                outerFunction();
            } catch (const char* e) {
                cerr << "捕获到char*类型异常:" << e << endl;
            }
            return 0;
        }
        

        在这个例子中,innerFunction抛出int类型异常,outerFunction没有处理这个异常,异常传播到main函数的try块。但main函数中只有捕获const char*类型异常的catch块,没有匹配的catch块,最终会导致程序调用std::terminate终止。

        注意事项

        • trycatch必须配合使用。
        • catch块会按顺序从上到下依次比对异常类型,找到匹配的异常类型后,执行对应的处理代码,执行完毕结束整个try - catch语句。
        • 抛出的异常对象会在匹配成功的catch语句的结束处被析构。
        • 异常对象与catch语句进行匹配时,不会做任何隐式类型转换,除了允许非constconst的转换、派生类到基类的转换以及将数组和函数类型转换为对应的指针。
        • @ 2025-4-10 20:03:39

          什么是异常处理

          在程序运行过程中,可能会遇到各种错误情况,比如除零错误、访问越界、文件打开失败等。异常处理就是一种机制,让程序在遇到这些错误时能有一个合适的处理方式,而不是直接崩溃。C++ 提供了一套异常处理机制,主要通过 trycatchthrow 这三个关键字来实现。

          关键关键字解释

          • try:包含可能会抛出异常的代码。当 try 块中的代码执行时,如果发生异常,程序会立即停止当前代码的执行,转而寻找合适的 catch 块来处理这个异常。
          • throw 语句:用于抛出异常。当程序遇到错误情况时,可以使用 throw 语句抛出一个异常对象。这个异常对象可以是任何类型,比如整数、字符串、自定义类等。
          • catch:用于捕获并处理异常。每个 catch 块会指定一个特定的异常类型,当 try 块中抛出的异常类型与 catch 块指定的类型匹配时,该 catch 块就会被执行。

          示例代码及解释

          下面是一个简单的 C++ 异常处理示例,用于处理除零错误:

          #include <iostream>
          using namespace std;
          
          // 定义一个函数用于执行除法运算
          double divide(double numerator, double denominator) {
              // 检查除数是否为零
              if (denominator == 0) {
                  // 如果除数为零,抛出一个字符串类型的异常
                  throw "Division by zero error!";
              }
              // 如果除数不为零,正常进行除法运算并返回结果
              return numerator / denominator;
          }
          
          int main() {
              double num1, num2;
              cout << "Enter the numerator: ";
              cin >> num1;
              cout << "Enter the denominator: ";
              cin >> num2;
          
              try {
                  // 调用 divide 函数进行除法运算
                  double result = divide(num1, num2);
                  // 如果没有抛出异常,输出计算结果
                  cout << "Result: " << result << endl;
              } catch (const char* msg) {
                  // 捕获并处理抛出的字符串类型的异常
                  cerr << "Error: " << msg << endl;
              }
          
              return 0;
          }
          

          代码执行流程

          1. 输入阶段:程序会提示用户输入被除数和除数。
          2. try 块执行:在 try 块中调用 divide 函数进行除法运算。
          3. 异常抛出:如果除数为零,divide 函数会使用 throw 语句抛出一个字符串异常 "Division by zero error!"
          4. 异常捕获:一旦抛出异常,程序会立即跳出 try 块,寻找匹配的 catch 块。由于抛出的是 const char* 类型的异常,所以会执行对应的 catch (const char* msg) 块。
          5. 异常处理:在 catch 块中,会将错误信息输出到标准错误流 cerr 中。
          6. 程序继续:异常处理完毕后,程序会继续执行 catch 块之后的代码。

          多个 catch 块处理不同类型异常

          #include <iostream>
          using namespace std;
          
          // 定义一个自定义异常类
          class CustomException {
          public:
              CustomException(const char* message) : msg(message) {}
              const char* what() const { return msg; }
          private:
              const char* msg;
          };
          
          // 定义一个函数用于执行除法运算
          double divide(double numerator, double denominator) {
              if (denominator == 0) {
                  // 抛出自定义异常
                  throw CustomException("Division by zero error!");
              }
              return numerator / denominator;
          }
          
          int main() {
              double num1, num2;
              cout << "Enter the numerator: ";
              cin >> num1;
              cout << "Enter the denominator: ";
              cin >> num2;
          
              try {
                  double result = divide(num1, num2);
                  cout << "Result: " << result << endl;
              } catch (CustomException& e) {
                  // 捕获自定义异常并处理
                  cerr << "Custom Error: " << e.what() << endl;
              } catch (...) {
                  // 捕获其他所有类型的异常
                  cerr << "Unknown error occurred." << endl;
              }
          
              return 0;
          }
          

          在这个示例中,定义了一个自定义异常类 CustomException,当除数为零时,抛出该自定义异常。同时,使用了多个 catch 块,一个用于捕获自定义异常,另一个 catch (...) 用于捕获其他所有类型的异常。

          总结

          C++ 的异常处理机制可以帮助我们更好地处理程序中可能出现的错误,提高程序的健壮性。通过 trycatchthrow 关键字,我们可以将正常的业务逻辑和错误处理逻辑分离,使代码更加清晰和易于维护。

          • @ 2024-7-24 22:53:59

            C++中的assert是一个宏,用于在运行时检查某个条件是否为真。如果条件为假,则assert会输出错误信息并终止程序执行。这是开发过程中发现逻辑错误和假设错误的一种重要调试手段。以下是关于C++ assert的详细教程:

            一、assert的基本用法 包含头文件: 在使用assert之前,需要包含头文件(或在旧代码中可能是<assert.h>)。 cpp

            #include <cassert>
            

            基本语法: assert宏的基本语法是assert(expression);,其中expression是需要检查的条件表达式。 cpp

            assert(condition);
            

            如果condition为true,则assert不执行任何操作;如果为false,则输出错误信息并终止程序。

            错误信息: 默认情况下,当assert失败时,会输出包含文件名、行号和失败的条件的错误消息。但是,直接控制输出的错误消息内容是不可能的,因为assert是通过宏实现的。 二、assert的使用场景 检查参数合法性: 在函数入口处使用assert来检查传入参数的合法性。 cpp

            void myFunction(int param) {  
                assert(param > 0); // 确保param大于0  
                // 函数体...  
            }
            

            检测不应发生的情况: 使用assert来检测那些理论上不应该发生的情况,如内部逻辑错误。 cpp

            int result = computeValue();  
            assert(result != -1); // 假设-1表示一个不应该发生的错误状态
            

            单元测试: 在单元测试中,使用assert来验证代码行为是否符合预期。 三、assert的优缺点 优点:

            快速定位错误:assert可以在条件不满足时立即终止程序,帮助开发者快速定位问题。 易于使用:语法简单,不需要额外的错误处理代码。 缺点:

            影响性能:虽然影响通常很小,但在性能敏感的应用中可能需要考虑。 不适用于生产环境:assert主要用于开发阶段,发布的产品代码中通常会被禁用。 四、禁用assert 在发布版本中,通常会禁用assert以避免性能影响。这可以通过定义宏NDEBUG来实现。

            cpp

            #define NDEBUG  
            #include <cassert>  
              
            // 此时,所有的assert调用都将无效  
            void myFunction() {  
                assert(false); // 这将不会执行任何操作  
            }
            

            五、assert与异常处理的比较 assert和异常处理(try-catch)是两种不同的错误处理机制。

            assert用于检测不应该发生的情况,条件不满足时会终止程序。 异常处理用于处理程序运行时可能遇到的预期错误,提供了一种恢复和继续执行的机制。 六、注意事项 每个assert只检验一个条件,以便于在断言失败时快速定位问题。 assert中不应使用改变环境的语句,如自增、自减操作,以避免在禁用assert时导致代码行为不一致。 在使用assert时,应确保它不会掩盖真正的错误或导致程序在禁用时行为异常。 通过以上教程,你应该对C++中的assert宏有了更深入的了解,并能够在开发过程中有效地使用它来帮助调试和定位问题。

            • @ 2024-7-24 22:50:51
              1. 异常处理的进一步理解 异常传递:当try块中的代码抛出一个异常时,如果当前函数中没有匹配的catch块,这个异常会沿着函数调用栈向上传递,直到找到能够处理它的catch块为止。如果最终没有找到匹配的catch块,程序会调用std::terminate()函数终止执行。 多个catch块:一个try块后面可以跟多个catch块,每个catch块用于捕获并处理不同类型的异常。当异常发生时,程序会按顺序检查每个catch块,直到找到匹配的类型为止。 捕获所有异常:如果你想要捕获所有类型的异常,可以使用省略号(...)作为catch块的参数。这被称为“通用捕获”。
              2. 异常处理的示例扩展 下面是一个扩展的示例,展示了如何使用多个catch块和通用捕获来处理不同类型的异常。

              cpp

              #include <iostream>  
              #include <string>  
              using namespace std;  
                
              int main() {  
                  try {  
                      // 假设这里有一些复杂的计算,可能会抛出不同类型的异常  
                      throw 100; // 抛出一个整数类型的异常  
                      // throw "出错了!"; // 也可以抛出一个字符串类型的异常  
                      // throw 3.14; // 或者抛出一个浮点数类型的异常  
                  } catch (int e) {  
                      cout << "捕获到整数类型的异常:" << e << endl;  
                  } catch (const string& e) {  
                      cout << "捕获到字符串类型的异常:" << e << endl;  
                  } catch (double e) {  
                      cout << "捕获到浮点数类型的异常:" << e << endl;  
                  } catch (...) {  
                      cout << "捕获到未知类型的异常" << endl;  
                  }  
                
                  return 0;  
              }
              

              在这个示例中,我们抛出了不同类型的异常,并使用了多个catch块来捕获和处理它们。注意,由于我们一次只能抛出一个异常,所以上面的代码中只有第一个throw语句会生效。你可以通过取消注释不同的throw语句来测试不同类型的异常处理。

              1. 异常处理的最佳实践 避免在析构函数中抛出异常:析构函数的主要目的是清理资源,如果在析构函数中抛出异常,可能会导致程序更加混乱。 谨慎使用异常规格:在C++11及以后的版本中,函数声明中的异常规格(exception specifications)已经被弃用,并建议使用noexcept关键字来替代。然而,对于小学生来说,这部分内容可能过于复杂,可以暂时忽略。 自定义异常类:虽然面向对象的概念不在本次教程的范围内,但值得一提的是,在实际开发中,为了更好地表示和处理异常,通常会定义自己的异常类。这些类通常会从std::exception类派生,并提供额外的信息和方法。不过,对于初学者来说,可以先从使用标准异常类开始。 合理使用异常:异常处理虽然强大,但也不是万能的。在性能敏感的代码区域,或者当错误处理逻辑与正常逻辑紧密耦合时,可能需要考虑其他错误处理机制(如错误码)。
              • @ 2024-7-24 22:49:29

                C++异常处理入门教程 什么是异常? 想象一下,你在做数学题时,突然遇到了一个除数为0的情况,这时你就不能继续按照正常的除法规则来计算了,因为除数为0在数学上是不允许的。在编程中,这种情况就像是一个“异常”,它打断了程序的正常执行流程。

                为什么要处理异常? 处理异常是为了让程序在遇到问题时能够给出一些提示或者采取一些补救措施,而不是直接崩溃或者停止运行。这样,即使程序遇到了错误,我们也能知道错在哪里,并尝试去修复它。

                C++中如何处理异常? 在C++中,处理异常主要用到三个关键字:try、catch和throw。

                throw 当你发现程序中出现了无法处理的情况时,可以使用throw关键字来抛出一个异常。抛出的东西可以是一个数字、一个字符串或者任何你想要的东西,但通常我们会抛出一个错误消息或者一个特定的错误代码。

                示例:

                cpp

                if (除数 == 0) {  
                    throw "不能除以0!";  
                }
                

                try 你可能会问,既然抛出了异常,那谁来处理它呢?这时候就需要用到try块了。我们把可能会抛出异常的代码放在try块里面。如果try块里面的代码抛出了异常,程序就会停止执行try块里的剩余代码,并去寻找能够处理这个异常的catch块。

                示例:

                cpp

                try {  
                    // 这里是可能会抛出异常的代码  
                    if (除数 == 0) {  
                        throw "不能除以0!";  
                    }  
                    // 其他代码...  
                }
                

                catch catch块就是用来捕获并处理异常的。每个catch块后面都会跟着一个异常类型(或者省略类型来捕获所有异常),用来指定这个catch块能够处理哪种类型的异常。当try块抛出了异常时,程序会去寻找与之匹配的catch块,并执行该块中的代码。

                示例:

                cpp

                try {  
                    // ...(可能会抛出异常的代码)  
                } catch (const char* msg) {  
                    // 这里处理捕获到的异常  
                    cout << "捕获到异常:" << msg << endl;  
                }
                

                注意:在这个例子中,我们使用const char*类型来捕获异常,因为我们抛出的是一个字符串。但是,你也可以抛出其他类型的对象,并在catch块中使用相应的类型来捕获它们。

                完整示例 下面是一个完整的示例,展示了如何在C++中使用try、catch和throw来处理除数为0的异常:

                cpp

                #include <iostream>  
                using namespace std;  
                  
                int main() {  
                    int 被除数 = 10;  
                    int 除数 = 0;  
                  
                    try {  
                        if (除数 == 0) {  
                            throw "不能除以0!";  
                        }  
                        cout << "结果是:" << 被除数 / 除数 << endl;  
                    } catch (const char* msg) {  
                        cout << "捕获到异常:" << msg << endl;  
                    }  
                  
                    return 0;  
                }
                

                在这个示例中,当除数等于0时,程序会抛出一个字符串类型的异常,并在catch块中捕获并处理这个异常,输出“捕获到异常:不能除以0!”。

                • @ 2024-7-24 22:46:36

                  C++异常处理教程 一、异常的基本概念 异常是在程序执行过程中发生的不正常情况,它打断了正常的程序流程。异常处理是一种编程机制,允许程序在遇到异常时能够优雅地处理,而不是简单地崩溃。

                  二、异常处理的关键字 C++异常处理主要涉及到三个关键字:try、catch和throw。

                  throw:用于抛出异常。当程序遇到无法处理的错误时,可以使用throw关键字抛出一个异常。抛出的异常可以是C++中的任何类型,包括内置类型(如int、float等)、指针类型、字符串类型或自定义类型。 try:try块用于标识可能引发异常的代码区域。如果try块中的代码抛出了异常,程序将停止执行try块中的剩余代码,并查找与之匹配的catch块来处理异常。 catch:catch块用于捕获并处理异常。每个catch块后面都跟着一个异常声明,用于指定该catch块能够捕获的异常类型。如果抛出的异常与某个catch块后面的异常声明匹配,那么程序将执行该catch块中的代码。 三、异常处理的语法 异常处理的基本语法如下:

                  cpp

                  try {  
                      // 可能引发异常的代码  
                  } catch (异常类型1 变量名1) {  
                      // 处理异常类型1的代码  
                  } catch (异常类型2 变量名2) {  
                      // 处理异常类型2的代码  
                  } // 可以有多个catch块来处理不同类型的异常  
                  catch (...) {  
                      // 处理所有未捕获的异常  
                  }
                  

                  如果try块中的代码抛出了异常,程序将按顺序查找catch块,直到找到与抛出的异常类型匹配的catch块。 如果抛出的异常类型与所有catch块都不匹配,且存在catch (...)块,则执行该块中的代码。catch (...)块用于捕获所有未明确指定的异常类型。 如果抛出的异常没有任何catch块能够捕获,则程序将调用std::terminate()函数终止执行。 四、异常处理的示例 以下是一个简单的异常处理示例:

                  cpp

                  #include <iostream>  
                  using namespace std;  
                    
                  int main() {  
                      int a = 10, b = 0;  
                      try {  
                          if (b == 0) {  
                              throw "除数不能为0!"; // 抛出一个字符串类型的异常  
                          }  
                          cout << "结果是:" << a / b << endl;  
                      } catch (const char* msg) {  
                          // 捕获并处理字符串类型的异常  
                          cerr << "异常:" << msg << endl;  
                      }  
                      return 0;  
                  }
                  

                  在这个示例中,如果b等于0,程序将抛出一个字符串类型的异常。由于try块后面跟着一个能够捕获字符串类型异常的catch块,因此程序将执行该catch块中的代码,输出异常信息。

                  五、异常处理的注意事项 避免在析构函数中抛出异常:析构函数的主要作用是释放资源,如果在析构函数中抛出异常,可能会导致资源泄露或程序崩溃。 异常规格说明(已弃用):在早期的C++标准中,可以使用异常规格说明来限制函数可能抛出的异常类型。然而,这个功能在C++11及以后的版本中已被弃用,因为它限制了程序的灵活性且难以正确使用。 自定义异常类:为了更好地表示和处理异常,可以定义自己的异常类。通常,自定义异常类会从std::exception类派生,并重载what()方法以提供异常信息。 异常的安全传递:在抛出和捕获异常时,应确保异常对象能够安全地传递。特别是当异常对象包含指针或引用时,需要特别注意对象的生命周期和内存管理。 异常的性能考虑:虽然异常处理能够提高程序的健壮性和可维护性,但它也可能对性能产生一定影响。因此,在性能敏感的代码区域中应谨慎使用异常处理。 通过以上教程,你应该对C++中的异常处理有了更深入的理解。在实际编程中,合理地使用异常处理机制可以使你的程序更加健壮和易于维护。

                  • @ 2024-7-24 22:44:50

                    C++异常处理教程(面向小学生) 什么是异常? 异常就是在程序运行时发生的一些“不正常”的事情,这些事情可能会让程序崩溃或者无法继续运行。比如,你尝试除以0,或者数组索引超出了范围。

                    为什么要处理异常? 因为异常可能会导致程序崩溃,而我们希望程序在遇到这些问题时能够给出友好的提示,而不是直接崩溃。所以,我们需要学会如何处理这些异常。

                    如何处理异常? 在C++中,我们使用try和catch来处理异常。

                    try块:把可能会引发异常的代码放在try块中。 catch块:如果try块中的代码引发了异常,那么程序会跳转到catch块中,我们可以在这里处理异常。 举个例子 假设我们有一个程序,它会让用户输入一个数字,然后计算这个数字的倒数。如果用户输入了0,我们就会抛出一个异常,因为0没有倒数。

                    cpp

                    #include <iostream>  
                      
                    int main() {  
                        int number;  
                        std::cout << "请输入一个数字:";  
                        std::cin >> number;  
                      
                        try {  
                            // 检查是否输入了0  
                            if (number == 0) {  
                                // 抛出一个异常  
                                throw "哎呀!你不能输入0,因为0没有倒数!";  
                            }  
                            // 计算并输出数字的倒数  
                            std::cout << "数字的倒数是:" << 1.0 / number << std::endl;  
                        } catch (const char* msg) {  
                            // 捕获到异常,输出异常信息  
                            std::cout << "发生异常了:" << msg << std::endl;  
                        }  
                      
                        return 0;  
                    }
                    

                    在这个例子中,如果用户输入了0,程序就会抛出一个异常("哎呀!你不能输入0,因为0没有倒数!"),然后跳转到catch块中执行,输出"发生异常了:哎呀!你不能输入0,因为0没有倒数!"。如果用户输入了一个非0的数字,程序就会正常计算并输出这个数字的倒数。

                    总结 异常处理就是用来处理程序中可能发生的不正常情况的一种机制。在C++中,我们使用try和catch来处理异常。把可能会引发异常的代码放在try块中,然后在catch块中处理这些异常。这样,即使程序遇到了问题,也能够给出友好的提示,而不是直接崩溃。

                    • 1