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

猜你喜欢

转载自blog.csdn.net/zj1131190425/article/details/84973442