c++学习笔记(15) 异常处理
异常处理概述:
异常是用一个throw语句抛出,同时用try-catch来捕获,例如一个简单的例子:
#include <iostream> using namespace std; int main(int argc, char *argv[]) { cout << "Enter two number: " << endl; int number1, number2; cin >> number1 >> number2; try { if(number2==0) // 如果除数为零 throw number1; // throw语句抛出异常 cout << number1 << "/" << number2 << " is " << (number1/number2) << endl; } catch (int ex) // catch捕获异常 ex: catch块参数 { // 异常处理 cout << "Excetion: an number " << ex << "can not be divided by zero" << endl; } cout << "Exception end" << endl; return 0; }
C++允许throw任何类型的值。当异常被抛出后,程序的正常执行流程被中断。当catch块捕获i到异常后,就执行里面的代码。
catch快块就像一个函数,其参数与抛出的异常值匹配,而与函数不同的是,catch块调用完毕后,程序控制流程不会返回到抛出异常的地方,而是直接执行catch块后的语句。
异常处理的优点:
将上述的代码改写为函数的形式:
#include <iostream> using namespace std; int quotient(int number1, int number2) { if(number2==0) throw number1; return number1/number2; } int main(int argc, char *argv[]) { cout << "Enter two number: " << endl; int number1, number2; cin >> number1 >> number2; try { int result = quotient(number1, number2); cout << number1 << "/" << number2 << " is " << result << endl; } catch (int) // catch捕获异常 { // 异常处理 cout << "Excetion: an number can not be divided by zero" << endl; } cout << "Exception end" << endl; return 0; }
quotient() 函数抛出异常,调用者的catch块会捕获到这个异常。这种机制允许一个函数给他的调用者抛出异常,否则函数自己必须处理这种异常,或者终止程序。例如错误发生时,一个被调用的函数,尤其是库函数,其自身不知道如何处理异常。库函数可以检测到错误,但只有函数调用者才知道如何处理异常。
异常处理的思路是将错误检测和异常处理分开。
异常类:
C++标准中的异常类
catch块的参数如果是类的话,则可以传递更多的信息
exception类定义在<exception>头文件中,类中包含一个虚函数what(),可以返回异常对象的错误信息
runtime_error是描述运行时错误的标准异常类的基类
overflow_error算术运算溢出
underflow_error溢出
logic_error描述逻辑错误
bad_alloc: new运算符在无法分配内存时抛出的异常
bad_cast是dynamic_cast在转换类型时发生错误所抛出的异常。
invalid_argument: 描述将非法的参数传递给函数时抛出的异常
out_of_range: 值超出允许范围
length_error: 对象大小超过最大允许长度
bad_except: 描述了从未预料的异常处理程序所抛出的异常
例如,对上面的代码进行修改,使用异常类。
#include <iostream> #include <stdexcept> // 包含异常类的头文件 using namespace std; int quotient(int number1, int number2) { if(number2==0) throw runtime_error("Divisor can not be zero"); // 实例化一个runtime_error()对象 return number1/number2; } int main(int argc, char *argv[]) { cout << "Enter two number: " << endl; int number1, number2; cin >> number1 >> number2; try { int result = quotient(number1, number2); cout << number1 << "/" << number2 << " is " << result << endl; } catch (runtime_error& ex) // catch捕获异常, 参数为runtime_error对象 { // 异常处理 cout << ex.what() << endl; } cout << "Exception end" << endl; return 0; }
可以在程序中同时捕获多个地方的异常:
#include <iostream> #include <stdexcept> // 包含异常类的头文件 using namespace std; int quotient(int number1, int number2) { if(number2==0) throw runtime_error("Divisor can not be zero"); // 实例化一个runtime_error()对象 return number1/number2; } double getArea(double radius) { if(radius<0) throw invalid_argument("Radius can not be negative"); return 3.14*radius*radius; } int main(int argc, char *argv[]) { cout << "Enter two number: " << endl; int number1, number2; cin >> number1 >> number2; try { int result = quotient(number1, number2); cout << number1 << "/" << number2 << " is " << result << endl; } catch (runtime_error& ex) // catch捕获异常, 参数为runtime_error对象 { // 异常处理 cout << ex.what() << endl; } double radius; cout << "Enter the radius: " << endl; cin >> radius; try { double area = getArea(radius); cout << "The area of the area is " << area << endl; } catch (invalid_argument& ex) { cout << "Exception: " << ex.what() << endl; } cout << "Exception end" << endl; return 0; }
自定义异常类:
C++允许定义自己的异常类。异常类与其他c++类没有什么差别,但是自定义的异常类应派生自exception类,这样就能够应用exception类中的一些公共特性(例如what()函数):
例如:定义一个派生自Geometric类的类Triangle, 在对Triangle的属性(边长)进行初始化和修改的时候,应该满足三角形三条边之间的关系,否则应该抛出异常,可以定义一个TriangleException来描述这个异常。
TriangleException.h文件 // 包含了类的实现,这种内联方式实现对于简短的函数来说效率更高
#ifndef TRIANGLEEXCEPTION_H #define TRIANGLEEXCEPTION_H #include <stdexcept> using namespace std; // 自定义异常类 TriangleException // TriangleException类派生自logic_error class TriangleException: public logic_error { private: // 数据域 double side1; double side2; double side3; // 派生类中,如果没有显示的调用基类的构造函数, // 则在派生类的构造函数中会缺省调用基的无参构造函数, // 因为 logic_error类没有无参的构造函数, // 所以在这里需要显示调用基类有参数的构造函数。 // 调用logic_error("Invalid triangle")设置了一个错误信息 // 当异常对象调用what()时就会返回错误信息 public: TriangleException(double side1, double side2, double side3):logic_error("Invalid triangle") { this->side1 = side1; this->side2 = side2; this->side3 = side3; } double getSide1() const { return side1; } double getSide2() const { return side2; } double getSide3() const { return side3; } }; #endif
对triangle类的定义:
#ifndef TRIANGLE_H #define TRIANGLE_H #include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h" // 异常类头文件 #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" // 基类头文件 #include <cmath> class Triangle: public Geometric { private: double side1; double side2; double side3; bool isValid(double side1, double side2, double side3) { return (side1<side2+side3)&&(side2<side1+side3)&&(side3<side2+side1); } public: Triangle() { side1 = 1; side2 = 2; side3 = 3; } Triangle(double side1, double side2, double side3) { if (!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); // 不满足三边关系则抛出异常 this->side1 = side1; this->side2 = side2; this->side3 = side3; } double getSide1() const { return side1; } double getSide2() const { return side2; } double getSide3() const { return side3; } void setSide1(double side1) { if(!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); // 不满足三边关系则抛出异常 this->side1 = side1; } void setSide2(double side2) { if(!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); this->side2 = side2; } void setSide3(double side2) { if(!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); this->side3 = side3; } double getPerimeter() const { return side1 + side2 + side3; } double getArea() const { double s = getPerimeter()/2; return sqrt(s*(s-side1)*(s-side2)*(s-side3)); } }; #endif
main.cpp文件
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h" // 异常类头文件 #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\Triangle.h" using namespace std; int main(int argc, char *argv[]) { try { Triangle tria(3,3,4); tria.setSide1(1); cout << "The Area is " <<tria.getArea() << endl; //tria.setSide1(1); } catch (TriangleException& ex) { cout << ex.what() << endl; cout << "The sides are " << ex.getSide1() << " " << ex.getSide2() << " " << ex.getSide3() << endl; } //displayGeometric(g1); //displayGeometric(circle1); // 超类型的变量引用子类型的对象 //displayGeometric(rec1); //cout << "rec1 area is " << rec1.getArea() << endl; //cout << equalArea(circle1, rec1); //cout << "circle area is " << circle1.getArea() << endl; //cout << "The circle and rectangle area is equal? " << ((equalArea(circle1, rec1))?"Yes":"No") << endl; return 0; }
运行结果:// 捕获异常
多重异常捕获
一个try-catch模块可能包含多个catch语句。可以处理tr语句抛出的各种异常。
例如,对于前面三角形的例子,可以在定义一个异常类NoPositiveSideException(存在负的边长时也抛出异常)
NoPositiveSideException.h文件
#ifndef NOPOSITIVESIDEEXCEPTION_H #define NOPOSITIVESIDEEXCEPTION_H #include <stdexcept> using namespace std; class NoPositiveSideException: public logic_error { private: double side; //double side2; //double side3; public: NoPositiveSideException(double side): logic_error("No-Positive side!") { this->side = side; } double getSide() { return side; } }; #endif
对Triangle类的修改:
#ifndef TRIANGLE_H #define TRIANGLE_H #include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h" // 异常类头文件 #include "E:\back_up\code\c_plus_code\chapter15\external_file\NoPositiveSideException.h" // 异常类头文件 #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" // 基类头文件 #include <cmath> class Triangle: public Geometric { private: double side1; double side2; double side3; bool isValid(double side1, double side2, double side3) { return (side1<side2+side3)&&(side2<side1+side3)&&(side3<side2+side1); } public: Triangle() { side1 = 1; side2 = 2; side3 = 3; } Triangle(double side1, double side2, double side3) { if(side1<=0) throw NoPositiveSideException(side1); if(side2<=0) throw NoPositiveSideException(side2); if(side3<=0) throw NoPositiveSideException(side3); if (!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); // 不满足三边关系则抛出异常 this->side1 = side1; this->side2 = side2; this->side3 = side3; } double getSide1() const { return side1; } double getSide2() const { return side2; } double getSide3() const { return side3; } void setSide1(double side1) { if(side1<=0) throw NoPositiveSideException(side1); if(!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); // 不满足三边关系则抛出异常 this->side1 = side1; } void setSide2(double side2) { if(side2<=0) throw NoPositiveSideException(side2); if(!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); this->side2 = side2; } void setSide3(double side2) { if(side3<=0) throw NoPositiveSideException(side3); if(!isValid(side1, side2, side3)) throw TriangleException(side1, side2, side3); this->side3 = side3; } double getPerimeter() const { return side1 + side2 + side3; } double getArea() const { double s = getPerimeter()/2; return sqrt(s*(s-side1)*(s-side2)*(s-side3)); } }; #endif
main.cpp文件
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\TriangleException.h" // 异常类头文件 #include "E:\back_up\code\c_plus_code\chapter15\external_file\NoPositiveSideException.h" // 异常类头文件 #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\Triangle.h" using namespace std; int main(int argc, char *argv[]) { cout << "Enter the three sides: " << endl; double side1, side2, side3; cin >> side1 >> side2 >> side3; try { Triangle tria(side1, side2, side3); //tria.setSide1(1); cout << "The Area is " <<tria.getArea() << endl; //tria.setSide1(1); } catch (TriangleException& ex) //多重异常捕获 { cout << ex.what() << endl; cout << "The sides are " << ex.getSide1() << " " << ex.getSide2() << " " << ex.getSide3() << endl; } catch (NoPositiveSideException& ex) // 多重异常捕获 { cout << ex.what() << endl; cout << "The side " << ex.getSide() << " is negative" << endl; } return 0; }
多个不同的异常类可以派生自同一个基类,如果catch的参数是基类的异常对象,则它能够捕获所有派生类的异常对象。还有catch模块的次序也很重要!派生类的catch在前,基类的catch在后
注:
catch的参数可以为(...),这同样的catch能捕获所有类型的异常。这种catch应放在所有的catch之后,作为默认异常处理程序,捕获所有没有被之前catch模块所捕获的异常。
异常的传播
在 try语句中发生异常的时候,c++会由前到后依次检查每个catch模块,检查异常对象是否与catch模块参数的类型相匹配。
重抛出异常:
一个异常被捕获后,他可以被重新抛出给函数的调用者。
#include <iostream> #include <stdexcept> // 包含异常类的头文件 using namespace std; void f1() { try { throw runtime_error("Exception in f1"); } catch (exception& ex) { cout << ex.what() << endl; cout << "Exception caught in f1" << endl; throw; // 重抛出异常runtime_error() } } int main(int argc, char *argv[]) { try { f1(); // f1内部抛出异常,处理后再重抛出异常 } catch (exception& ex) // 捕获被重新抛出的异常 { cout << "Exception caught in main" << endl; cout << ex.what() << endl; } return 0; }
运行结果:
异常说明:
可以在函数的头部声明这个函数可能抛出的异常类型有哪些:
例如:
void f1() throw(runtime_error, logic_error) // 异常说明,函数可能会抛出那些异常类,throw(ExceptionList) { try { throw runtime_error("Exception in f1"); throw logic_error("Logic error"); } }
throw()称为空异常说明,放置于函数头后,说明函数不能抛出任何异常。
异常类型列表中如果有bad_exception,则函数抛出一列表中未定义的异常时,会抛出一个bad_exception异常,如果列表中没有bad_exception,发生这种情况时程序会终止。
------------------------------------------------------end---------------------------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)