向编译器声明一个函数名和一个可选的局部变量名列表。在 FoxPro DEFINE CLASS ... ENDDEFINE 中使用时,将声明一个方法。
[Attributes] [Modifiers] FUNCTION <idFunction>
[Typeparameters]
[([<idParam> [AS | REF|OUT|IN <idType>] [, ...])]
[AS <idType>]
[TypeparameterConstraints]
[<idConvention>]
[EXPORT LOCAL]
[DLLEXPORT STRING_CONST]
[=> <expression>]
CRLF
[<Body>]
[ENDFUNC | END FUNCTION]
Attributes | 一个可选的特性列表,用于描述实体的元信息,例如在 MsTest 类库中包含测试的方法/函数上的 [TestMethod] 属性。请注意,当特性写在关键字上方的行时,特性必须在同一行或以分号结尾。 |
Modifiers | 一个可选的修饰符列表,用于指定主体的可见性或范围,例如:PUBLIC, STATIC, INTERNAL, EXPORT 和 UNSAFE. |
请注意,在 FoxPro 兼容类中作为类成员使用的函数和过程可以有更多的修饰符。 |
<idFunction> | 函数的有效标识符名称。 函数是一个实体,因此与其他实体共享相同的名称空间。 这意味着不可能出现函数和类同名的情况。 |
TypeParameters | 这适用于具有泛型类型参数的方法。这类似于 `<T>`,用于具有一个名为 T 的类型参数的方法。通常,参数列表中的一个参数也是类型 T。 |
<idParam> | 参数变量。 以这种方式指定的变量会自动声明为局部变量。 这些变量也称为形参(形式参数),用于接收调用实体时传递的参数。 |
AS | REF | OUT | IN <idType> | 指定参数变量的数据类型(称为强类型)。 AS 表示参数必须通过值传递,REF 表示参数必须通过带 @ 操作符的引用传递。OUT 是一种特殊的 REF 参数,不必在调用前赋值,必须在实体内部赋值。IN 参数作为 READONLY 引用传递。 |
列表中的最后一个参数也可以声明为 PARAMS <idType>[] ,这将告诉编译器函数/方法可以接收零个或多个可选参数。 |
使用 CLIPPER 调用约定的函数或方法将被编译为一个带有单个参数的函数,该参数被声明为 Args PARAMS USUAL[] 。 |
AS <idType> | 指定数据类型。如果省略,编译器依据编译选项来决定数据类型,或者是 USUAL,或者是由编译器自行决定。 |
TypeParameterConstraints | 在这里,您可以为类型参数指定约束,例如 WHERE T IS SomeName 或 WHERE T IS New。 |
<idConvention> | 指定此实体的调用约定。 <idConvention> 必须是以下内容之一: |
o CLIPPER
o STRICT
o PASCAL
o CALLBACK
o THISCALL
大多数 调用约定 仅用于向后兼容。 但是,存在两个例外情况: 1. CLIPPER 声明方法具有未类型化参数。通常只有没有声明参数的方法才需要这样做。否则,编译器在检测到未类型化参数时将假定使用 CLIPPER 调用约定。 2. 外部 DLL 的方法和函数可以使用 STRICT、PASCAL、 CALLBACK 调用约定。 |
EXPORT LOCAL | X# 允许使用该子句,但会被忽略。 |
=> <Expression> | 替代实体多行正文(body)的单一表达式。不能与正文(body)一起编译 |
<Body> | 构成此主体代码的程序语句。 |
<Body> 可以包含一个或多个 RETURN 语句,以将控制权返回给调用例程,并作为函数的返回值。如果未指定 RETURN 语句,当函数定义结束时,控制权将返回给调用例程,并且函数将根据指定的返回值数据类型返回一个默认值(如果返回值没有强类型,则返回 NIL)。 |
不能与表达式主体(Expression Body)结合使用。 |
ENDFUNC | END FUNCTION | 这些(可选)关键字表示函数的逻辑终点。 |
函数是一个子程序,由一系列声明和语句组成,每当提及 <idFunction> 时都要执行(见下面的备注部分)。
函数和过程(参见本指南中的 PROCEDURE 语句)是基本的过程编程单元。 您将在应用程序中使用它们来组织计算代码块。
通过 STATIC FUNCTION(静态函数),您可以将函数名称的可见性限制在当前模块内,从而限制对函数的访问。 在设计模块时,如果模块中包含一些公共例程(即在整个应用程序中可见),而其他一些例程则完全属于支持例程(即只有同一模块中的其他例程才需要),那么该功能就非常有用。
只需使用 STATIC FUNCTION 声明所有支持函数即可。 这样做有两个立竿见影的好处。 首先,应用程序中的其他模块不会无意中调用你的支持例程。 其次,由于静态引用是在编译时解析的,而公共引用是在链接时解析的,因此不会出现名称冲突。 例如,如果在模块 X 中声明了一个静态 Service() 函数,而在模块 Y 中声明了一个公共 Service() 函数,那么在 X 中对 Service() 的所有引用都将执行静态版本,而应用程序中对 Service() 的所有其他引用都将执行公共版本。
Start() 函数: 所有应用程序都必须有一个名为 Start() 的函数或过程,或者与图形用户界面类库链接,并有一个 CLASS App 方法 Start()。 Start()是应用程序执行时的启动例程。 Start() 不能声明任何参数,在正常情况下也不应返回值。 如果要在声明语句中使用强类型,则必须指定 AS USUAL PASCAL。
通过代码块输出本地语言 创建代码块时,可以在代码块定义中访问创建实体中定义的局部变量,而无需将它们作为参数传递(即局部变量对代码块是可见的)。 利用这一事实以及可以将代码块作为参数传递的事实,就可以导出局部变量。 例如:
FUNCTION One() EXPORT LOCAL
LOCAL nVar := 10 AS INT, cbAdd AS CODEBLOCK
cbAdd := {|nValue| nValue + nVar}
? NextFunc(cbAdd) // Result: 210
FUNCTION NextFunc(cbAddEmUp)
RETURN (EVAL(cbAddEmUp, 200))
在 NextFunc() 中对代码块进行求值时,函数 One() 的本地 nVar 变为可见,尽管它没有直接作为参数传递。
调用函数:调用函数的语法如下:
<idFunction>([<uArgList>])
其中 <uArgList> 是一个可选的逗号分隔参数列表,用于将参数传递给指定函数。 函数使用作为函数声明一部分指定的参数变量,按传递的顺序接收参数。
请注意,如果函数没有参数,则 FUNCTION 语句中不需要使用括号,但在调用时始终需要使用括号。
可以在表达式中调用函数,也可以作为程序语句调用函数。 如果作为程序语句调用,返回值将被忽略。
也可以以别名表达式的形式调用函数,例如:
<idAlias>-><idFunction>([<uArgList>])
执行此操作后,与 <idAlias> 关联的工作区将被选中,函数将被执行,原始工作区将被重新选中。 您可以将别名表达式指定为程序语句,就像指定其他表达式一样。
函数可以递归调用自身。 这意味着您可以在函数的 <FunctionBody> 中引用函数。
调用函数的具体方式取决于您在声明函数时指定(显式或隐式)的调用约定(<idConvention>)。
CLIPPER 调用约定: 如果您声明的函数在参数列表中不包含任何数据类型,则函数默认使用 CLIPPER 调用约定。 您也可以在 FUNCTION 声明语句中指定 CLIPPER 调用约定,前提是不在参数列表中使用强类型。
虽然 CLIPPER 调用约定不允许强类型参数,但它支持函数返回值的强类型。
使用 CLIPPER 调用约定,为函数声明的参数数不必与调用函数时传递的参数数一致。 您可以跳过任何参数,方法是将其从列表中删除(指定两个连续的逗号)或从列表末尾省略。 例如
FUNCTION Start()
MyFunc(1,, 3) // 省略第二个参数
MyFunc(1, 2) // 省略最后一个参数
FUNCTION MyFunc(x, y, z)
...
一个未接收值的参数会被函数自动初始化为NIL,以便您可以检查是否有省略的参数。您可以使用 PCount() 来帮助确定传递的参数数量 — 该函数返回传递的最后一个参数的位置。
在 CLIPPER 函数中指定的任何参数都可以接收按值或引用传递的参数 — 语义是在调用函数时确定的,而不是在声明时确定的。表达式和变量的默认方法是按值传递。除了字段变量,当使用引用运算符(@)作为前缀时,所有变量都是按引用传递的。字段变量不能按引用传递,始终按值传递。
STRICT 调用约定:如果在参数列表中声明了任何数据类型,函数默认使用 STRICT 调用约定。您还可以在 FUNCTION 声明语句中指定STRICT调用约定。
使用 STRICT 调用约定,通过对函数的参数和返回值进行强类型化并声明函数的传递语义,您放弃了 CLIPPER 调用约定允许的许多功能,但在编译速度、应用程序完整性和执行速度方面获得了优势。
STRICT 函数不支持可变数量的参数、PCount() 或在宏表达式中使用的能力。
与 CLIPPER 函数类似,STRICT 函数允许在调用函数时确定调用语义,但仅适用于多态参数(即未强类型化的参数)。当参数被类型化时,调用语义也根据您使用 AS 或 REF 关键字而声明。AS 表示参数必须按值传递,REF 表示参数必须按引用传递(使用引用运算符(@))。
PASCAL调用约定:要指定此调用约定,请在 FUNCTION 声明语句中使用 PASCAL 作为最后一个关键字。从语法上讲,PASCAL 调用约定与 STRICT 相同,使用限制也相同,但在内部处理方式上有所不同。它与 Microsoft Pascal 调用约定相同,主要用于与 Windows 进行低级接口交互。
CALLBACK 调用约定:要指定此调用约定,请在 FUNCTION 声明语句中使用 CALLBACK 作为最后一个关键字。这是一种带有 Windows 序言和尾声的特殊 PASCAL 调用约定。它用于与 Windows 进行低级接口交互。
参数:作为在 FUNCTION 声明语句中指定参数的替代方法,您可以使用 PARAMETERS 语句来指定它们。然而,这种做法并不推荐,因为它效率较低,并且不提供编译时完整性验证。有关更多信息,请参阅本指南中的 PARAMETERS 语句。
本例演示了一个将数值格式化为货币的函数:
FUNCTION Start()
? Currency(1000) // Result: $1,000.00
FUNCTION Currency(nNum)
LOCAL cNum
IF nNum < 0
cNum := Transform(-1 * nNum, ;
"999,999,999,999.99")
cNum := PadL("($" + LTRIM(cNum)+ ")", ;
LEN(cNum))
ELSE
cNum := Transform(nNum, ;
"999,999,999,999.99")
cNum := PadL("$" + LTRIM(cNum), ;
LEN(cNum))
ENDIF
RETURN cNum
下一个示例演示了一个函数,该函数接收一个格式化为逗号分隔列表的字符串,并返回一个数组,每个项包含一个元素:
aList := ListAsArray("One, Two")
// Result: {"One", "Two"}
FUNCTION ListAsArray(cList)
LOCAL nPos
LOCAL aList := {} // 定义空数组
DO WHILE (nPos := AT(",", cList)) != 0
// 增加新元素
AADD(aList, SUBSTR(cList, 1, nPos - 1))
cList := SUBSTR(cList, nPos + 1)
ENDDO
AADD(aList, cList)
RETURN aList // 返回数组
此示例通过将参数与 NIL 比较,检查是否有跳过的参数:
FUNCTION MyFunc(param1,param2,param3)
IF param2 = NIL
param2 := "默认值"
ENDIF ...
这里使用 Currency() 函数(定义如上)作为别名表达式:
USE invoices NEW
USE customer NEW
? Invoices->Currency(Amount)
FIELD, LOCAL, MEMVAR, METHOD, PROCEDURE, RETURN