本课学习目标
- 理解为什么要把长程序拆成多个过程和函数。
- 建立并调用自定义 Sub。
- 建立并调用有返回值的 Function。
- 理解参数(Parameter)与实参(Argument)的区别。
- 指定参数的数据类型。
- 使用 Return 从 Function 返回结果。
- 理解 ByVal 与 ByRef 的基本区别。
- 认识局部变量与方法作用域。
- 通过多个小方法重构 Windows Forms 程序。
- 完成一个模块化的课程学费计算器。
14.1 为什么要拆分程序?
一个事件处理程序如果包含几十甚至几百行代码,很快就会变得难以理解。把独立任务拆成 Sub 和 Function,可以让每段代码只负责一件事。
所有代码塞在 Click 事件
验证、计算、格式化、清除、显示全部混在一起,重复代码越来越多。
每个方法负责一个任务
例如 ValidateInput、CalculateDiscount、CalculateFinalFee、DisplayResult。
14.2 什么是 Sub?
Sub 用来执行一项工作,但不会像 Function 那样返回一个结果值。
Private Sub ShowWelcomeMessage()
lblResult.Text = "欢迎使用课程学费计算器。"
End Sub
调用:
ShowWelcomeMessage()
Private Sub
ShowWelcomeMessage
()
...
End Sub
常见适合写成 Sub 的工作:
- 清除界面
- 显示结果
- 设置控件状态
- 加载界面资料
- 把一组重复动作集中到一个地方
14.3 什么是 Function?
Function 与 Sub 最大的区别是:Function 会返回一个值。
Private Function CalculateSquare(number As Double) As Double
Return number * number
End Function
调用 Function:
Dim result As Double = CalculateSquare(5)
执行流程:
CalculateSquare(5)
→
返回 25
执行动作
通常不产生一个需要送回调用处的值。
计算并返回结果
函数声明最后必须指定返回类型,例如 As Double。
14.4 什么是参数?
参数让同一个方法可以处理不同资料。
Private Sub ShowStudentName(studentName As String)
lblResult.Text = "学生:" & studentName
End Sub
这里:
studentName As String
是参数。
调用时:
ShowStudentName("李明")
"李明" 是这次调用传进去的实际值,也称为实参(Argument)。
14.5 多个参数
一个方法可以有多个参数:
Private Function CalculateTotal(
unitPrice As Decimal,
quantity As Integer) As Decimal
Return unitPrice * quantity
End Function
调用:
Dim total As Decimal = CalculateTotal(25.5D, 4)
这里参数顺序很重要:
unitPrice = 25.5D
quantity = 4
返回 102.0D
14.6 Return:把结果送回调用位置
Function 使用 Return 把结果返回:
Private Function CalculateAverage(
score1 As Double,
score2 As Double) As Double
Dim average As Double = (score1 + score2) / 2
Return average
End Function
调用:
Dim averageScore As Double = CalculateAverage(78, 86)
此时 averageScore 得到 82。
14.7 Function 的返回类型
| Function | 返回类型 | 例子 |
|---|---|---|
| GetStudentName() | String | "李明" |
| CalculateAverage() | Double | 82.5 |
| CalculateFee() | Decimal | 1080.00 |
| IsPassing() | Boolean | True |
| GetCount() | Integer | 25 |
返回 Boolean 的 Function
Private Function IsPassing(score As Double) As Boolean
Return score >= 50
End Function
使用:
If IsPassing(75) Then
lblResult.Text = "及格"
End If
14.8 ByVal:把值传给方法
VB.NET 参数通常使用 ByVal。它表示方法不能通过重新给参数赋值来直接改变调用方变量本身。
Private Sub ChangeNumber(ByVal number As Integer)
number = 100
End Sub
例如:
Dim value As Integer = 10
ChangeNumber(value)
lblResult.Text = value.ToString()
这里 value 仍然是 10。
14.9 ByRef:允许方法改变调用方变量
ByRef 会让方法能够修改调用方的变量。
Private Sub AddBonus(ByRef score As Integer)
score += 5
End Sub
调用:
Dim score As Integer = 70
AddBonus(score)
lblResult.Text = score.ToString()
结果变成 75。
较清楚的 Function 版本
Private Function AddBonus(score As Integer) As Integer
Return score + 5
End Function
调用:
score = AddBonus(score)
14.10 局部变量与作用域
在 Sub 或 Function 里面声明的变量通常只在该方法内部有效:
Private Function CalculateDiscount(
courseFee As Decimal,
discountRate As Decimal) As Decimal
Dim discountAmount As Decimal =
courseFee * discountRate
Return discountAmount
End Function
discountAmount 是局部变量。离开 CalculateDiscount 之后,其他方法不能直接访问它。
14.11 事件处理程序本身也是 Sub
你一直在使用的按钮 Click 代码其实也是一个 Sub:
Private Sub btnCalculate_Click(
sender As Object,
e As EventArgs) Handles btnCalculate.Click
' 按钮点击时执行的代码
End Sub
只是这个 Sub 由 Windows Forms 事件系统自动调用,而不是我们直接写:
btnCalculate_Click(...)
因此可以把复杂工作移到自己的 Function 或 Sub,让事件处理程序只负责“协调流程”。
14.12 实作:课程学费计算器
建立新的 Windows Forms 项目:
CourseFeeCalculator2026
程序让用户输入学生姓名与课程原价,再选择会员等级。Click 事件本身保持简短,真正的计算拆分到多个 Function 与 Sub。
原价:1,200.00
折扣率:10%
折扣金额:120.00
最终学费:1,080.00
加入以下控件:
| 控件 | Name | Text / 设置 |
|---|---|---|
| Form | Form1 | Text = 课程学费计算器 |
| TextBox | txtStudentName | 输入学生姓名 |
| TextBox | txtCourseFee | 输入课程原价 |
| ComboBox | cmbMembership | 普通会员、银卡会员、金卡会员、白金会员 |
| Button | btnCalculate | Text = 计算学费 |
| Button | btnClear | Text = 清除 |
| Label | lblResult | 显示结果;AutoSize = True |
Function 1:取得折扣率
Private Function GetDiscountRate(
membership As String) As Decimal
Select Case membership
Case "银卡会员"
Return 0.05D
Case "金卡会员"
Return 0.1D
Case "白金会员"
Return 0.15D
Case Else
Return 0D
End Select
End Function
Function 2:计算折扣金额
Private Function CalculateDiscountAmount(
courseFee As Decimal,
discountRate As Decimal) As Decimal
Return courseFee * discountRate
End Function
Function 3:计算最终学费
Private Function CalculateFinalFee(
courseFee As Decimal,
discountAmount As Decimal) As Decimal
Return courseFee - discountAmount
End Function
Sub:显示计算结果
Private Sub DisplayResult(
studentName As String,
courseFee As Decimal,
discountRate As Decimal,
discountAmount As Decimal,
finalFee As Decimal)
lblResult.Text =
$"学生:{studentName}" & Environment.NewLine &
$"原价:{courseFee:N2}" & Environment.NewLine &
$"折扣率:{discountRate:P0}" & Environment.NewLine &
$"折扣金额:{discountAmount:N2}" & Environment.NewLine &
$"最终学费:{finalFee:N2}"
End Sub
Sub:清除表单
Private Sub ClearForm()
txtStudentName.Clear()
txtCourseFee.Clear()
cmbMembership.SelectedIndex = -1
lblResult.Text = ""
txtStudentName.Focus()
End Sub
计算按钮:只负责协调流程
Private Sub btnCalculate_Click(sender As Object, e As EventArgs) _
Handles btnCalculate.Click
Dim studentName As String =
txtStudentName.Text.Trim()
Dim courseFee As Decimal
If String.IsNullOrWhiteSpace(studentName) Then
MessageBox.Show("请输入学生姓名。")
txtStudentName.Focus()
Exit Sub
End If
If Not Decimal.TryParse(txtCourseFee.Text, courseFee) Then
MessageBox.Show("请输入有效课程学费。")
txtCourseFee.Focus()
Exit Sub
End If
If courseFee < 0 Then
MessageBox.Show("课程学费不能小于 0。")
txtCourseFee.Focus()
Exit Sub
End If
Dim membership As String = cmbMembership.Text
Dim discountRate As Decimal =
GetDiscountRate(membership)
Dim discountAmount As Decimal =
CalculateDiscountAmount(courseFee, discountRate)
Dim finalFee As Decimal =
CalculateFinalFee(courseFee, discountAmount)
DisplayResult(
studentName,
courseFee,
discountRate,
discountAmount,
finalFee)
End Sub
清除按钮
Private Sub btnClear_Click(sender As Object, e As EventArgs) _
Handles btnClear.Click
ClearForm()
End Sub
14.13 逐步理解程序结构
btnCalculate_Click 负责读取界面资料和验证。
GetDiscountRate 根据会员等级返回 Decimal。
CalculateDiscountAmount 和 CalculateFinalFee 各负责一个公式。
DisplayResult 不计算,只负责把结果显示到界面。
清除按钮不再重复写多行清除代码。
14.14 拆成方法有什么好处?
代码像步骤一样阅读
GetDiscountRate、CalculateFinalFee 直接表达程序意图。
同一逻辑可以多次调用
不需要复制粘贴相同计算代码。
修改集中在一个地方
折扣公式变化时,只修改对应 Function。
14.15 初学者常见错误
Function 忘记返回值
Function 应通过 Return 返回符合声明类型的结果。
参数顺序传错
多个相同类型参数特别容易传反,应使用清楚的参数名称和方法名称。
把 Sub 当 Function 使用
Sub 没有返回值,不能写成 Dim x = ClearForm()。
过度使用 ByRef
如果只是想得到计算结果,通常 Function Return 比改变外部变量更清楚。
14.16 小练习
- 新增 Function:IsValidFee(courseFee As Decimal) As Boolean。
- 新增 Function:GetMembershipDescription(membership As String) As String。
- 把姓名空白检查拆成 Function:IsStudentNameValid(name As String) As Boolean。
- 新增 Sub:ShowError(message As String, control As Control),集中处理 MessageBox 与 Focus。
- 把 GetDiscountRate 的折扣率改成:银卡 6%、金卡 10%、白金 18%,确认只需修改一个 Function。
- 尝试用 ByRef 写一个 AddBonus,然后再改写成返回值 Function,比较哪个更清楚。
本课复习
- Sub 与 Function 最主要的区别是什么?
- 什么是参数(Parameter)?什么是实参(Argument)?
- Function 的 As Decimal 表示什么?
- Return 的作用是什么?
- 为什么参数应该声明明确的数据类型?
- ByVal 与 ByRef 有什么区别?
- 为什么很多情况下 Function 比 ByRef 更容易理解?
- 什么是局部变量?
- 为什么事件处理程序应该尽量保持简短?
- 把程序拆成多个小方法有什么维护上的好处?