异常处理 - 让程序更健壮
专栏导航
← 上一篇:面向对象设计原则 - 封装与访问修饰符
← 第一篇:编程世界初探
专栏目录
异常处理 - 让程序更健壮
- 专栏导航
- 一级目录
- 二级目录
- 三级目录
- 什么是异常?
- 现实生活中的类比
- 编程中的异常
- 异常的结果
- try-catch-finally 语句
- 基本语法
- 执行流程
- 基本异常处理
- 示例 1:处理除以零
- 多个 catch 块
- 常见异常类型
- 处理用户输入非数字
- 不使用异常处理
- 使用异常处理
- try-catch 的多种写法
- 写法 1:带异常对象
- 写法 2:不使用异常对象
- 写法 3:捕获所有异常
- finally 块
- 使用场景
- 抛出异常(throw)
- 抛出预定义异常
- 抛出自定义异常消息
- 异常处理最佳实践
- ✅ 推荐做法
- ❌ 不推荐做法
- 实践:简单的计算器(带异常处理)
- 异常处理流程图
- 本章总结
一级目录
二级目录
三级目录
程序运行中难免会出现各种意外情况。异常处理让我们的程序在遇到问题时能够优雅地应对,而不是直接崩溃。
什么是异常?
现实生活中的类比
想象你在开车:
- 正常情况:顺利到达目的地
- 异常情况:
- 车没油了 → 需要加油
- 爆胎了 → 需要换胎
- 迷路了 → 需要看导航
如果遇到问题不处理,后果会很严重(撞车、被困)。
编程中的异常
异常(Exception)是程序运行时发生的错误或意外情况。
常见异常示例:
// 1. 除以零异常
int a = 10;
int b = 0;
int c = a / b; // DivideByZeroException
// 2. 空引用异常
string text = null;
int length = text.Length; // NullReferenceException
// 3. 类型转换异常
string number = "abc";
int num = int.Parse(number); // FormatException
// 4. 索引越界异常
int[] arr = { 1, 2, 3 };
int value = arr[5]; // IndexOutOfRangeException
异常的结果
如果不处理异常,程序会崩溃并显示错误信息:
未处理的异常: System.DivideByZeroException: 尝试除以零。
try-catch-finally 语句
异常处理使用 try-catch-finally 结构。
基本语法
try
{
// 可能抛出异常的代码
}
catch (异常类型 变量名)
{
// 处理异常的代码
}
finally
{
// 无论是否发生异常,都会执行的代码
}
执行流程
try 块
↓
发生异常?
├─ 否 → 正常执行完成 → finally 块 → 结束
└─ 是 → catch 块 → finally 块 → 结束
基本异常处理
示例 1:处理除以零
using System;
namespace Week9Practice
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== 异常处理示例 =====
");
try
{
Console.Write("请输入第一个数字:");
int num1 = int.Parse(Console.ReadLine());
Console.Write("请输入第二个数字:");
int num2 = int.Parse(Console.ReadLine());
int result = num1 / num2;
Console.WriteLine($"结果:{result}");
}
catch (DivideByZeroException)
{
Console.WriteLine("错误:除数不能为零!");
}
catch (FormatException)
{
Console.WriteLine("错误:请输入有效的数字!");
}
finally
{
Console.WriteLine("
程序执行完毕。");
}
}
}
}
运行示例(正常情况):
请输入第一个数字:10
请输入第二个数字:2
结果:5
程序执行完毕。
运行示例(除以零):
请输入第一个数字:10
请输入第二个数字:0
错误:除数不能为零!
程序执行完毕。
运行示例(格式错误):
请输入第一个数字:abc
错误:请输入有效的数字!
程序执行完毕。
多个 catch 块
可以捕获多种类型的异常。
try
{
// 可能抛出异常的代码
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"除以零异常:{ex.Message}");
}
catch (FormatException ex)
{
Console.WriteLine($"格式异常:{ex.Message}");
}
catch (Exception ex) // 捕获所有其他异常
{
Console.WriteLine($"其他异常:{ex.Message}");
}
注意: Exception 是所有异常的基类,放在最后,否则会捕获所有异常。
常见异常类型
| 异常类型 | 说明 | 示例 |
|---|---|---|
DivideByZeroException | 除以零 | 10 / 0 |
FormatException | 格式错误 | int.Parse("abc") |
IndexOutOfRangeException | 索引越界 | arr[5] |
NullReferenceException | 空引用 | null.Length |
ArgumentException | 参数无效 | 方法参数不符合要求 |
InvalidOperationException | 无效操作 | 在无效状态下操作 |
处理用户输入非数字
不使用异常处理
Console.Write("请输入年龄:");
string input = Console.ReadLine();
int age = int.Parse(input); // 如果输入非数字,程序崩溃
使用异常处理
using System;
namespace Week9Practice
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== 年龄验证 =====
");
int age = 0;
bool isValid = false;
while (!isValid)
{
try
{
Console.Write("请输入你的年龄:");
string input = Console.ReadLine();
age = int.Parse(input);
if (age >= 0 && age <= 150)
{
isValid = true;
// 判断是否成年
if (age >= 18)
{
Console.WriteLine($"你 {age} 岁,已经是成年人了。");
}
else
{
Console.WriteLine($"你 {age} 岁,还是未成年人。");
}
}
else
{
Console.WriteLine("年龄必须在 0-150 之间!");
}
}
catch (FormatException)
{
Console.WriteLine("错误:请输入有效的数字!");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误:{ex.Message}");
}
}
Console.WriteLine("
程序执行完毕。");
}
}
}
运行示例:
请输入你的年龄:abc
错误:请输入有效的数字!
请输入你的年龄:200
年龄必须在 0-150 之间!
请输入你的年龄:18
你 18 岁,已经是成年人了。
程序执行完毕。
try-catch 的多种写法
写法 1:带异常对象
try
{
int a = 10 / 0;
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"异常类型:{ex.GetType().Name}");
Console.WriteLine($"异常消息:{ex.Message}");
Console.WriteLine($"堆栈跟踪:{ex.StackTrace}");
}
写法 2:不使用异常对象
try
{
int a = 10 / 0;
}
catch (DivideByZeroException)
{
Console.WriteLine("除数不能为零!");
}
写法 3:捕获所有异常
try
{
int a = 10 / 0;
}
catch (Exception ex)
{
Console.WriteLine($"发生异常:{ex.Message}");
}
finally 块
finally 块中的代码无论是否发生异常都会执行。
使用场景
场景 1:资源释放
using System;
using System.IO;
namespace Week9Practice
{
class Program
{
static void Main(string[] args)
{
StreamReader reader = null;
try
{
reader = new StreamReader("文件.txt");
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
catch (FileNotFoundException)
{
Console.WriteLine("文件未找到!");
}
catch (Exception ex)
{
Console.WriteLine($"读取文件时出错:{ex.Message}");
}
finally
{
// 释放资源
if (reader != null)
{
reader.Close();
Console.WriteLine("文件已关闭。");
}
}
}
}
}
场景 2:清理操作
try
{
// 打开数据库连接
Console.WriteLine("连接数据库...");
// 执行操作
Console.WriteLine("执行操作...");
}
catch (Exception ex)
{
Console.WriteLine($"错误:{ex.Message}");
}
finally
{
// 关闭数据库连接
Console.WriteLine("关闭数据库连接...");
}
输出(无论是否异常):
连接数据库...
执行操作...
关闭数据库连接...
抛出异常(throw)
我们不仅可以捕获异常,还可以主动抛出异常。
抛出预定义异常
class Calculator
{
public double Divide(double a, double b)
{
if (b == 0)
{
throw new DivideByZeroException("除数不能为零!");
}
return a / b;
}
}
// 使用
Calculator calc = new Calculator();
try
{
double result = calc.Divide(10, 0);
}
catch (DivideByZeroException ex)
{
Console.WriteLine(ex.Message);
}
抛出自定义异常消息
public void SetAge(int age)
{
if (age < 0 || age > 150)
{
throw new ArgumentException("年龄必须在 0-150 之间!");
}
this.age = age;
}
异常处理最佳实践
✅ 推荐做法
- 具体捕获异常类型
catch (FormatException ex)
{
// 处理格式异常
}
- 提供有意义的错误信息
Console.WriteLine($"年龄输入错误:请输入 0-150 之间的数字。");
- 使用 finally 释放资源
finally
{
// 清理资源
}
❌ 不推荐做法
- 捕获所有异常但不处理
catch
{
// 什么都不做
}
- 捕获所有异常并忽略
catch (Exception ex)
{
// 忽略异常
}
- 捕获异常后重新抛出相同的异常
{
throw; // 失去了原始异常信息
}
实践:简单的计算器(带异常处理)
using System;
namespace Week9Practice
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== 简易计算器 =====");
while (true)
{
Console.WriteLine("
1. 加法");
Console.WriteLine("2. 减法");
Console.WriteLine("3. 乘法");
Console.WriteLine("4. 除法");
Console.WriteLine("5. 退出");
Console.Write("请选择操作:");
try
{
string choice = Console.ReadLine();
if (choice == "5")
{
Console.WriteLine("再见!");
break;
}
Console.Write("请输入第一个数字:");
double num1 = double.Parse(Console.ReadLine());
Console.Write("请输入第二个数字:");
double num2 = double.Parse(Console.ReadLine());
double result = 0;
switch (choice)
{
case "1":
result = num1 + num2;
break;
case "2":
result = num1 - num2;
break;
case "3":
result = num1 * num2;
break;
case "4":
if (num2 == 0)
{
throw new DivideByZeroException("除数不能为零!");
}
result = num1 / num2;
break;
default:
Console.WriteLine("无效的选择!");
continue;
}
Console.WriteLine($"结果:{num1} {GetOperator(choice)} {num2} = {result}");
}
catch (FormatException)
{
Console.WriteLine("错误:请输入有效的数字!");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"错误:{ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"发生未预期的错误:{ex.Message}");
}
}
}
static string GetOperator(string choice)
{
switch (choice)
{
case "1": return "+";
case "2": return "-";
case "3": return "*";
case "4": return "/";
default: return "?";
}
}
}
}
运行示例:
===== 简易计算器 =====
1. 加法
2. 减法
3. 乘法
4. 除法
5. 退出
请选择操作:1
请输入第一个数字:10
请输入第二个数字:abc
错误:请输入有效的数字!
1. 加法
2. 减法
3. 乘法
4. 除法
5. 退出
请选择操作:4
请输入第一个数字:10
请输入第二个数字:0
错误:除数不能为零!
1. 加法
2. 减法
3. 乘法
4. 除法
5. 退出
请选择操作:4
请输入第一个数字:10
请输入第二个数字:2
结果:10 / 2 = 5
异常处理流程图
用户输入数据
↓
try 块开始
↓
解析输入 → int.Parse()
↓
是否发生异常?
↓
├─ 否 → 正常执行
│ ↓
│ 显示结果
│ ↓
│ finally 块
│ ↓
│ 结束
│
└─ 是 → catch 块
↓
处理异常
↓
显示错误信息
↓
finally 块
↓
结束
本章总结
- ✅ 理解了异常的概念和原因
- ✅ 掌握了 try-catch-finally 结构
- ✅ 学会了处理多种异常类型
- ✅ 了解了 finally 块的用途
- ✅ 学会了抛出异常







