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---------------------------------------------------------

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @   Alpha205  阅读(167)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示