适合具备 C 语言基础的 C++ 入门教程(十二)

引言

在前面的教程中,叙述了模板函数以及模板类的相关概念,在本节教程中,笔者将着重叙述 C++中的异常机制,所谓异常,是程序在执行期间产生的问题,异常提供一种转移程序控制权的方式。而且异常处理主要涉及到三个关键字:try、catch、throw,下面将对异常这个概念进行详细叙述。

异常的引入

为什么要引入异常这个机制呢,假设有如下一个调用关系:

A ----> B ----> C

那如果说是,C函数中出现了一个问题,那要将这个问题找到,就需要在 C函数里返回一个值,然后根据这个值一级一级地向上处理,有多少层就要往上传递多少层,下面是伪代码示意图:

int C()
{
    return XXX;
}

int B()
{
    if (C())
    	{...}
    else
        return -Err;
}

int A()
{
    if (B())
    {...}
    else
    {/*处理问题*/}
}

由上述这样一个伪代码可以看出,如果通过返回值这样地方式来处理子函数出现地问题的时候,那么当函数调用很深的时候,这种处理方式的弊端也就显现出来了,代码会看起来很冗长,且略显复杂,所以也就有了异常处理机制。

异常处理机制

在最开始讲述这个概念的时候,我们依旧采用刚刚所述的那个背景,A ----> B ----> C,再有了异常机制之后,我们就可以用异常的处理机制来解决这个问题,那基于这样一个背景,同样是C函数里出现了问题,我们用依据话来概括异常,也就是函数A捕捉函数C发出的异常。我们将上述这句话进行剖析也就剖析成如下这样几句话:

  • 1、谁捕捉异常?A
  • 2、谁抛出异常?C
  • 3、捕捉到异常怎么处理?随A

基于上述这样一个机制,我们来简单地编写一个异常处理代码:

void C()
{
	throw 1;
}

void B()
{
	C();
}

void A()
{
	try
    {
		B();
	} 
    catch (i)
	{
		cout<<"catch exception "<<i<<endl;
	}
}

上述代码啥意思呢,try后面接一个函数调用,它的意思实际也就是说,试着运行一下B(),如果B()中存在问题,那么就捕获这个错误,这里的catchC()函数里的throw 1是所对应起来的。程序运行结果如下所示:

image-20210226110245582

也就说,throw的值是多少,那么这里捕获的变量也就是多少。

我们继续丰富上面的代码,如果说抛出的异常有很多个该如何进行处理呢?我们同样是基于刚才的背景进行叙述,函数之间的调用关系跟上述一样,我们给出各个函数的代码:

void C(int i)
{
	int a = 1;
	double b= 1.2;
	float c = 1.3;

	if (i == 0)
	{
		cout<<"In C, it is OK"<<endl;
	}
	else if (i == 1)
		throw a;
	else if (i == 2)
		throw b;
	else 
		throw c;
}

void B(int i)
{
	cout<<"call C ..."<<endl;
	C(i);
	cout<<"After call C"<<endl;
}

void A(int i)
{
	try 
	{
		B(i);
	} 
	catch (int j)
	{
		cout<<"catch int exception "<<j<<endl;
	} 
    catch (double d)
	{
		cout<<"catch double exception "<<d<<endl;
	} 
	catch (...){
		cout<<"catch other exception "<<endl;
	}
}

我们会根据传入到 B()中的参数,依次抛出不同类型的异常,然后去设置不同的捕获方式,在编写捕获代码的时候,涉及到其他的这个选项,可以用...来替代,下面是主函数的代码,:

int main(int argc, char **argv)
{
	int i;

	if (argc != 2)
	{
		cout<<"Usage: "<<endl;
		cout<<argv[0]<<" <0|1|2|3>"<<endl;
		return -1;
	}

	i = strtoul(argv[1], NULL, 0);

	A(i);
	
	return 0;
}

其中上述中的主函数中的argc表示的是当前输入的参数个数,而argv[1]表示的是输入的第二个参数,通过strtoul将字符串转换为整型。下面是函数运行的结果:

image-20210226162409999

通过上述可以看到运行程序时输入的命令是./exception3 2,那么这个时候也就是说argc的值等于2,而argv[0]的值等于./exception3(字符串),argv[1]的值等于2(字符)。

上述中,我们讲述了异常处理机制时在扔出各个类型的异常时的处理方法,在整个C++教程中,贯穿始终的一直是这个概念,那么对于C++来说,抛出异常的时候可以抛出异常么,答案是可以的。下面我们就来看一个抛出异常的代码,代码如下所示,首先是类的代码:

class MyException
{
public:
    void what(void) {cout << "This is MyException" << endl;}
};

紧接着,我们来编写各个函数的代码:

void C(int i)
{
	int a = 1;
	double b= 1.2;
	float c = 1.3;

	if (i == 0)
	{
		cout<<"In C, it is OK"<<endl;
	}
	else if (i == 1)
		throw a;
	else if (i == 2)
		throw b;
	else if (i == 3)
		throw c;
	else if (i == 4)
		throw MyException();
}

void B(int i)
{
	cout<<"call C ..."<<endl;
	C(i);
	cout<<"After call C"<<endl;
}

void A(int i)
{
	try 
    {
		B(i);
	} 
    catch (int j)
	{
		cout<<"catch int exception "<<j<<endl;
	} 
    catch (double d)
	{
		cout<<"catch double exception "<<d<<endl;
	} 
    catch (MyException &e) /* 捕获类异常 */
	{
		e.what();
	} 
    catch (...)
    {
		cout<<"catch other exception "<<endl;
	}
}

上述代码就是异常扔出的一个例子,通过上述可见,当传入的参数为4的时候,抛出的异常就是,throw MyException();,在捕获异常的时候,方法也是跟之前的一样。

如果在上述类的基础上,又多出来一个子类的异常,又该如何编写呢,下面是子类继承自父类的代码:

class MySubException : public MyException
{
public:
	void what(void) { cout<<"This is MySubException"<<endl; }
};

抛出异常的代码与之前的MyException是一样的,代码如下:

void C(int i)
{
	int a = 1;
	double b= 1.2;
	float c = 1.3;

	if (i == 0)
	{
		cout<<"In C, it is OK"<<endl;
	}
	else if (i == 1)
		throw a;
	else if (i == 2)
		throw b;
	else if (i == 3)
		throw c;
	else if (i == 4)
		throw MyException();
	else if (i == 5)
		throw MySubException();
}

捕获异常的代码与之前相比,就需要额外注意几个地方了,代码如下所示:

void A(int i)
{
	try 
    {
		B(i);
	} 
    catch (int j)
	{
		cout<<"catch int exception "<<j<<endl;
	} 
    catch (double d)
	{
		cout<<"catch double exception "<<d<<endl;
	} 
    catch (MyException &e)
	{
		e.what();
	} 
    catch (MySubException &e)
	{
		e.what();
	} 
    catch (...)
    {
		cout<<"catch other exception "<<endl;
	}
}

B()函数与主函数代码的写法与之前一致,基于此,我们编译当前的代码,编译结果如下所示:

image-20210226164620229

通过警告信息,我们可以知道,MySubException这个异常的会被更早地被MyException捕获,也就是说实际上MySubException这个异常是不会被捕获到的,下面是程序运行的结果:

image-20210226164938499

可以看到,即便输入的参数是5,但是捕获到的异常是MyException的。

如何更改呢这个错误呢,其实也很简单,只要在捕获MyExceptionMySubException时,将向后顺序进行颠倒就好了,修改之后的代码如下:

    /*省略之前的代码*/
	catch (MySubException &e)
	{
		e.what();
	} 
    catch (MyException &e)
	{
		e.what();
	} 
	/*省略之后的代码*/

这样一来就能够捕获到子类的异常了。那除了上述这种按照顺序捕获父类和子类的异常以外,我们也可以使用多态的方法来捕获到子类的异常,抛出异常的代码和之前的一样 ,B 函数的代码和主函数的代码与之前一样,我们先来看捕获的代码。

void A(int i)
{
	try 
    {
		B(i);
	} 
    catch (int j)
	{
		cout<<"catch int exception "<<j<<endl;
	} 
    catch (double d)
	{
		cout<<"catch double exception "<<d<<endl;
	} 
    catch (MyException &e)
	{
		e.what();
	} 
    catch (...)
    {
		cout<<"catch other exception "<<endl;
	}
}

可以看到,在捕获类的异常的代码的时候,并没有关于MySubException的捕获代码,但是又是如何实现对于MySubException的捕获呢?我们继续来看类的代码:

class MyException 
{
public:
	virtual void what(void) { cout<<"This is MyException"<<endl; }
};

class MySubException : public MyException
{
public:
	void what(void) { cout<<"This is MySubException"<<endl; }
};

上述代码中,可以看到MyException中的 void what(void); 方法前加了virtual,也就是虚函数,从而实现了多态,这个时候,就能够捕获到MySubException的异常。

另外一方面,我们在编写抛出异常的代码的时候,也可以采用如下的方式事先声明抛出哪些类型的异常,代码如下所示:

void C(int i) throw(int, double)
{
	int a = 1;
	double b= 1.2;
	float c = 1.3;

	if (i == 0)
	{
		cout<<"In C, it is OK"<<endl;
	}
	else if (i == 1)
		throw a;
	else if (i == 2)
		throw b;
	else if (i == 3)
		throw c;
	else if (i == 4)
		throw MyException();
	else if (i == 5)
		throw MySubException();
}

我们看到,在上述中,我们只声明了抛出异常的类型只有intdouble,那么也就是说不能够再去抛出其他类型的异常了,即便下面写的代码中有抛出其他类型,其他代码在上述的基础上不变,我们看代码执行的结果:

image-20210226173027356

可以看到,这个时候,就出错了,并没有能够抛出MyException异常。

错误处理函数修改

通过上述的错误信息,我们可以看到当程序执行错误的时候,会终止,并输出terminate called after throwing an instance of 'MyException',实际上这个错误信息,我们可以自定义输出,在出现错误的时候,大致分为两类,一个是unexpected函数,一个是terminate函数,我们都可以重新定义这两个函数,首先,我们在主函数编写如下两句话:

int main(int argc, char** argv)
{
    /* 省略相关代码 */
    set_unexpected (my_unexpected_func);
	set_terminate(my_terminate_func);
    /* 省略相关代码 */
}

然后,我们来实现这两个函数:

void my_unexpected_func() 
{
	cout<<"my_unexpected_func"<<endl;
}

void my_terminate_func () { cout<<"my_terminate_func"<<endl; }  

这个时候,我们再执行代码,程序出错的时候,就会输出如下所示的信息:

image-20210226174031134

小结

本节的内容到此结束,所涉及的代码可以通过百度云链接的方式获取:

链接:https://pan.baidu.com/s/1xRJ5fePEQU2-bY1l7Ti1Kw
提取码:s6wn

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Dev-C++是一个自由开放源代码的C/C++集成开发环境。它基于Mingw或GCC编译器,而且它是Windows操作系统下的一个本地应用程序。它是在Delphi环境下开发的,这使得它具有图形化的用户界面,并且容易上手使用。 Dev-C++已经有很长时间没有更新了,其最新版本是Dev-C++ 5.11,该版本支持C++11规范的一些新功能。不过,由于开发者长时间不更新,Dev-C++已经变得有些陈旧,无法完全支持最新的C++标准。所以,如果你需要使用最新的C++特性,那么Dev-C++可能不是最好的选择。 除此之外,Dev-C++还有一些其他的限制,例如它对大型项目的支持不够完善,也没有充足的调试工具。因此如果你需要开发更大型的项目,可以使用其他更为专业的集成开发环境,例如Visual Studio和Code::Blocks等。 总的来说,Dev-C++是一个简单易用的C++编程工具,特别适合初学者熟练C++语言基础知识。如果你想更好地理解C++的底层原理,或者只是想做一些简单的编程作业,那么Dev-C++可能很适合你。 ### 回答2: Dev-C++ 5.11是一款免费的C/C++编程软件,是一个基于Mingw或者TDM-GCC编译器的全功能的集成开发环境(IDE),具备丰富的功能。该软件是由Bloodshed Software公司开发的,后由Orwell开发、维护和更新。该软件简单易用,是初学者以及一些C语言爱好者常用的编程软件之一。 Dev-C++ 5.11的主要特点包括: 1. 集成了GCC、G++和GDB编译器和调试器,可以编写、调试和编译C/C++程序。 2. 支持多种编译选项,可以根据需要定制编译选项。 3. 支持多种编辑模式,包括C++、C、Pascal等语言,同时支持语法高亮、代码折叠等功能。 4. 支持源代码自动补全、函数跳转、智能提示等功能,可以提高编程效率。 5. 支持多种插件,如代码分析、代码比较、工程管理等。 6. 支持多种调试器,如GDB、CDB、WINDGB等,可以调试本地和远程程序。 Dev-C++ 5.11的界面简洁清晰,易于使用。它不仅适用于Windows平台,而且可以在Linux和Mac OS X等平台上运行。该软件能够使得C语言编程更加便捷高效,是C语言编程入门者的好帮手。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值