跳转到内容

Futurebasic/语言/参考/local fn

来自维基教科书,开放的书籍,为了一个开放的世界

[CLEAR] LOCAL FN functionName [(arg1 [,arg2 ...])]    [statementBlock] END FN [= expr]

该语句标志着 FB 局部函数的开始。局部函数的结束由 END FN 语句标记。局部函数是语句的命名集合,可以通过引用函数的名称来访问和执行(见 FN <userFunction> 语句)。局部函数中引用的所有变量和数组(除了显式声明为“全局”的变量和数组)都对该函数是局部的,这意味着它们在函数外部没有任何影响;在函数外部出现的任何同名变量实际上是不同的变量,并在内存中占用不同的位置,而不是函数的局部变量。(当数组被列为函数的正式参数时,此规则会出现例外;请参阅以下内容。)当您的程序“调用”(执行)局部函数时,您可以通过参数列表(也称为参数列表)将数据传递给函数,并且可以通过返回值接收来自函数的值。局部函数允许您封装复杂的编程任务;它们是一种基本且非常强大的编程结构。

除了标记函数的开始,LOCAL FN 语句还声明函数的名称、返回值的数据类型(如果有)以及输入参数的数量和类型(如果有)。局部函数可以放置在程序中的任何位置,除了另一个局部函数内部;您也不应将局部函数放置在“块”结构中,例如 LONG IF...END IF 等。statementBlock 中的语句可以包含任何内容,但以下内容除外

  • 一个 LOCAL 语句;
  • 一个 ENTERPROC...EXITPROC 块;
  • 另一个局部函数。

添加 CLEAR 关键字会导致每次调用函数时,所有函数的局部变量和数组(除了参数变量 arg1arg2 等)都被初始化为零、空字符串或空记录。(您可以通过使用带有 CLEAR 关键字的 LOCAL 语句来实现相同的效果;有关更多信息,请参阅 LOCAL 语句。)

functionName 必须是唯一的;也就是说,它必须与程序中任何其他 LOCAL FNENTERPROC FNLONG FNDEF FN USINGDEF FN <expr> 语句中使用的名称不同。如果函数要返回值,则应在 functionName 末尾包含适当的类型标识符后缀来指定返回值的数据类型。例如,要返回字符串值的局部函数可以声明如下

  LOCAL FN FullName$(idNum&)

函数返回值的默认数据类型为“长整数”;如果函数要返回长整数值,您可以包含或省略“&”类型标识符后缀。如果函数不返回值,则不应将类型标识符后缀追加到 functionName

为了执行 statementBlock 中的语句,您的程序必须使用 FN <userFunction> 语句“调用”该函数。FN <userFunction> 语句可以出现在程序中的任何位置,只要它调用的函数是在 FN <userFunction> 语句上方某个位置定义的(使用 LOCAL FN 语句)或原型化的(使用 DEF FN <prototype> 语句)。此限制是必要的,以便允许您的程序编译;但是,程序语句的实际执行顺序不受您放置 LOCAL FN 的位置影响。

函数参数 每个参数 arg1arg2 等可以具有以下任何形式

LOCAL FN 语句中的参数称为函数的“形式参数”。它们不能是全局变量。您不应在 DIM 语句中声明形式参数变量;它们是由 LOCAL FN 语句隐式声明的。

当您的程序调用函数时,在 FN <userFunction> 语句中传递给它的参数称为“实际参数”。它们在数量上必须与函数的形式参数匹配,并且它们必须是“兼容类型”(有关更多信息,请参阅 FN <userFunction>)。每次调用函数时,都会按以下方式为其形式参数赋值

  • 如果形式参数是 simpleVar,则实际参数的值将被复制到 simpleVar 中。
  • 如果形式参数的形式为 ptrVar AS POINTER [TO someType],则实际参数应为记录变量或长整型表达式。在第一种情况下,记录的地址将被复制到 ptrVar 中;在第二种情况下,长整型的值将被复制到 ptrVar 中。
  • 如果形式参数的形式为 @longVar&@ptrVar AS POINTER [TO someType],则实际参数必须是变量(可能是记录变量),或者是由“=”前缀的长整型表达式。在第一种情况下,变量的地址将被复制到 longVar&ptrVar 中。在第二种情况下,长整型表达式的值将被复制到 longVar&ptrVar 中。
  • 如果形式参数是 array(dim1 [,dim2 ...]),则实际参数必须是相同类型数组的基元素,该数组具有相同的维数。基元素是所有下标都设置为零的元素。然后,整个数组对局部函数是可访问的,并且(重要的是!)对数组元素在函数内进行的任何更改都将在函数退出后持续存在。

如果局部函数没有参数,则应省略 functionName 后的括号

.

传递未知大小的数组 有时,编写一个在参数列表中传递的数组上运行的局部函数很有用,而事先不知道传递数组的大小。例如,假设您希望编写一个对长整型数组元素进行排序的函数,并且您希望它在传递数组的声明大小无关紧要的情况下都能工作。

当您将数组声明为形式参数时,FB 会忽略 LOCAL FN 语句中数组第一个声明维的值。例如,假设我们有一个这样定义的函数

LOCAL FN SetElements(anArray&(1,7), max&)   '将数组中的每个元素设置为 1492:   FOR i& = 0 TO max&     FOR j = 0 TO 7       anArray&(i&,j) = 1492     NEXT   NEXT END FN

当我们将长整型数组传递给 FN SetElements 时,传递的数组可以具有任何大小作为其第一个声明的维,只要它具有声明为 7 的第二个维即可。例如

DIM arrayOne&(1250,7), arrayTwo&(465,7) FN SetElements(arrayOne&(0,0), 1250) FN SetElements(arrayTwo&(0,0), 465)

在函数内部,我们可以安全地操作数组中的元素,只要我们使用的下标不超过传递的实际数组的声明维即可。因此,在 FN SetElements 中,我们可以将 anArray& 中的第一个下标设置为远远大于 1 的值,即使 anArray& 是用维数 (1,7) “声明”的。

注意:您在参数数组的第二个、第三个等维数方面没有相同的自由度。如果数组是多维的,则第二个和后续维数必须在“形式”数组参数(在 LOCAL FN 语句中)和声明实际传递数组的外部 DIM 语句中声明为相同的值。

返回值 如果您在 END FN 语句中指定了 expr,则函数将“返回”expr 的值。这可以是与 functionName 中出现的类型标识符后缀(如果有)兼容的任何表达式。当您的函数“返回值”时,这意味着您可以将函数(使用 FN <userFunction>)作为字符串或数字表达式的一部分引用,并且函数的返回值将被替换为表达式。例如

   maxPuppets = 6 * FN storeCount%(x)

在这里,如果 FN storeCount%(x) 返回值为 7,则值 42 将被分配给 maxPuppets

局部变量的寿命 函数的局部变量的内存空间在调用函数时被保留。此内存将在执行 END FN 语句后释放。因此,您不应该在函数执行完毕后引用局部变量的地址;特别是,您不应该将局部变量的地址传递回调用该函数的例程。例如

'不要这样做! LOCAL FN myFunction&(x,y,z)   DIM r#   r# = SQR(x*x + y*y + z*z) END FN = @r#

   rAddr& = FN myFunction&(x,y,z)

执行完上述操作后,rAddr& 指向不再保留的内存区域(r# 的旧地址),并且不应使用。

另一方面,将局部变量的地址传递到另一个局部函数是可以的。这是因为第一个局部函数在调用第二个局部函数时尚未完成执行。因此,在第二个函数执行时,保存第一个函数局部变量的内存空间仍然完好无损地保留着。

'这是可以的: LOCAL FN FirstFn   DIM 255 myString$   '将局部变量的地址传递到另一个 FN:   FN SecondFN(@myString$) END FN   : LOCAL FN SecondFn(strAddr&)   BLOCKMOVE @gString$, strAddr&, LEN(gString$)+1 END FN

递归函数 您可以同时执行多个函数,从某种意义上说,一个函数可以调用第二个函数,第二个函数可以调用第三个函数,依此类推。如果您设计函数调用,使得一个函数可以调用正在执行的函数,那么您就有一个“递归函数”。递归函数最明显的(但不是唯一的)示例是任何调用自身的函数。发生这种情况时,我们说函数的两个(或更多)“实例”同时执行。

在 FB 中,每个正在执行的本地函数“实例”都维护着自己的私有局部变量集,它们不会干扰该函数的任何其他正在执行的实例的局部变量。递归调用函数非常类似于调用一个“不同的”函数,该函数恰好包含完全相同的程序行。

虽然递归函数乍一看可能是一个奇怪的概念,但它们完全可以接受,并且经常非常有用。例如,以下是一个简短的程序,它打印给定输入字符串中包含的所有字符的排列;请注意,FNpermute_r 自身调用。在不使用递归函数的情况下编写这样的程序将非常困难。

'函数原型:DEF FN Permute(aString$) DEF FN permute_r(prefix$, suffix$)

INPUT "Enter a word: "; theWord$ FN Permute(theWord$) END

LOCAL FN Permute(aString$)    '打印 aString$ 中所有字母的排列    FN permute_r("", aString$) END FN

LOCAL FN permute_r(prefix$, suffix$)   '打印 prefix$+suffix$ 的所有排列   '这些排列以 prefix$ 开头   LONG IF suffix$ = ""     PRINT prefix$   XELSE     FOR i = 1 to LEN(suffix$)       '将 suffix$ 的第 i 个字母移到 newprefix$:       newprefix$ = prefix$ + MID$(suffix$, i, 1)       newsuffix$ = LEFT$(suffix$,i-1) + MID$(suffix$,i+1)       '现在打印所有以       'newprefix$ 开头的排列       FN permute_r(newprefix$, newsuffix$)     NEXT   END IF END FN

返回多个值 END FN 语句只能返回单个数字或字符串表达式。但很多时候,让一个本地函数能够返回多个值非常有用。实现此目的的方法是通过函数的参数列表。如果您为函数提供了对某个外部变量或数组地址的访问权限,那么该函数就可以更改该地址处的內容,从而有效地修改该变量或数组的值。

有三种方法可以将地址传递给您的函数

  • 如果您传递整个数组(在正式参数列表中使用 array(dim1 [,dim2 ...] 语法),那么您的函数隐式地可以访问所传递数组的地址。您在函数内部对数组元素所做的任何更改实际上都是对外部数组进行的,因此这些更改在函数退出后仍然存在。
  • 如果您在函数的正式参数列表中使用 @var 语法,并在调用函数时指定一个变量,那么变量的地址将被复制到 var 中。然后,您的函数可以修改该地址处的內容。
  • 您可以显式地将任何地址传递到长整数或 POINTER 正式参数中。

示例: CD Example: LOCALFN.BAS

另请参见

[编辑 | 编辑源代码]

FN <userFunction>; LOCAL; @FN; DEF FN <prototype>

华夏公益教科书