Show/Hide Toolbars

XSharp

 

X# 提供了对动态作用域变量的支持,这些变量在运行时被完全创建和维护。
所谓动态作用域,是指这些变量的作用域不受创建变量的实体的限制。

 

警告! Core 和 Vulcan 方言不支持动态作用域变量。在其他方言中,只有启用了 -memvar 编译器选项,才支持动态范围变量。

 

变量类型

生存期

可见性

PRIVATE

直到创建过程的 Return 语句或者至释放前

所创建的过程和被调用的例程

PUBLIC

应用程序或至释放前

应用程序

 

动态作用域变量的数据类型会随着变量内容的变化而变化。因此,它们通常被称为动态或多态变量。

提供动态作用域变量主要是为了与 Clipper/Xbase 兼容;不过,它们在某些情况下也非常有用。例如,它们可以让你快速开发原型,并具有某些你可能难以抗拒的继承属性。

不过,您必须意识到,使用它们是有代价的。请考虑以下几点:

 

由于这些变量不是在编译时解决的,因此需要以运行时代码的形式开销,从而使你的应用程序变得比所需的更大更慢。

编译时无法检查这些变量的类型兼容性。

使用这些变量的继承属性违背了模块化编程的基本原则之一,可能会导致维护和调试问题。此外,这种做法还会增加向词法作用域和类型变量过渡的难度。

 

本节将全面探讨动态作用域变量,但在选择使用该变量类之前,X# 还提供了几种变量声明选项。本章接下来的两节将向你介绍词法作用域变量和强类型变量,你可能会发现它们很有用。

重要提示!为了便于说明,本节中的一些示例使用了非正统的编程方法。不建议使用 Public 量和 Private 变量的继承属性,而不是传递参数和返回值。

 

Private

Private 是动态作用域变量的两种类型之一,有几种创建 Private 变量的方法:

 

在 PRIVATE 语句中列出变量名。如果此时不进行赋值,变量将使用 NIL 值和数据类型;否则,变量将使用其赋值的数据类型。您可以随时为变量分配新值(新数据类型):
       PRIVATE X := 10, y
这样就将 x 创建为一个数值变量,将 y 创建为一个值为 NIL 的无类型变量。稍后,您可以通过为变量赋值来更改值及其类型:
 X := "X# Is great"
  Y := Today()

在函数、过程或方法定义的参数语句中列出变量名。当例程被调用时,变量的数据类型与相关参数相同,如果省略参数,则变量的数据类型为 NIL。您可以随时为变量分配新值(和新数据类型)。

为一个不存在的变量名赋值(例如,x := 10)。在为变量赋值之前,变量的数据类型与所赋值相同(只有在使用了 -undeclared-memvar 命令行选项后,这个方法才有效)。

 

Private 变量具有以下属性:

 

你可以在创建例程和创建者调用的任何例程中访问它们。换句话说,被调用的例程会自动继承 Private 变量,而无需将它们作为参数传递。

你可以通过在被调用的例程中明确地创建一个私有变量(使用 PRIVATE 或 PARAMETERS)或声明一个同名的局部变量(使用 LOCAL)来隐藏它们。

当创建者返回到它的调用例程时,它们会自动从内存中释放,或者你也可以使用 RELEASE、CLEAR ALL 或 CLEAR MEMORY 来显式地释放它们。

 

在本例中,函数 Volume() 需要传递三个参数。调用函数时,它会创建三个 Private 变量 nLength、nWidth 和 nHeight 来接受参数。由于这些变量是使用 PARAMETERS 语句创建的,因此使用这些名称创建的任何高级变量(无论是 Public 变量还是 Private 变量)都会被暂时隐藏,以防止它们的值在内存中被覆盖:

FUNCTION Volume()
 PARAMETERS nLength, nWidth, nHeight
 RETURN nLength * nWidth * nHeight

在下一个示例中,Volume() 的修改版创建了一个 Private 变量(假设没有其他名为 nVolume 的变量可见)来存储其返回值。如果变量 nVolume 在调用 Volume() 之前就已存在,并且对 Volume() 可见(例如,对于调用 Volume() 的例程来说,nVolume 可能是公共变量或私有变量),那么它的值将在内存中被覆盖,并在函数返回调用例程时保持不变:

FUNCTION Volume()
 PARAMETERS nLength, nWidth, nHeight
 nVolume := nLength * nWidth * nHeight
 RETURN nVolume

在此版本中,Volume( ) 将 nVolume 变量指定为 PRIVATE。这样做可以暂时隐藏任何同名的上层变量(无论是 Public 变量还是 Private 变量),防止其值在内存中被覆盖:

FUNCTION Volume()
 PARAMETERS nLength, nWidth, nHeight
 PRIVATE nVolume := nLength * nWidth * nHeight
 RETURN nVolume

Public

第二类未声明变量是 Public。Public 变量在整个应用程序中都有生命周期和可见性,你只能用一种方式定义它们:

 

在 PUBLIC 语句中列出变量名。如果此时不赋值,变量的值将是 FALSE(或数组元素的 NIL);否则,变量的数据类型将是其赋值的数据类型。您可以随时为变量赋新值(和新的数据类型)。

 

Public 变量具有以下属性:

 

一旦创建,你就可以在应用程序的任何地方访问它们。换句话说,Public 变量会被应用程序中的所有例程自动继承,而无需将它们作为参数传递或作为返回值发布。

你可以通过明确地创建一个 Private 变量(使用 PRIVATE 或 PARAMETERS)或声明一个同名的局部变量(使用 LOCAL)来隐藏公有变量。

除非使用 RELEASE、CLEAR ALL 或 CLEAR MEMORY 明确地释放它们,否则它们不会从内存中释放。

 

在本例中,函数 Volume() 的定义不带参数。相反,调用例程 Compute() 创建了三个公有变量 nLength、nWidth 和 nHeight,它们对 Volume() 自动可见:

PROCEDURE Compute()
PUBLIC nLength := 5, nWidth := 2, nHeight := 4
? Volume() // 结果: 40
 
FUNCTION Volume()
RETURN nLength * nWidth * nHeight

在下一个示例中,Volume() 的修改版创建了一个 Public 变量来存储计算出的体积,从而避免了向调用例程返回数值的麻烦。由于 nVolume 是公有变量,当 Volume() 返回时,它不会从内存中释放:

PROCEDURE Compute()
PUBLIC nLength := 5, nWidth := 2, nHeight := 4
Volume()
? nVolume // 结果: 40 , 仅在 -undeclared 编译器选项时有效
RETURN
 
PROCEDURE Volume()
PUBLIC nVolume
nVolume := nLength * nWidth * nHeight
RETURN

使用上一个示例中的 nVolume 变量,不需要使用 -undeclared 命令行选项,一个更好的解决方案是

PROCEDURE Compute()
PUBLIC nLength := 5, nWidth := 2, nHeight := 4
MEMVAR nVolume   // 告诉编译器 nVolume 是 Public 或 private 变量
Volume()
? nVolume // 结果: 40
RETURN

PROCEDURE Compute()
PUBLIC nLength := 5, nWidth := 2, nHeight := 4
Volume()
? _MEMVAR->nVolume // 结果: 40
RETURN
 

请注意,不建议使用此类程序。

变量引用

一旦按照前两节的演示创建了 Public 或 Private 变量,就可以通过引用变量名来获取其值。您可以使用内置命令或函数显示变量的值:

? nVolume
QOut(nVolume)

或使用其值作为表达式的一部分:

Str(nVolume, 10, 2) + " cubic feet"

对于动态作用域变量,可以使用 _MEMVAR 别名来限定变量引用。在某些情况下,你可能必须这样做,以帮助编译器解决可能会产生歧义的引用(例如,如果你有一个与内存变量同名的字段变量,并希望在表达式中使用内存变量)。

 

注意:MEMVAR 是内存变量的缩写,与动态作用域变量同义。

 
假设数据库文件 Measures 有名为 nLength、nWidth 和 nHeight 字段,本示例将使用字段变量值调用 Volume():

FUNCTION Calculate()
PRIVATE nLength := 5, nWidth := 2, nHeight := 3
USE measures
? Volume(nLength, nWidth, nHeight)
...

要强制函数使用 Private 变量而不是字段变量,可以使用 _MEMVAR->(或更简单的 M->)别名来限定变量名:

FUNCTION Calculate()
PRIVATE nLength := 5, nWidth := 2, nHeight := 3
USE measures
? Volume(_MEMVAR->nLength, _MEMVAR->nWidth, _MEMVAR->nHeight)
...

当然,最好还是注意使用唯一的字段名和变量名,以避免出现类似上述的含糊不清的情况,但问题是编译器在处理含糊不清的引用时有一定的默认规则。如果不想受这些默认规则的摆布,最好在任何情况下都限定变量名。

MEMVAR 声明

虽然你可能会听到这样的说法,但在讨论动态作用域变量时提到的这些语句并不是声明。声明一词指的是旨在告知编译器某些内容的语句PRIVATE、PARAMETERS 和 PUBLIC 是在运行时生成内存变量的语句。

 

事实上,你从来不需要向编译器声明动态作用域变量,这就是它们效率低下的原因。因为它们不是通过编译时声明语句创建的,所以编译器必须生成运行时代码来处理类型转换、内存管理和解决变量名的模糊引用等问题,因为同一时间有可能出现多个同名变量。

 

不过,你可以使用 MEMVAR 语句声明动态作用域变量,它们将被创建为 PRIVATE 变量:

FUNCTION Calculate()
MEMVAR nLength, nWidth, nHeight
nLength := 5
nWidth := 2
nHeight := 3
USE measures
? Volume(nLength, nWidth, nHeight)

在这种情况下,MEMVAR 语句会使内存变量优先于同名的字段变量,从而导致 Volume() 被调用为 Private 变量。

 

使用 MEMVAR 向编译器声明动态作用域变量名,可能会使程序效率略有提高(尤其是在有大量模糊引用的情况下);但这并不能消除这些变量的运行时开销。