Skip to content

5. 函数和过程

5.1. VBScript 的预定义函数

一种语言的丰富性很大程度上源于其函数库,因为这些函数可以封装在对象中,并以方法的形式呈现。从这个角度来看,VBScript 可以说相当有限。

下表列出了 VBScript 中对象之外的函数。我们不会对它们进行详细说明。这些函数的名称通常能反映其用途。读者如需了解特定函数的详细信息,请查阅相关文档。

Abs
数组
Asc
Atn
CBool
CByte
CCur
CDate
CDbl
Chr
CInt
CLng
转换
Cos
CreateObject
CSng
Date
DateAdd
DateDiff
DatePart
日期序列
DateValue
Day
派生数学
Eval
Exp
Filter
货币格式
格式化日期和时间
数字格式
百分比格式
GetLocale
GetObject
GetRef
十六进制
Hour
InputBox
InStr
InStrRev
Int, Fixs
IsArray
IsDate
IsEmpty
IsNull
IsNumeric
IsObject
Join
LBound
LCase
Left
Len
LoadPicture
Log
LTrim、RTrim 和 Trims
数学
Mid
Minute
MonthName
MsgBox
现在
10月
替换
RGB
Rnd
Round
脚本引擎
脚本引擎构建版本
ScriptEngineMajorVersion
ScriptEngineMinorVersion
设置区域设置
Sgn
空间
分裂
平方
字符串比较
字符串
Tan
时间
Timer
TimeSerial
TimeValue
类型名称
UBound
UCase
VarType
星期
星期名称

5.2. 模块化编程

描述一个问题的编程解决方案,即描述计算机能够执行且能够解决该问题的一系列基本操作。根据编程语言的不同,这些基本操作的复杂程度各异。例如:

  • 从键盘或磁盘读取数据
  • 将数据写入屏幕、打印机、磁盘等
  • 计算表达式
  • 在文件中进行导航
  • ……

描述一个复杂的问题可能需要数千条甚至更多的这类基本指令。因此,人类的大脑很难把握程序的全貌。面对这种难以把握问题整体的困难,我们将问题分解为更容易解决的子问题。考虑以下问题:对通过键盘输入的一组数值列表进行排序,并将排序后的列表显示在屏幕上。

我们最初可以采用以下形式描述解决方案:

  début
        lire les valeurs et les mettre dans un tableau T
        trier le tableau T
        écrire les valeurs triées du tableau T à l'écran
  fin

我们将该问题分解为 3 个子问题,这样更容易解决。算法记法通常比之前的更规范,该算法可写为:

   début
         lire_tableau(T)
         trier_tableau(T)
         écrire_tableau(T)
   fin

其中 T 代表一个数组。这些操作

    . lire_tableau(T)
    . trier_tableau(T)
    . écrire_tableau(T)

是非基本操作,必须通过基本操作来描述。这一过程在所谓的模块中完成。数据 T 被称为模块参数。它是调用程序传递给被调用模块(输入参数)或从被调用模块接收(输出参数)的信息。因此,模块的参数就是调用程序与被调用模块之间交换的信息。

module read_array(T)
模块 sort_array(T)
模块 read_array(T)

read_array(T) 模块可描述如下:


début
    écrire "Tapez la suite de valeurs à trier sous la forme val1 val2 ... : "
    lire valeurs
    construire tableau T à partir de la chaîne valeurs
fin

至此,我们已充分描述了 read_array 模块。事实上,这三个必要操作均可直接转换为 VBScript 代码。最后一个操作需要使用 Split 函数。如果 VBScript 没有这个函数,那么操作 3 则必须进一步分解为在 VBScript 中有直接对应操作的基本操作。

write_array(T) 模块可描述如下:


début
    construire chaîne texte "valeur1,valeur2,...." à partir du tableau T
    écrire texte
fin

write_array(T) 模块可描述如下(假设 T 中元素的索引从 0 开始):

début
      N<-- indice dernier élément du tableau T
      pour IFIN variant de N à 1
      faire
          //on recherche l'indice IMAX du plus gd élément de T
          // IFIN est l'indice du dernier élément de T

          chercher_max(T, IFIN, IMAX) 

          // on échange l'élément le plus grand de T avec le dernier élément de T

          échanger (T, IMAX, IFIN)

      finfaire
FIN

在此,算法再次使用了非基本操作:


 . chercher_max(T, IFIN, IMAX)
 . échanger(T, IMAX, IFIN)

find_max(T, IFIN, IMAX) 返回数组 T 中最大元素的索引 IMAX,该数组的最后一个元素索引为 IFIN。

swap(T, IMAX, IFIN) 交换数组 T 中索引为 IMAX 和 IFIN 的两个元素。

因此,我们必须描述这些新的非基本操作。

模块 find_max(A, IFIN, IMAX)

   begin
        IMAX<--0

        for i 取值从 1 到 IFIN
        执行
           如果 T[i] > T[IMAX] 则
             开始
                IMAX<--i
             结束
        结束
    结束
交换模块(T IMAX, IFIN)
  开始
       temp<----T[IMAX]
       T[IMAX]<---T[IFIN]
       T[IFIN]<---temp
  end

已使用基本的VBScript操作对初始问题进行了完整描述,因此现在可以将其转换为该语言。请注意,不同语言的基本操作可能存在差异,因此对问题的分析在某个阶段必须考虑到所使用的编程语言。一种语言中存在的对象在另一种语言中可能不存在,从而改变所使用的算法。因此,如果某种语言具有排序函数,那么在此处不使用它将是荒谬的。

此处应用的原则被称为自顶向下分析。若概述解决方案的框架,则如下所示:

我们有一个树形结构。

5.3. VBScript 函数与过程

完成模块化分析后,程序员可以将算法的各个模块转换为 VBScript 函数过程函数过程均可接受输入/输出参数,但函数会返回结果,因此可以在表达式中使用,而过程则不会。

5.3.1. VBScript 函数和过程的声明

VBScript 过程的声明如下

sub nomProcédure([Byref/Byval] param1, [Byref/Byval] param2, ...)
    instructions
end sub

以及函数的定义

function nomFonction([Byref/Byval] param1, [Byref/Byval] param2, ...)
    instructions
end sub

要返回结果,函数必须包含一个赋值语句,将结果赋给与函数同名的变量:

functionName=result

函数或过程的执行有两种结束方式:

  1. 遇到 end function 或 end sub 语句时
  2. 遇到 exit function 或 exit sub 语句时

对于函数,请注意,在函数通过 end function 或 exit function 结束之前,其结果必须已赋值给一个与该函数同名的变量。

5.3.2. 函数或过程的参数传递模式

在函数或过程的输入输出参数声明中,需指定从调用程序到被调用程序的参数传递模式(byRef、byVal):

sub nomProcédure([Byref/Byval] param1, [Byref/Byval] param2, ...)

function nomFonction([Byref/Byval] param1, [Byref/Byval] param2, ...)

如果未指定 byRefbyVal 模式,则使用 byRef 模式。

实际参数,形式参数

考虑一个定义为

function nomFonction([Byref/Byval] paramForm1, [Byref/Byval] paramForm2, ...)
...
end function

在函数或过程定义中使用的参数 paramForm1 称为形式参数。可以通过以下语句从主程序或另一个模块调用上述函数:

résultat=nomFonction(paramEff1, paramEff2, ...)

在调用函数或过程时使用的参数 paramEffi 称为实际参数。当函数 functionName 开始执行时,形式参数会接收相应实际参数的值。关键字 byRefbyVal 决定了这些值的传递方式。

按值传递模式 (byVal)

当形式参数指定此传递模式时,形式参数与实际参数是两个不同的变量。在函数或过程执行之前,实际参数的值会被复制到形式参数中。如果函数或过程在执行过程中修改了形式参数的值,这不会影响相应实际参数的值。这种传递方法非常适合用作函数或过程的输入参数。

按引用传递模式 (byRef)

如果未指定参数传递模式,则此传递模式为默认模式。当形式参数指定此传递模式时,形式参数与相应的实际参数是同一个变量。因此,如果函数修改了形式参数,实际参数也会随之被修改。此传递模式非常适合:

  • 输出参数,因为其值必须传递回调用程序
  • 复制成本较高的输入参数,例如数组

以下程序展示了参数传递的示例:

程序


Sub proc1(byval i, ByRef j, k)
  ' i is passed by value (byval) - the effective parameter and the formal parameter are then different
  ' j is passed by value (byref) - the effective parameter and the formal parameter are then identical
  ' the k passage mode is not specified. The default is by reference
  i=i+1
  j=j+1
  k=k+1
  affiche "dans proc1",i,j,k
End Sub
 
 Sub affiche(byval msg, ByVal i, ByVal j, ByVal k)
  ' displays the values of i and j and k
  wscript.echo msg & " i=" & i & " j=" & j & " k=" & k
End Sub
 
 
' ------------- calls to functions and procedures
 
' init i and j
  i=4:j=5 : k=6
 
' check
  affiche "dans programme principal, avant l'appel à proc1 :",i,j,k

' call proc1 procedure
  proc1 i,j,k
 
' check
  affiche "dans programme principal, après l'appel à proc1 :",i,j,k
 
' end
  wscript.quit 0

结果

dans programme principal, avant l'appel à proc1 : i=4 j=5 k=6
dans proc1 i=5 j=6 k=7
dans programme principal, après l'appel à proc1 : i=4 j=6 k=7

注释

  • 在 VBScript 脚本中,函数和过程没有特定的位置。它们可以出现在源代码的任何位置。通常,它们会集中在开头或结尾,而主程序则以连续代码块的形式组织。

5.3.3. 调用函数和过程的语法

设 p 是一个接受形式参数 pf1、pf2、... 的过程

  • 对过程 p 的调用形式为
p pe1, pe2, ...

参数周围不加圆括号

  • 如果过程 p 不接受参数,可以使用 call p 或 p(),声明时则使用 sub p 或 sub p()

设 f 是一个接受形式参数 pf1、pf2、... 的函数

  • 函数 f 的调用形式为
résultat=f(pe1, pe2, ...)

参数周围必须使用圆括号。如果函数 f 不带参数,可以使用 ff() 进行调用,也可以使用 function ffunction f() 进行声明。

  • 调用程序可以忽略函数 f 的返回值。此时,函数 f 被视为一个过程,并遵循调用过程的规则。我们写 f pe1, pe2, ...(不带圆括号)来调用函数 f。

如果函数或过程是对象方法,其规则似乎有所不同且不一致。

  • 因此,我们可以写 MyFile.WriteLine "This is a test." 或 MyFile.WriteLine("This is a test.")
  • 虽然我们可以写 wscript.echo 4,但不能写 wscript.echo(4)。

我们将遵循以下规则:

  • 作为过程使用的函数或过程的参数周围不加圆括号
  • 函数的参数需用圆括号括起

5.3.4. 函数的示例

以下是函数定义和使用的几个示例:

程序


 Function plusgrandque(byval i, ByVal j)
  ' returns boolean true if i>j, boolean false otherwise
 
  ' data verification
  If isnumeric(i) And isnumeric(j) Then
    If i>j Then
      plusgrandque=true
    Else
      plusgrandque=false
    End If
  Else
    wscript.echo "Arguments (" & i & "," & j & ") erronés"
    plusgrandque=false
  End If
  Exit Function
End Function
 
 Function rendUnTableau(byval n)
  ' makes an array of n elements
  tableau=array()
  ' check validity of parameter n
  If isnumeric(n) And n>=1 Then
    ReDim Preserve tableau(n)
    For i= 0 To n-1
      tableau(i)=i
    Next
  Else
    wscript.echo "Argument [" & n & "] erroné"
  End If
  ' we return the result
  rendUnTableau=tableau
End Function
 
 Function argumentsVariables(byref arguments)
  ' arguments is an array of numbers whose sum is returned
  somme=0
  For i=0 To ubound(arguments)
    somme=somme+arguments(i)
  Next
  argumentsVariables=somme
End Function
 
  ' two parameter-free functions declared in 2 different ways
  Function sansParametres1
    sansParametres=4
  End Function
 
  Function sansParametres2()
    sansParametres=4
  End Function
 

' ------------- calls to functions and procedures
 
' calls function plusgrandque
  wscript.echo "plusgrandque(10,6)=" & plusgrandque(10,6)
  wscript.echo "plusgrandque(6,10)=" & plusgrandque(6,10)
  wscript.echo "plusgrandque(6,6)=" & plusgrandque(6,6)
  wscript.echo "plusgrandque(6,'a')=" & plusgrandque(6,"a")
 
' calls to the rendUnTableau function
  monTableau=rendUnTableau(10)
  For i=0 To ubound(monTableau)
    wscript.echo monTableau(i)
  Next
  monTableau=rendUnTableau(-6)
  For i=0 To ubound(monTableau)
    wscript.echo monTableau(i)
  Next
 
' calls to the argumentsVariables function
  wscript.echo "somme=" & argumentsVariables(array(-1,2,7,8))
  wscript.echo "somme=" & argumentsVariables(array(-1,10,12))
 
' function calls without parameters
  res=sansParametres1
  res=sansParametres1()
  sansParametres1
  sansParametres1()
 
  res=sansParametres2
  res=sansParametres2()
  sansParametres2
  sansParametres2()
 
' end
  wscript.quit 0

结果

plusgrandque(10,6)=Vrai
plusgrandque(6,10)=Faux
plusgrandque(6,6)=Faux
Arguments (6,a) erronés
plusgrandque(6,'a')=Faux
0
1
2
3
4
5
6
7
8
9

Argument [-6] erroné
somme=16
somme=21
somme=10

注释

  • `rendUnTableau` 函数演示了函数可以返回多个结果,而不仅仅是一个。它只需将这些结果放入一个数组变量中,并将该变量作为结果返回即可。
  • 反之,argumentsVariables 函数则展示了如何编写一个接受可变数量参数的函数。同样,你只需将参数放入数组变量中,并将该变量作为函数参数传入即可。

5.3.5. 输出参数或函数结果

假设对某个应用程序的分析表明,需要一个具有输入参数 Ei 和输出参数 Sj 的模块 M。请记住,输入参数是调用程序提供给被调用程序的信息,反之,输出参数则是被调用程序提供给调用程序的信息。在 VBScript 中,针对输出参数有几种解决方案:

  • 如果只有一个输出参数,可以将其设为函数的结果。这样就不再有输出参数,而仅仅是函数结果。
  • 如果有 n 个输出参数,其中一个可以作为函数结果,其余 n-1 个则作为输出参数。或者,也可以不用函数,而使用一个带有 n 个输出参数的过程。还可以使用一个返回数组的函数,该数组包含要返回给调用程序的 n 个值。 请注意,被调用程序通常按值将结果返回给调用程序。当输出参数按引用传递时,可以避免这种复制操作。因此,后一种方案能够节省时间。

5.4. 用于排序值的 VBScript 程序

我们从对通过键盘输入的数值进行排序的算法研究开始,探讨了模块化编程。以下是可用的 VBScript 实现:

程序

' main program
Option Explicit

Dim T        ' the table of values to be sorted

' reading values
T=lire_tableau

' value sorting
trier_tableau T

' display of sorted values
ecrire_tableau T

' end
wscript.quit 0

' ---------- functions & procedures

' -------- lire_tableau
Function lire_tableau
    ' values are requested
    wscript.stdout.write "Tapez les valeurs à trier sous la forme val1 val2  ... valn : "
    ' we read them
    Dim valeurs
    valeurs=wscript.stdin.readLine
    ' we put them in a table
    lire_tableau=split(valeurs," ")
End Function

' -------- ecrire_tableau
Sub ecrire_tableau(byref T)
    ' displays the contents of table T
    wscript.echo join(T," ")
End Sub

' -------- trier_tableau
Sub trier_tableau (byref T)
    ' sorts array T in ascending order

    ' find the imax index of the T[0..ifin] array
    ' to exchange T[imax] with the last element of array T[0..ifin]
    ' then start again with an array with 1 fewer element

    Dim ifin, imax, temp
    For ifin=ubound(T) To 1 Step -1
        ' find the imax index of the T[0..ifin] array
        imax=chercher_max(T,ifin)
        ' we exchange the max with the last element of array T[0..ifin]
        temp=T(ifin):T(ifin)=T(imax):T(imax)=temp
    Next
End Sub

' -------- chercher_max
Function chercher_max(byRef T, ByVal ifin)
    ' find the imax index of the T[0..ifin] array
    Dim i, imax
    imax=0
    For i=1 To ifin
        If cdbl(T(i))>cdbl(T(imax)) Then imax=i
    Next

    ' We return the result
    chercher_max=imax
End Function

结果

Tapez les valeurs à trier sous la forme val1 val2  ... valn : 10 9 8 7 6 1
1 6 7 8 9 10

注释:

  • 初始算法中定义的交换模块在此并未作为 VBScript 模块实现,因为其结构过于简单,无需单独创建模块。

5.5. 模块化形式的TAX程序

我们重新审视该税务计算程序,此次采用模块化形式编写

程序

' calculating a taxpayer's tax liability
' the program must be called with three parameters: married children salary
' married: character Y if married, N if unmarried
' children: number of children
' salary: annual salary without cents

' mandatory variable declaration
Option Explicit
Dim erreur

' retrieve arguments and check their validity
Dim marie, enfants, salaire
erreur=getArguments(marie,enfants,salaire)
' mistake?
If erreur(0)<>0 Then wscript.echo erreur(1) : wscript.quit erreur(0)

' retrieve the data needed to calculate taxes
Dim limites, coeffR, coeffN
getData limites,coeffR,coeffN

' the result is displayed
wscript.echo "impôt=" & calculerImpot(marie,enfants,salaire,limites,coeffR,coeffN)

' leave without error
wscript.quit 0

' ------------ functions and procedures

' ----------- getArguments
Function getArguments(byref marie, ByRef enfants, ByRef salaire)
    ' must retrieve three values passed as arguments to the main program
    ' an argument is passed to the program without spaces in front and behind it
    ' use regular expressions to check data validity

    ' returns an error array variant with 2 values
    ' error(0): error code, 0 if no error
    ' error(1): error message if error otherwise empty string

    Dim syntaxe
    syntaxe= _
    "Syntaxe : pg marié enfants salaire" & vbCRLF & _
    "marié : caractère O si marié, N si non marié" & vbCRLF & _
    "enfants : nombre d'enfants (entier >=0)" & vbCRLF & _
    "salaire : salaire annuel sans les centimes (entier >=0)"

    ' we check that there are 3 arguments
    Dim nbArguments
    nbArguments=wscript.arguments.count
    If nbArguments<>3 Then
        ' error msg
        getArguments= array(1,syntaxe & vbCRLF & vbCRLF & "erreur : nombre d'arguments incorrect")
        ' end
        Exit Function
    End If

    Dim modele, correspondances
    Set modele=new regexp

    ' marital status must be among the characters oOnN
    modele.pattern="^[oOnN]$"
    Set correspondances=modele.execute(wscript.arguments(0))
    If correspondances.count=0 Then
        ' error msg
        getArguments=array(2,syntaxe & vbCRLF & vbCRLF & "erreur : argument marie incorrect")
        ' we leave
        Exit Function
    End If
    ' the value
    If lcase(wscript.arguments(0)) = "o"Then
        marie=true
    Else
        marie=false
    End If

    ' children must be an integer >=0
    modele.pattern="^\d{1,2}$"
    Set correspondances=modele.execute(wscript.arguments(1))
    If correspondances.count=0 Then
        ' error
        getArguments= array(3,syntaxe & vbCRLF & vbCRLF & "erreur : argument enfants incorrect")
        ' we leave
        Exit Function
    End If
    ' the value
    enfants=cint(wscript.arguments(1))

    ' salary must be an integer >=0
    modele.pattern="^\d{1,9}$"
    Set correspondances=modele.execute(wscript.arguments(2))
    If correspondances.count=0 Then
        ' error
        getArguments= array(4,syntaxe & vbCRLF & vbCRLF & "erreur : argument salaire incorrect")
        ' we leave
        Exit Function
    End If
    ' the value
    salaire=clng(wscript.arguments(2))

    ' finished without error
    getArguments=array(0,"")
End Function

' ----------- getData
Sub getData(byref limites, ByRef coeffR, ByRef coeffN)
    ' we define the data needed to calculate the tax in 3 tables
    limites=array(12620,13190,15640,24740,31810,39970,48360, _
    55790,92970,127860,151250,172040,195000,0)
    coeffr=array(0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45, _
    0.5,0.55,0.6,0.65)
    coeffn=array(0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5, _
    12106,16754.5,23147.5,30710,39312,49062)
End Sub

' ----------- calculerImpot
Function calculerImpot(byval marie,ByVal enfants,ByVal salaire, ByRef limites, ByRef coeffR, ByRef coeffN)

    ' the number of shares is calculated
    Dim nbParts
    If marie=true Then
        nbParts=(enfants/2)+2
    Else
        nbParts=(enfants/2)+1
    End If
    If enfants>=3 Then nbParts=nbParts+0.5

    ' we calculate the family quotient and taxable income
    Dim revenu, qf
    revenu=0.72*salaire
    qf=revenu/nbParts

    ' tax calculation
    Dim i, impot
    i=0
    Do While i<ubound(limites) And qf>limites(i)
        i=i+1
    Loop
    calculerImpot=int(revenu*coeffr(i)-nbParts*coeffn(i))
End Function

注释

  • getArguments 函数用于获取纳税人的信息(配偶、子女、工资)。在此,这些信息作为参数传递给 VBScript 程序。如果情况发生变化——例如,这些参数来自图形用户界面——只需重写 getArguments 过程,其他部分无需修改。
  • getArguments 函数能够检测参数中的错误。当发生这种情况时,有人可能会考虑在 getArguments 函数内部使用 wscript.quit 语句来终止程序的执行。但这绝不应该在函数或过程内部进行。如果函数或过程检测到错误,必须以某种方式向调用程序发出信号。是否终止执行应由调用程序决定,而非过程本身。 在本例中,调用程序可能会决定要求用户通过键盘重新输入错误数据,而不是停止执行。
  • 在此,getArguments 函数返回一个数组变量,其第一个元素是错误代码(无错误时为 0),第二个元素是发生错误时的错误消息。通过检查返回结果,调用程序可以判断是否发生了错误。
  • getData 过程用于获取计算税款所需的数据。在此,这些数据直接在 getData 过程内部定义。如果这些数据来自其他来源(例如文件或数据库),则只需重写 getData 过程,其他过程无需修改。
  • `calculerImpot` 函数在获取所有数据后进行税额计算,无论数据通过何种方式获取。
  • 因此请注意,模块化编程允许在不同场景中(重复)使用特定模块。过去二十年来,这一概念已在面向对象编程的框架下得到了深入发展。