程序调试与异常处理
1 程序调试概述
程序调试是在程序中查找错误的过程,在开发过程中,程序调试是检查代码并验证它是否能够正常运行的有效方式。
2 常用的程序调试操作
常用的程序调试包括断点操作、开始、中断和停止程序的操作、单步执行程序以及使程序运行到指定的位置。
2.1 断点操作
断点通知调试器,应用程序在某点(暂停执行)或某情况发生时中断。发生中断时,称程序和调试器处于中断模式。进入中断模式并不会终止或结束程序的执行,所有元素(如函数、变量和对象)都保留在内存中。执行可以在任何时候继续。
插入断点的方式有三种:
①在要设置断点行旁边的灰色空白行中单击
②右击设置断点的代码行,在弹出的快捷菜单中选择"断点/插入断点"命令
③单击要设置断点的代码行,选择菜单中的"调试/切换代码"命令
插入断点后,就会在设置断点的行旁边的灰色空白处出现一个红色圆点,并且该行代码也呈高亮显示。
删除断点的三种主要方式
①单击设置了断点的代码行左侧的红色圆点
②在设置了断点的代码行左侧的红色圆点上单击鼠标右键,在弹出的快捷菜单中选择删除断点命令
③在设置了断点的代码行上单击鼠标右键,在弹出的快捷菜单中选择"断点/删除断点"命令
④选择菜单中的"调试/删除所有断点"命令
2.2 开始执行
开始执行是最基本的调试功能之一,有三种方式开始执行程序调试。
①从"调试"菜单中选择"启动调试"命令
②在源窗口中右击,可执行代码中的某行,然后从弹出的菜单中选择"运行到光标处"命令
③直接单击工具栏中的启动按钮,启动调试
如果选择"启动调试"命令,则程序启动并一直运行到断点。可以在任何时刻中断执行,以检查值、修改变量或检查程序状态。
如果选择"运行到光标处"命令,则应用程序启动并一直运行到断点或光标位置,具体要看时断点在前还是光标在前,可以在源窗口中设置光标位置,如果光标在断点前面则代码首先运行到光标处。
2.3 中断执行
当执行到达一个断点或发生异常时,调试器将中断程序的执行。选择"调试/全部中断"命令后,调试器将停止所有在调试器下运行的程序的执行。程序并不退出,可以随时恢复执行。调试器和应用程序现在处于中断模式。
①调试带单中的全部中断命令
②通过工具栏中的中断按钮中断执行
③使用快捷键 Ctrl+Alt+Break (pause/break键)
2.4 停止执行
停止执行意味着终止正在调试的进程并结束调试会话
①通过选择菜单中的"调试/停止调试"命令来结束运行和调试
②选择工具栏中的停止按钮停止执行
③停止快捷键Shift+F5
2.5 单步执行和逐过程执行
单步执行意味着调试器每次只执行一行代码,单步执行主要是通过逐语句、逐过程和跳出这三种命令实现的。逐语句和逐过程的主要区别是当某一行包含函数调用时,逐语句仅执行调用本身,然后在函数内的第一个代码行处停止。而逐过程执行整个函数,然后在函数外的第一行处停止。如果位于函数调用的内部并想返回到调用函数时,应使用跳出,跳出将一直执行代码,直到函数返回,然后在调用函数中的返回点处中断。
①当启动调试后,可以单击工具栏中的按钮来实现逐语句、逐过程和跳出这三种操作
②除了在工具栏中单击这三个按钮外,已可以使用快捷键执行这三种操作
逐语句 F11
逐过程 F10
跳出 Shift+F10
2.6 运行到指定位置
如果希望程序运运行到指定的位置,可以在指定代码行上单击鼠标右键,在弹出的快捷菜单中选择运行到光标处命令,这样当程序运行到光标处时就会自动暂停;另外,也可以在指定的位置插入断点,同样可以使程序运行到插入断点的的代码时自动暂停。
3 异常处理概述
在编写程序时,不仅要关心程序的正常操作,还应该检查代码错误及可能发生的各类不可预期的实践。在现代编程语言中,异常处理是解决这些问题的主要方法。异常处理是一种功能强大的机制,用于处理应用程序可能产生的错误或是其他可以中断程序执行的异常情况。异常处理可以捕获程序执行所发生的错误,通过异常处理可以有效、快速地构建各种用来处理程序异常情况的程序代码。
异常处理实际上就相当于大楼失火(发生异常),烟雾感应器捕获到高于正常密度的烟雾(捕获异常),将自动喷水进行灭火(处理异常)。
在.NET类库中,提供了针对各种异常情形所设计的异常类,这些类包含了异常的相关信息。配合异常处理语句,应用程序能够轻易地避免程序执行时可能中断应用程序的各种错误。.NET框架中公共异常类如下表,这些异常类都是System.Execption的直接或间接子类。
异常类 | 说明 |
---|---|
System.ArithmeticException | 在算术运算期间发生的异常 |
System.ArrayTypeMismatchException | 当储存一个数组时,如果由于被存储的元素的实际类型与数组的实际类型不兼容而导致存储失败,就会引发异常 |
System.DivideByZeroException | 在试图用零除整数值时引发 |
System.IndexOutOfRangeException | 在试图用小于零或超出数组界限的下标索引数组时引发 |
System.InvalidCastException | 当从基类型或接口到派生类型的显示转换在运行时失败,就会引发此异常 |
System.NullReferenceException | 在需要使用引用对象的场合,如果使用null引用,就会引发此异常 |
System.OutOfMemoryException | 在分配内存的尝试失败时引发 |
System.OverflowException | 在选中的上下文中所进行的算术运算、类型转换或转换操作导致溢出时引发的异常 |
System.StackOverflowException | 挂起的方法调用过多而导致执行堆栈溢出时引发的异常 |
System.TypeInitializationException | 在静态构造函数引发异常并且没有可以捕捉到它的catch子句时引发 |
4 异常处理语句
C#程序中,可以使用异常处理语句处理异常。主要的异常处理语句有throw语句、try…catch语句和try…catch…finally语句,通过这三个异常处理语句,可以对可能产生异常的代码进行监控。
4.1 try…catch语句
try…catch语句允许在try后面的大括号中{}放置可能发生异常情况的程序代码,对这些程序代码进行监控。在catch后面的大括号{}中则放置处理错误的程序代码,以处理程序发生的异常。try…catch语句的基本格式如下:
try
{
被监控的代码
}
catch(异常类名 异常变量名)
{
异常处理
}
在catch子句中,异常类名必须为System.Exception或从System.Exception派生的类型。当catch子句指定了异常类名和一场变量名后,就相当于声明了一个具有给定名称和类型的异常变量,此异常变量表示当前正在处理的异常。
说明:
只捕捉能够合法处理的异常,而不要在catch子句中创建特殊异常的列表。
实例1:
static void Main(string[] args)
{
try
{
object obj = null;
int N = (int)obj;
}
catch (Exception ex)
{
Console.WriteLine("捕获异常:" + ex );
}
Console.ReadKey();
}
效果:
查看运行结果,抛出了异常。因为声明的object变量obj初始化为null,然后又将obj强制转换为int类型,这样就产生了异常,由于使用了try…catch语句,所以将这个异常捕获,并将异常输出。
实例2:
static void Main(string[] args)
{
try
{
checked
{
int a = 6000000;
int b = 6000000;
int c = a * b;
}
}
catch (OverflowException oex)
{
Console.WriteLine("捕获异常:" + oex);
}
Console.ReadKey();
}
效果:
4.2 throw语句
throw语句用于主动引发一个异常,使用throw语句可以在特定的情况下,自行抛出异常。
通常throw语句与try…catch语句或try…finally语句一起使用,当引发异常时,程序查找处理此异常的catch语句。也可以用throw语句重新引发已捕获的异常。
基本格式如下:
throw ExObject
ExObject:所要抛出的异常对象,这个异常对象时派生自System.Exception类的类对象
实例:
using System;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
try
{
checked
{
Console.WriteLine("请输入分子:");
string str1 = Console.ReadLine();
Console.WriteLine("请输入分母:");
string str2 = Console.ReadLine();
test t1 = new test();
Console.WriteLine("分子除以分母的值:" + t1.MyInt(str1, str2));
}
}
catch (FormatException)
{
Console.WriteLine("请输入数值格式数据" );
}
Console.ReadKey();
}
class test
{
public int MyInt(string a, string b)
{
int int1;
int int2;
int num;
try
{
int1 = int.Parse(a);
int2 = int.Parse(b);
if (int2 == 0)
{
throw new DivideByZeroException();
}
num = int1 / int2;
return num;
}
catch (DivideByZeroException de)
{
Console.WriteLine("用零除整数引发异常~!");
Console.WriteLine(de.Message);
return 0;
}
}
}
}
}
效果:
4.3 try…catch…finally语句
将finally语句与try…catch语句结合,行成try…catch…finally语句。finally语句同样以区块的方式存在,他被放在try…catch语句的最后面,程序执行完毕,最后都会调到finally语句块,执行其中的代码。无论程序是否产生异常,最后都会执行finally语句区块中的程序代码。其基本格式如下:
try
{
被监控的代码
}
catch(异常类名 异常变量名)
{
异常处理
}
...
fianlly
{
程序代码
}
说明:使用catch子句是为了允许处理异常。无论是否引发了异常,使用finally子句即可执行清理代码。如果分配了昂贵或优先的资源(如数据库连接或留),则应将释放这些资源的代码放置在finally块中。
实例:
static void Main(string[] args)
{
string str = "张三";
object obj = str;
try
{
int i = (int)obj;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("程序执行完毕!!");
}
Console.ReadLine();
}
效果: