VB Visual Basic 2026 中文教程
第 14 课 · 程序模块化

Sub、Function 与参数

程序越来越长时,如果所有代码都放在按钮事件里,会变得难读、难测试,也难重用。VB.NET 可以把一项工作拆成独立的 Sub 或 Function,再通过参数传入资料。这样程序结构会更清楚,也为下一课的类与对象打下基础。

适合:完成第 13 课的初学者建议时间:55–70 分钟实作:CourseFeeCalculator2026

本课学习目标

  • 理解为什么要把长程序拆成多个过程和函数。
  • 建立并调用自定义 Sub
  • 建立并调用有返回值的 Function
  • 理解参数(Parameter)与实参(Argument)的区别。
  • 指定参数的数据类型。
  • 使用 Return 从 Function 返回结果。
  • 理解 ByValByRef 的基本区别。
  • 认识局部变量与方法作用域。
  • 通过多个小方法重构 Windows Forms 程序。
  • 完成一个模块化的课程学费计算器。

14.1 为什么要拆分程序?

一个事件处理程序如果包含几十甚至几百行代码,很快就会变得难以理解。把独立任务拆成 Sub 和 Function,可以让每段代码只负责一件事。

没有拆分

所有代码塞在 Click 事件

验证、计算、格式化、清除、显示全部混在一起,重复代码越来越多。

拆分之后

每个方法负责一个任务

例如 ValidateInput、CalculateDiscount、CalculateFinalFee、DisplayResult。

事件处理程序btnCalculate_Click
调用 Function计算折扣率
调用 Function计算最终学费
调用 Sub显示结果

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
Sub

执行动作

通常不产生一个需要送回调用处的值。

Function

计算并返回结果

函数声明最后必须指定返回类型,例如 As Double。

14.4 什么是参数?

参数让同一个方法可以处理不同资料。

Private Sub ShowStudentName(studentName As String)
    lblResult.Text = "学生:" & studentName
End Sub

这里:

studentName As String

是参数。

调用时:

ShowStudentName("李明")

"李明" 是这次调用传进去的实际值,也称为实参(Argument)。

Parameter
方法定义时声明的输入名称,例如 studentName As String
Argument
调用方法时实际传入的值,例如 "李明"
Return value
Function 完成后送回调用位置的结果。

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。

Return 之后 Function 会结束 一旦执行到 Return,结果会送回调用位置,并离开当前 Function。

14.7 Function 的返回类型

Function返回类型例子
GetStudentName()String"李明"
CalculateAverage()Double82.5
CalculateFee()Decimal1080.00
IsPassing()BooleanTrue
GetCount()Integer25

返回 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。

初学阶段优先使用 ByVal 大多数普通参数传递都适合 ByVal,因为方法的副作用更少,也比较容易理解。

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。

ByRef 要谨慎使用 ByRef 会让方法改变外部变量,代码较难追踪。只有当“修改调用方变量”确实是方法设计的一部分时才使用。很多场景使用 Function 返回新值会更清楚。

较清楚的 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 之后,其他方法不能直接访问它。

局部变量
在 Sub 或 Function 内部声明,只在该方法的作用域内使用。
参数
进入方法时提供资料,也只在该方法内部直接使用。
好处
减少不同代码区域互相影响,让每个方法更独立。

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。

Course Fee Calculator 2026
学生姓名:
李明
课程原价:
1200
会员等级:
金卡会员 ▾
操作:
计算学费清除
学生:李明
原价:1,200.00
折扣率:10%
折扣金额:120.00
最终学费:1,080.00

加入以下控件:

控件NameText / 设置
FormForm1Text = 课程学费计算器
TextBoxtxtStudentName输入学生姓名
TextBoxtxtCourseFee输入课程原价
ComboBoxcmbMembership普通会员、银卡会员、金卡会员、白金会员
ButtonbtnCalculateText = 计算学费
ButtonbtnClearText = 清除
LabellblResult显示结果;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 逐步理解程序结构

1
事件处理程序取得输入

btnCalculate_Click 负责读取界面资料和验证。

2
Function 决定折扣率

GetDiscountRate 根据会员等级返回 Decimal。

3
Function 完成计算

CalculateDiscountAmount 和 CalculateFinalFee 各负责一个公式。

4
Sub 负责显示

DisplayResult 不计算,只负责把结果显示到界面。

5
ClearForm 重用界面操作

清除按钮不再重复写多行清除代码。

14.14 拆成方法有什么好处?

可读性

代码像步骤一样阅读

GetDiscountRate、CalculateFinalFee 直接表达程序意图。

重用

同一逻辑可以多次调用

不需要复制粘贴相同计算代码。

维护

修改集中在一个地方

折扣公式变化时,只修改对应 Function。

一个方法尽量只负责一件事 方法名称如果很难概括它做什么,通常表示里面承担了太多工作,可以考虑继续拆分。

14.15 初学者常见错误

错误 1

Function 忘记返回值

Function 应通过 Return 返回符合声明类型的结果。

错误 2

参数顺序传错

多个相同类型参数特别容易传反,应使用清楚的参数名称和方法名称。

错误 3

把 Sub 当 Function 使用

Sub 没有返回值,不能写成 Dim x = ClearForm()

错误 4

过度使用 ByRef

如果只是想得到计算结果,通常 Function Return 比改变外部变量更清楚。

14.16 小练习

  1. 新增 Function:IsValidFee(courseFee As Decimal) As Boolean
  2. 新增 Function:GetMembershipDescription(membership As String) As String
  3. 把姓名空白检查拆成 Function:IsStudentNameValid(name As String) As Boolean
  4. 新增 Sub:ShowError(message As String, control As Control),集中处理 MessageBox 与 Focus。
  5. 把 GetDiscountRate 的折扣率改成:银卡 6%、金卡 10%、白金 18%,确认只需修改一个 Function。
  6. 尝试用 ByRef 写一个 AddBonus,然后再改写成返回值 Function,比较哪个更清楚。

本课复习

  1. Sub 与 Function 最主要的区别是什么?
  2. 什么是参数(Parameter)?什么是实参(Argument)?
  3. Function 的 As Decimal 表示什么?
  4. Return 的作用是什么?
  5. 为什么参数应该声明明确的数据类型?
  6. ByValByRef 有什么区别?
  7. 为什么很多情况下 Function 比 ByRef 更容易理解?
  8. 什么是局部变量?
  9. 为什么事件处理程序应该尽量保持简短?
  10. 把程序拆成多个小方法有什么维护上的好处?