第一章:程序设计简介

Visual FoxPro 将过程化程序设计与面向对象程序设计结合在一起,帮助用户创建出功能强大、灵活多变的应用程序。从概念上讲,程序设计就是为了完成某一具体任务而编写一系列指令;从深层次来看,Visual FoxPro 程序设计涉及到对存储数据的操作。

如果以前没有编过程序,本章将教您从头开始。若您已经熟悉某些程序设计语言,想比较它们与 Visual FoxPro 的异同,请参阅“Visual FoxPro and Other Programming Languages”。有关面向对象程序设计的说明,请参阅第三章“面向对象程序设计”

本章内容要点:

程序设计的优点

通常,在程序中能够完成的工作都可以通过人工操作来完成,但需要足够的时间。例如,在 customer 表中要查找一个特定顾客的信息(如 Ernst Handel 公司),可以通过人工执行一系列指令来实现。

若要以人工方式在表中查找一个订单

  1. 在“文件”菜单中选择“打开”命令。

  2. 在“文件类型”列表框中选择“表”。

  3. 进入 Visual FoxPro 目录下的 samples\data\ 子目录,在文件列表中双击 Customer.dbf。

  4. 在“显示”菜单中选择“浏览”命令。

  5. 滚动遍历表,查找 Company 字段为“Ernst Handel”的记录。

若要通过编程实现上述目的,可以在“命令”窗口中键入下列 Visual FoxPro 命令:

USE samples\data\Customer
LOCATE FOR Company = "Ernst Handel"
BROWSE

在表中找到这家公司以后,就可以执行预定的操作(如将订单的最高限额提高 3 个百分点)。

若要通过人工操作提高订单的最高限额

  1. 选定 maxordamt 字段。

  2. maxordamt 字段值乘以 0.03,再加上原来的字段值,然后在该字段处键入得到的结果。

若要通过编程实现以上结果,可以在“命令”窗口中键入以下命令:

REPLACE maxordamt WITH maxordamt * 1.03

如果要修改某个顾客订单的最高限额,用人工方式或在“命令”窗口中键入指令都很容易实现,但如果要把所有订单的最高限额都提高 3 个百分点,恐怕就不是一件轻而易举的事情。若还是用人工方式来做,不但费时费力,而且还容易出错。解决此类问题的更好办法是编写一个可执行的程序文件,那么该文件可以轻松无误地完成这一工作。

增加所有顾客订单最大限额值的示例程序

代码 注释
USE samples\data\customer
打开 CUSTOMER 表。
SCAN
浏览表中所有记录,针对每条记录执行 SCAN 与 ENDSCAN 之间的所有指令。
REPLACE maxordamt WITH ;
  maxordamt * 1.03
将订单的最高限额提高 3 个百分点。(分号 (;)指令将继续下一行指令。)
ENDSCAN
结束为浏览表中每一记录而执行的代码。

和“命令”窗口中单独键入每条命令相比,运行程序有如下优点:

以下各节将详细介绍 Visual FoxPro 程序设计的概念、结构和流程。

Visual FoxPro 的编程机制

Visual FoxPro 程序由代码组成,代码包括以命令形式出现的指令、函数或 Visual FoxPro 可以理解的任何操作。这些指令包含在:

使用“命令”窗口

可以在“命令”窗口中键入 Visual FoxPro 命令并按 ENTER 键执行。若要重新执行该命令,还可以将光标移到此命令所在行并按 ENTER 键。

甚至可以在“命令”窗口中象执行独立程序一样执行多行代码。

可在“命令”窗口中运行多行代码

  1. 选择代码行。

  2. 按 ENTER 或在快捷菜单中选择“运行所选区域”。

因为“命令”窗口是一个编辑窗口,所以在编辑命令时可以使用 Visual FoxPro 提供的编辑工具。在“命令”窗口中可以编辑、插入、删除、剪切、复制和粘贴正文。

在“命令”窗口中执行命令的优点是:能够立即执行被键入的命令,不需要将其保存为文件并用程序方式执行。

此外,在菜单和对话框中所作的选择可以马上转换为“命令”窗口中的命令。用户可以将这些命令复制并粘贴到 Visual FoxPro 程序中,然后重复执行这些程序。这样做可以很容易地重复执行成百上千条的命令。

创建程序

Visual FoxPro 程序是包含一系列命令的文本文件。在 Visual FoxPro 中,可以通过以下途径创建程序:

若要创建程序

  1. “项目管理器”中,选定“代码”选项卡中的“程序”项。

  2. 选择“新建”命令。

    - 或者 -

  3. 在“文件”菜单中选择“新建”命令。

  4. 在“新建”对话框中选择“程序”。

  5. 选择“新建文件”按钮。

    - 或者 -

Visual FoxPro 打开了一个称为“程序1”的新窗口,这时就可以键入应用程序了。

保存程序

创建程序后,请注意保存。

若要保存程序

若用户要关闭一个没有保存的程序,则会弹出相应对话框,提示用户是保存还是放弃已做的修改。

若用户保存了一个由“项目管理器”创建的程序则该程序被加入项目中。

若用户保存一个尚未命名的程序,则会打开“另存为...”对话框,这样提示用户可以在其中为程序指定程序名。程序保存后,用户可以运行或修改它。

修改程序

程序保存后可以修改。首先,按以下方式打开想要修改的程序:

若要打开程序

打开文件之后便可进行修改,修改完毕后请注意保存。

运行程序

程序创建之后便可运行。

若要运行程序

使用 Visual FoxPro 设计工具编写代码

借助“表单设计器”“类设计器”“菜单设计器”,程序员可以很容易地把程序代码与用户界面连接起来,这样应用程序便可响应用户的输入并执行相应的代码。同样,“报表设计器”将程序代码与报表文件联系起来,以此定制结构复杂并且符合用户要求的报表。

如果想充分发挥 Visual FoxPro 的强大功能,请使用刚才提到的设计工具。有关“报表设计器”的详细内容,请参阅《用户指南》中的第七章“设计报表和标签”

有关“类设计器”的详细内容,请参阅本书第三章“面向对象程序设计”

有关“表单设计器”的详细内容,请参阅本书第九章“创建表单”

有关“菜单设计器”的详细内容,请参阅本书第十一章“设计菜单与工具栏”

程序设计的基本概念

设计程序时,就是用一系列指令存储数据并操作这些数据。程序设计的原材料是数据和数据的存储容器,而处理这些原材料的工具是命令、函数和操作符。

存储数据

用户使用的数据可能包括时间、货币数量以及日期、名称和说明等。每个数据都有其数据类型,属于同一类型的数据可以按相似的方法进行处理。您当然可以直接处理数据而不加以存储,但这样做会失去很大的灵活性,而且 Visual FoxPro 提供的许多功能没有发挥作用。为增强处理数据的能力,Visual FoxPro 提供了多种数据存储容器。

数据类型决定了数据的存储方式和使用方式。两个实数可以做乘法运算,但两个字符型数据不能做乘法运算。同样道理,字符可以用大写方式打印,而数字就不存在大小写的问题。下表列出了 Visual FoxPro 的主要数据类型。

数据类型

类型 示例
数值型 123
3.1415
- 7
字符型 “Test String”
“123”
“01/01/95”
逻辑型 .T.
.F.
日期型
日期时间型
{^1998-01-01}
{^1998-01-01 12:30:00 p}

数据容器

数据容器允许在多个数据上进行相同的操作。例如,将一个雇员工作的小时数加起来,再乘以每小时工作应付的工资,扣除税款后,便可知道一个雇员应得的报酬。若对每个雇员都进行这样的操作将非常麻烦,但若将这些信息保存在数据容器中并对数据容器进行操作,那么,通过运行程序就能实现数据的更新。下表列出了 Visual FoxPro 中主要的数据容器:

类型 说明
变量 在随机存储器 (RAM) 中的单个数据元素。
记录 多行预定义字段,每个字段包含一条预定义数据,表存储在磁盘上。
数组 随机存储器中的多元素数据。

处理数据

数据容器和数据类型构成了处理数据的基础,而对数据的处理最终要通过操作符、函数和命令来实现。

使用操作符

操作符是联系数据的纽带。这里介绍的是 Visual FoxPro 中最常用的操作符。

操作符 适用的数据类型 示例 结果
= 全部数据类型
? n = 7
若变量 n 中的值等于 7,打印 .T.;否则打印 .F.
+ 数值型、字符型、日期型、日期时间型
? "Fox" + "Pro"
打印“FoxPro”
! 或 NOT 逻辑型
? !.T.
打印 .F.
*, / 数值型
? 5 * 5
? 25 / 5
打印 25
打印 5

注释 表达式前的问号 (?) 会在当前活动的输出窗口(通常是 Visual FoxPro 的主窗口)中另起一行,显示表达式的结果。

注意,操作符应与数据类型相匹配。下面的语句将两个数值赋给两个变量,因为变量名的首写字母为 n,所以马上知道该变量保存的是数值型数据,但这样的规定只是约定的,实际上,您可以使用字母、数字及下划线的任意组合为变量命名。

nFirst = 123
nSecond = 45

下面的语句将两个字符串赋值给两个变量,变量名首写字母 c 表明该变量保存的是字符型数据。

cFirst = "123"
cSecond = "45"

下面两个操作是加法操作和连接操作,因变量的数据类型不同而产生不同的结果。

? nFirst + nSecond
? cFirst + cSecond

输出

168
12345

因为 cFirst 是字符型数据,而 nSecond 是数值型数据,所以执行下面操作时将出现数据类型不匹配的错误:

? cFirst + nSecond

可以使用类型转换函数来避免出现上述错误。例如,STR( ) 将数值转化为字符后返回,而 VAL( ) 将一个由数字组成的字符串转化为数值。这些函数与删掉前导空格的 LTRIM( ) 函数连用,可以进行如下操作:

? cFirst + LTRIM(STR(nSecond))
? VAL(cFirst) + nSecond

输出

12345
168

使用函数

函数可返回特定类型的数据。例如,上面用过的 STR( ) 函数和 VAL( ) 函数分别返回字符型和数值型数据,您可以通过这些函数所带的文档来了解其返回值类型。

可以用以下五种方法调用 Visual FoxPro 函数:

另外,本章中用到的其他一些函数如下表所示:

函数 说明
ISDIGIT( ) 若字符串最左端字符为数字,则返回“真”(.T.),否则返回“假”(.F.)。
FIELD( ) 返回字段名。
LEN( ) 返回字符表达式的字符数目。
RECCOUNT( ) 返回当前活动表的记录数目。
SUBSTR( ) 从一个字符串的指定位置开始,返回指定字符数目的一个子串。

使用命令

命令即为完成一个特定动作的指令。每条命令都有自己特定的语法,用来说明为实现该命令的功能所必须包含的内容。此外,与命令有关的还有一些可选子句,这些关键字可进一步指导命令干些什么。

例如,USE 命令可以打开表或关闭表:

USE 语法 说明
USE
关闭当前工作区中的表。
USE customer
在当前工作区中打开 CUSTOMER 表,同时关闭当前工作区中所有已打开的表。
USE customer IN 0
在下一个可用工作区中打开 CUSTOMER 表。
USE customer IN 0 ;
  ALIAS mycust
在下一个可用工作区中打开 CUSTOMER 表,并且指定该工作区的别名为 mycust

本章中用到的其他一些命令如下表所示:

命令 说明
DELETE 在表中给某些记录作删除标记。
REPLACE 更新字段值。
GO 将表中记录指针移到指定的位置。

程序流的控制

Visual FoxPro 中有一类特殊的命令,它们可以反复执行其他命令或函数,并决定这些命令和函数何时执行以及执行的次数。这类特殊命令可用来实现两种主要的程序结构:条件分支循环,它们在程序设计过程中作用很大。下面的程序将演示条件分支结构和循环结构。示例之后是这些概念的详细说明。

假设您是一位老板,有 10,000 个雇员,并且,您要给年薪多于或等于 $30,000 的雇员涨 3% 的工资,给年薪低于 $30,000 的雇员涨 6% 的工资。下面的示例程序将完成这一任务。

此示例程序假定在当前工作区中已打开了一个表,此表有一个名为 salary 的数值型字段。有关工作区的详细内容,请参阅第七章“处理表”中的“使用多个表”部分。

增加雇员薪金的示例程序

代码 注释
SCAN
在 SCAN 与 ENDSCAN 之间程序代码的执行次数等于表中的记录个数,每次执行之后,记录指针移向表中的下一条记录。
   IF salary >= 30000.00
      REPLACE salary WITH ;
         salary * 1.03
检查每条记录,若 salary 字段值大于等于 30,000,则以高出此值 3% 的数值替换。

WITH 后的分号 (;) 表示下一行是本行命令的继续行。

   ELSE
      REPLACE salary WITH ;
         salary * 1.06
检查每条记录,若 salary 字段值小于 30,000,则用大于此值 6% 的数值替换。
   ENDIF
ENDSCAN
结束 IF 语句。

结束 SCAN 语句。


该示例程序使用了条件分支和循环来控制程序的流程。

条件分支

条件分支根据条件的测试结果执行不同的操作。Visual FoxPro 中有两条命令实现条件分支:

只有逻辑条件值为“真”(.T.) 时,才会执行在初始语句和 ENDIF 或 ENDCASE 语句之间的程序。示例程序中,IF 命令区分两种状态:一种是 salary 值大于等于 $30,000,另一种是 salary 值小于 $30,000。根据不同的状态值执行不同的操作。

在下面的示例中,若变量 nWaterTemp 的值小于 100,将不执行任何操作。

* 若条件成立,则置逻辑变量值为真。
IF nWaterTemp >= 100
   lBoiling = .T.
ENDIF

注释 程序行开始的星号说明该行是注释,注释可以帮助程序员回忆每段代码的设计目的。编译、执行程序时,Visual FoxPro 将忽略这些注释。

若要判断多种可能的情况,DO CASE ... ENDCASE 结构将比用多个 IF 语句更有效,并且易于跟踪调试。

循环

循环结构可以按照需要多次重复执行一行或多行代码。在 Visual FoxPro 中有三种循环语句:

如示例程序所描述的那样,若对表中全部记录执行某一操作,可以使用 SCAN。随着记录指针的移动,SCAN 循环允许对每条记录执行相同的代码块。

若事先知道循环次数,则可以使用 FOR 循环。例如,已知表中的字段数(字段数可以用函数 FCOUNT( ) 得到),那么就可以用一个 FOR 循环打印表中的全部字段名。

FOR nCnt = 1 TO FCOUNT( )
   ? FIELD(nCnt)
ENDFOR

想要在某一条件满足时结束循环,可以使用 DO WHILE 语句。使用 DO WHILE 结构,事先可以并不清楚循环的次数,但需要知道什么时候结束循环。例如,假定一张表包含员工的姓名和姓名缩写,需要使用姓名缩写来查找某些人员的信息。当您想要向表中加入一个姓名缩写时,如果表中已有了一个这样的姓名缩写,便会出现问题。

通过给姓名缩写加一个数字的方法,可以解决这个难题。例如,Michael Suyama 的缩写为 MS,下一人 Margaret Sun 的姓名缩写与之相同,则将其替换为 MS1。以后,Michelle Smith 的姓名缩写便是 MS2,依次类推。这个过程可以用一个 DO WHILE 循环实现。

用 DO WHILE 语句产生唯一标识的示例程序

代码 注释
nHere = RECNO()
保存目前记录位置。
cInitials = LEFT(firstname,1) + ;
   LEFT(lastname,1)
nSuffix = 0
取姓和名字段的首写字母构成一个人的姓名缩写。

建立一个变量以便在需要时保存要添加到姓名缩写后的数字。

LOCATE FOR person_id = cInitials
查看表中是否有另一个人的姓名缩写与之相同。
DO WHILE FOUND( )
若找到与 cInitials 相同的 person_id 值,则函数 FOUND( )返回“真”(.T.) ,执行 DO WHILE 循环。
否则,将执行 ENDDO 之后的第一条语句。
   nSuffix = nSuffix + 1
   cInitials = ;
      LEFT(cInitials,2);
 + ALLTRIM(STR(nSuffix))
准备新的后缀值,并将其加在姓名缩写的末尾。
   CONTINUE
CONTINUE 使最近执行的 LOCATE 命令再一次求值。程序检查在 cInitials 中的值是否在另一个记录的 person_id 字段中出现过,如果存在,则 FOUND( ) 将仍然返回“真”(.T.),并且在 DO WHILE 循环体中的语句再一次执行。如果 cInitials 中的值是唯一的,那么 FOUND( ) 将返回“假”(.F.),程序将继续执行 ENDDO 的下一个语句。
ENDDO
结束 DO WHILE 循环。
GOTO nHere
REPLACE person_id WITH cInitials
返回记录并将唯一标识代码存储在 person_id 字段中。

因为无法预知可能匹配的标识代码会被找到多少次,因此这里要用 DO WHILE 循环。

程序设计的过程

掌握了一些基本概念后,程序设计就变成了一个不断重复的过程。您需要多次重复某些步骤,并在这个过程中不断对代码进行优化。从编写第一行程序开始,便不断进行测试,完成每个“试验 — 查错”的过程。对语言越熟悉,则编程速度越快,而且更多的初步测试工作将在头脑中进行。

程序设计的基本步骤包括:

开始程序设计之前,请注意以下几个问题:

本节以下部分将详细介绍建立一个小型 Visual FoxPro 程序的各个步骤。

对问题进行说明

开始解决问题以前,需要将其说明清楚。有时调整问题的说明方式会有助于问题的解决。

假设从不同的数据源获得一批数据,其中大部分是数值型数据,但有些数据,除包含数字外,也夹杂着一些虚线和空格。现在需要从字段中清除这些空格和虚线,并将所得到的数值型数据加以保存。

可以按如下方式说明这个程序要达到的目标,而不是简单地立即着手从原始数据中除去空格和虚线。

目标 用新值替换字段中的原始值,新值包含原始值中除空格和虚线外的所有内容。

这种说明方式的优点在于可以避免在处理可变长度字符串时遇到问题。

分解问题

因为最终是以操作、命令和函数的方式将具体的指令提供给 Visual FoxPro,所以需要将问题分解为多个独立的步骤。在问题中,最容易分离出来的任务是在字符串中逐个扫描字符,只有将单个字符分离出来才能决定是否应该保存它。

在扫描字符的时候,将检查它是否为虚线或空格。到了这一步也许您想要更详细地说明这个问题,括号之后的数据应该如何提取,货币符号、逗号和句号该如何消除等一系列问题将出现在脑海中。代码越通用,为将来节省的工作量就越多,问题就在于如何做到事半功倍。下面给出了一个比从前适用范围更广的问题说明方式。

精炼后的目标 用原始数据中的数值型字符更新字段值。

这便是在字符的层次上重新说明问题。如果字符是数字则保留,否则将指针移向下一个字符。当用原始字符串中的数字重新构成一个只含有数字元素的新串时,将这个新串替换掉原来的字符串。如此循环,直到将所有记录中的数据全部更新。

概括起来,全部问题可以分解成以下一些模块:

  1. 逐个检查字符。

  2. 判断该字符是否为数字。

  3. 若是数字,则将其复制到新串。

  4. 检查完字符串中的全部字符后,用只含数字的新串替换原串。

  5. 重复上述步骤,直到表中全部记录都被更新。

编制模块

明确了要达到的目标以后,便可以开始使用 Visual FoxPro 的命令、函数和操作符来构造各模块。

因为命令和函数是用来处理数据的,所以需要一些数据来测试其功能。这些用于测试的数据应与实际数据尽量相近。

例如,在“命令”窗口中键入如下命令,将一个测试字符串赋值给变量:

cTest = "123-456-7 89 0"

逐个检查字符

首先,需要在字符串中检查单个字符。有关可用于操作字符串的函数列表,请参阅“帮助”中的“字符函数”

您将发现三个用以返回字符串特定部分的函数:LEFT( )RIGHT( )SUBSTR( ),其中 SUBSTR( ) 可以返回字符串中的任意子串。

SUBSTR( ) 需要三个参数:字符串、起始字符的位置和需返回的字符数。若要了解 SUBSTR( ) 是否可完成所需工作,请在“命令”窗口中键入以下命令:

? SUBSTR(cTest, 1, 1)
? SUBSTR(cTest, 3, 1)
? SUBSTR(cTest, 8, 1)

输出

1
3
-

用于测试的字符串中的第一个、第三个和第八个字符将显示在 Visual FoxPro 的主窗口中。

可以使用循环结构多次重复执行相同操作。因为用于测试的字符串有固定的字符数 (14),所以能够使用 FOR 循环语句。每执行一遍,FOR 循环的计数器增加 1,因此可在 SUBSTR( ) 函数中使用该计数器。因为在“命令”窗口中不能测试循环结构,所以我们编写一个示例程序。

若要创建新程序

  1. 在“命令”窗口中键入如下命令:
    MODIFY COMMAND numonly
    
  2. 在打开的窗口上键入如下代码:
    FOR nCnt = 1 TO 14
    ? SUBSTR(cTest, nCnt, 1)
    ENDFOR
    

创建程序之后便可运行。

若要运行程序

  1. 在打开的程序窗口中,按下 CTRL+E 键。

  2. 若“保存”对话框出现,选择“是”按钮。

运行此程序时,各个结果将在 Visual FoxPro 主窗口中分行显示。

测试程序片段

现在第一步工作已经结束,让我们来逐个检查字符串中的字符。

判断字符是否为数值

从字符串中分离出单个字符之后便可判断它是否为数值。在“字符函数”帮助主题中,ISDIGIT( ) 可以完成此项功能。

在“命令”窗口中,键入以下命令来测试 ISDIGIT( ):

? ISDIGIT('2')
? ISDIGIT('-')
? ISDIGIT(SUBSTR(cTest, 3, 1))

输出

.T.
.F.
.T.

输出结果可以看出:在 cTest 中,‘2’是数值,‘-’不是,‘3’是数值。

若字符为数值,将它复制到新串中

既然可以逐一检查字符是否为数值,下面我们可以使用变量 cNumOnly 来保存该数字。

为了创建一个变量,需要给它赋一个初值:一个零长度的字符串。

cNumOnly = ""

因为 FOR 循环扫描整个字符串,所以最好用一个变量来暂存每次分离出来的字符以便处理。

cCharacter = SUBSTR(cTest, nCnt, 1)

提示 在计算、求值以及函数中得到的结果最好保存到内存变量中,这样便于直接处理这些变量而不必重新计算或求值。

可以使用以下代码将每个被确认为数字的字符添加到新串中:

cNumOnly = cNumOnly + cCharacter

这个程序是:

cNumOnly = ""
FOR nCnt = 1 TO 14
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR

测试模块

如果将两行用于输出字符串的命令加在程序末尾并运行程序,就可以看出程序如何对测试字符串进行操作。

cNumOnly = ""
FOR nCnt = 1 TO 14
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? cTest
? cNumOnly

输出

123-456-7 89 0
1234567890

输出似乎是正确的,但在测试模块的过程中改变被测试的字符串将出现问题。在“命令”窗口中,键入以下命令再运行程序:

cTest = "456-789 22"

程序产生错误信息,FOR 循环应执行 14 次而测试串中只有 10 个字符。这样看来,对于可变长字符串需要用另外的方法。若再次查看帮助中“字符函数”将发现,函数 LEN( ) 将返回字符串中字符的个数。如果把它放到 FOR 循环语句中,那么程序在两种情况下都可以良好地运行。

cNumOnly = ""
FOR nCnt = 1 TO LEN(cTest)
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? cTest
? cNumOnly

组装全部模块

若要完善此程序,则需要从表中读取数据。在对表操作时,需要逐条扫描记录,针对记录中的字段而非变量进行操作。

可以先建立一个临时的表保存一些示例字符串,此表可以只含有一个字符型字段 TestField 和四五个记录。

TestField 字段值
123-456-7 89 0 -9221 9220 94321 99-
456-789 22 000001 98-99-234
789 22

用字段名替换测试字符串名之后,程序如下所示:

FOR nCnt = 1 TO LEN(TestField)
   cCharacter = SUBSTR(TestField, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? TestField
? cNumOnly

在滚动并浏览表时,可以人工调整记录指针,当记录指针指向某记录时,程序将处理该记录中的数据。也可以采用另一种方法:在原有程序中,在处理表记录的循环体代码外部添加一个遍历结构 SCAN。

SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
      cCharacter = SUBSTR(TestField, nCnt, 1)
      IF ISDIGIT(cCharacter)
         cNumOnly = cNumOnly + cCharacter
      ENDIF
   ENDFOR
? TestField
? cNumOnly
?
ENDSCAN

输出

123-456-7 89 0
1234567890

456-789 22
45678922

-9221 9220 94321 99-
922192209432199

000001 98-99-234
0000019899234

整体测试

也许用户需要将运行结果保存在表中,而不是仅仅打印出来,那么下行代码将完成此项工作:

REPLACE TestField WITH cNumOnly

完整的程序如下所示:

SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
      cCharacter = SUBSTR(TestField, nCnt, 1)
      IF ISDIGIT(cCharacter)
         cNumOnly = cNumOnly + cCharacter
      ENDIF
   ENDFOR
   REPLACE TestField WITH cNumOnly
ENDSCAN

完成全部程序代码之后,在处理实际数据之前,必须用示例数据进行测试。

使程序更可靠

所谓可靠的程序是指一个程序不仅仅能够完成设计的功能,还可以预料到可能出现的错误并进行排错处理。上面的程序包含两个假设,只有满足这两个假设条件,程序才能够正确地运行。

如果当前工作区中没有已打开的表,或者表中没有名为 TestField 的字符型字段,那么程序运行时将产生错误信息,并且无法完成任务。

删除某字段所有记录中非数字字符的示例程序

代码 注释
lFieldOK = .F.
变量确认程序正常运行的条件是否成立。初值为“假”(.F.),假设条件不成立。
FOR nCnt = 1 TO FCOUNT( )
   IF FIELD(nCnt) = ;
      UPPER("TestField")
      IF TYPE("TestField") = "C"
         lFieldOK = .T.
      ENDIF
      EXIT
   ENDIF
ENDFOR
这部分代码检查当前表的每一个字段,直到发现有一个名为 TestField 的字符型字段,发现该字段存在后,设置 lFieldOK 为“真”(.T.),同时执行 EXIT 指令跳出循环体(找到所需字段后,没必要再继续查找),否则 lFieldOK 变量仍为“假”(.F.)。
IF lFieldOK
若当前活动表含有名为 TestField 的字符型字段,则运行程序中字符串转换部分的代码。
SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
     cCharacter = ;
    SUBSTR(TestField, nCnt, 1)
     IF ISDIGIT(cCharacter)
     cNumOnly = cNumOnly + ;
     cCharacter
     ENDIF
   ENDFOR
转换代码。
   REPLACE TestField WITH ;
  cNumOnly
ENDSCAN
ENDIF
结束 IF lFieldOK 条件。

该程序最大的局限在于只能处理一个字段。若需要对一个字段名不是 TestField 的字段进行同样操作,就不得不从头检查程序,把全部的 TestField 改写为所要处理的字段名。

若将该程序改写为过程,可使代码的通用性和可重用性大为提高,并将减少以后的工作量。以下各节将对此进行阐述。

使用过程和用户自定义函数

过程和函数可以将常用代码集中在一起,供应用程序在需要时调用。这样做提高了程序代码的可读性和可维护性,使您可以不必对程序进行多次修改,只变动一个地方就足够了。

Visual FoxPro 的过程如下所示:

PROCEDURE myproc
  *本行是注释,但也可为可执行代码。
ENDPROC

习惯上,过程是为完成某个操作而编写的代码,函数用来计算并返回一个值。在 Visual FoxPro 中,这二者区别不大。

FUNCTION myfunc
  * 本行为注释,但也可作为可执行代码
ENDFUNC

可以将过程和函数保存在单独的程序文件中,也可放在一般程序的结尾。不能把可执行的主程序代码放在过程或函数之后。

若将过程或函数放在单独的程序文件中,可以在应用程序中使用 SET PROCEDURE TO 命令访问它们。例如,保存过程或函数的文件名为 FUNPROC.PRG,可在“命令”窗口中使用下面的命令调用它们:

SET PROCEDURE TO funproc.prg

调用过程或函数

在程序中有两种调用过程或函数的方式:

如果向过程或函数发送值或接收它们的返回值,则两种方式都可以加以扩展。

向过程或函数发送值

若要向过程或函数传递值,可以使用参数。例如,下面的过程接收一个参数:

PROCEDURE myproc( cString )
   * 下行显示一条信息
   MESSAGEBOX ("myproc" + cString)
ENDPROC

注释 在过程或函数定义行的括号中包含参数,表明该参数的作用域仅为该过程或函数,例如 PROCEDURE myproc (cString)。也可以使用 LPARAMETERS,让函数或过程来接收局部作用域的参数。

在函数中,参数以同样方式工作。要向函数或过程传递参数值,可以使用字符串或包含字符串的变量,如下表所示。

被传递的参数

代码 注释
DO myproc WITH cTestString
DO myproc WITH "test string"
调用过程,并传递原义字符串或字符变量。
myfunc("test string")
myfunc( cTestString )
调用函数,并传递原义字符串或字符变量的副本。

注释 若在调用过程或函数时不是用 Do 命令,UDFPARMS 设置控制如何传递参数。在默认条件下,UDFPARMS 设置为 VALUE,即传递参数的副本。在使用 DO 命令调用过程或函数时,实际的参数被传递(以引用方式传递参数)。这时,在过程或函数运行过程中,参数值的任何变化都将反映到原始数据中,而与 UDFPARMS 状态无关。

可以向过程或函数传递多个参数,参数之间用逗号分开。例如,下面的过程有三个参数:日期、字符串和数字。

PROCEDURE myproc( dDate, cString, nTimesToPrint )
   FOR nCnt = 1 to nTimesToPrint
      ? DTOC(dDate) + " " + cString + " " + STR(nCnt)
   ENDFOR
ENDPROC

下面的代码将调用这个过程:

DO myproc WITH DATE(), "Hello World", 10

接收函数的返回值

函数默认的返回值是“真”(.T.),但可以使用 RETURN 命令返回任意值。例如,下面的函数返回比参数值晚两周的日期。

FUNCTION plus2weeks
PARAMETERS dDate
   RETURN dDate + 14
ENDFUNC

下面的代码将此函数的返回值保存在一个变量中:

dDeadLine = plus2weeks(DATE())

要保存和显示函数的返回值,如下表所示:

处理返回值

代码 注释
var = myfunc( )
将函数的返回值保存于变量中。
? myfunc( )
在活动输出窗口中打印函数的返回值。

在过程或函数中检验参数

检验发送的参数是否为过程或函数所要接收的数据是一种良好的设计习惯,TYPE( )PARAMETERS( ) 可以用来检验参数的类型和个数。

例如,上面提到的函数需要接收一个日期型参数,可以用 TYPE( ) 函数检验所接收的参数类型是否正确。

FUNCTION plus2weeks( dDate )
   IF TYPE("dDate") = "D"
      RETURN dDate + 14
   ELSE
      MESSAGEBOX( "You must pass a date!" )(“必须传递一个日期型数据!”)
      RETURN {}      && 返回空的日期
   ENDIF
ENDFUNC

当过程所接收的参数多于所需要的个数时,Visual FoxPro 将产生一个错误信息。例如,如果您只列出了两个参数,却使用三个参数调用它,这时将会出错。但如果过程接收的参数个数小于所要求的数目,则 Visual FoxPro 仅将余下的参数赋初值为“假”(.F.),而不产生出错信息,因为无法得知最后的参数是被置为“假”值,还是被忽略。下面的过程确认参数个数是否正确。

PROCEDURE SaveValue( cStoreTo, cNewVal, lIsInTable )
   IF PARAMETERS( ) < 3
      MESSAGEBOX( "Too few parameters passed." )
      RETURN .F.
   ENDIF
   IF lIsInTable
      REPLACE (cStoreTo) WITH (cNewVal)
   ELSE
      &cStoreTo = cNewVal
   ENDIF
   RETURN .T.
ENDPROC

将 NUMONLY 程序转换成函数

“程序设计的过程”一节介绍的 NUMONLY.PRG 程序中,可将删除字符串中非数字字符的那部分程序代码转换为函数,使其运行更可靠。

返回字符串中所有数字字符的示例过程

代码 注释
FUNCTION NumbersOnly( cMixedVal )
函数头,参数为一个字符串。
  cNumOnly = ""
  FOR nCnt = 1 TO LEN(cMixedVal)
   cCharacter = ;
  SUBSTR(cMixedVal, nCnt, 1)
    IF ISDIGIT(cCharacter)
       cNumOnly = ;
   cNumOnly + cCharacter
    ENDIF
   ENDFOR
从原始串中建立只含数字的新串。
RETURN cNumOnly
返回只含数字的新串。
ENDFUNC
函数结尾。

除了可以使用户在许多条件下使用这段代码之外,函数还提高了程序的可读性。

SCAN
   REPLACE FieldName WITH NumbersOnly(FieldName)
ENDSCAN

或者,可更简单成:

REPLACE ALL FieldName WITH NumbersOnly(FieldName)

以下章节的内容简介

过程化程序设计和面向对象程序设计相结合,再加上 Visual FoxPro 的设计工具,可以帮助用户开发出功能全面、适用范围广的应用程序。本书以下章节将讨论在开发 Visual FoxPro 应用程序过程中会遇到的问题。

有关面向对象程序设计的详细内容,请参阅第三章“面向对象程序设计”。若要学习如何使用表单设计器创建表单,请参阅第九章“创建表单”