江苏省高校计算机等级考试命题研究院 江苏省高校计算机等级考试辅导
江苏省二级VFP常用编程命令及常用函数
VFP常用编程命令及常用函数
常用编程命令及常用函数
注:在语法中如用方括号“[  ]”括起来的词句,表示可以不用。
常用命令

假设(if...endif)
循环(do while...enddo)
分支(do case...endcase)
赋值(store...to)
调用表单(do form)
启动事件处理(read events)
清除事件处理(clear events)
开关命令执行状态(set talk on/off)
结束程序(cancel)
 

 常用函数

取年份(year())
取月份(month())
取天日(day())
取整(int())
四舍五入(round())
删除标记(delete())
记录号(recno())
记录数(reccount())
找到记录(found())
表结尾(eof())
表开头(bof())
 
 

假设语句
根据一逻辑表达式的值,有条件的执行一组命令。
语法
IF 逻辑表达式 [THEN]
    程序组1
[ELSE
    程序组2]
ENDIF
参数
逻辑表达式代表一逻辑值,如果这逻辑值为真,系统执行程序组1,否则不执行程序组1,如果有else及程序组2的话,则执行程序组2,如没有则什么也不执行,直接执行endif后面的语句。
备注
在if...endif之间还可以有if...endif,即该命令是可以嵌套的。
then可以有也可以没有,对程序没有影响。
else及程序组2可以有也可以没有,但如果有的话,在逻辑表达式的值为假时,程序组2将被执行。
if 与 endif 必须配对使用,即有一个 if 必须有一个 endif, 否则程序会出错。
举例
例1:假如分数字段的值大于等于90,则让等级字段的值为“优”,并显示该记录。程序如下:
if 分数>=90
  replace 等级 with '优'
  display
endif
 例2:在数据库中可以把性别字段设为逻辑型,“真”代表“男”,“假”代表“女”,但显示时不能显示真、假,否则别人看不懂,而应显示男、女,因此我们设一变量xb,根据性别字段的具体值,将xb的值设为男或女,然后在需要的时候显示xb的值即可。程序如下:
if  性别
  xb='男'
else
  xb='女'
endif
其中第一句:if  性别,表示“假如性别为真”的意思,不要写成“if  性别=.t.”。
以上程序也可以写成如下形式,效果相同:
if  .not. 性别
  xb='女'
else
  xb='男'
endif
即,假如性别不为真的话,xb的值就为女,否则为男。
循环语句(do while...enddo)
根据条件重复执行一组程序。
语法
DO WHILE lExpression
    Commands
    [LOOP]
    [EXIT]
ENDDO
参数
lExpression 为一逻辑表达式,其值如果是“真”,即执行commands程序组,如果表达的值为“假”,则跳过该段程序组,执行enddo后面的语句。
Commands 当表达式 lExpression 值为真时所要执行的程序组。
LOOP 此为 do while...enddo 命令的一个子句,它可以放在 commands 程序组中间的任何地方,当程序一旦执行到该子句时,则返回 do while 重新执行。此子句根据需要可以有,也可以没有。
EXIT 与loop一样是 do while...enddo 命令的一个子句,它可以放在 commands 程序组中间的任何地方,当程序一旦执行到该子句时,则跳出循环,执行enddo后面的语句。此子句根据需要可以有,也可以没有。
备注
程序一旦执行到do while 语句,如果 lExpression 的值为真,则执行 commands 程序组,该程序组执行完后,就到enddo语句,enddo会将程序返回 do while 再次验证 lExpression 是否为真,如仍是,则又一次执行 commands ,如此循环往复,直到 lExpression 为假,或遇上 exit 语句。
该语句可以嵌套,即循环中还可以有循环。
do while 与 enddo 必须配对使用,即有一个 do while 必须有一个 enddo 否则程序会出错。
举例
例1:在数据库中不断将记录指针往下移,直到数据库结尾。程序如下:
do while .not. eof()
  skip
enddo
 例2:在上面的例子加入此功能,当遇到性别字段为“女”时,跳出循环。程序如下:
do while .not. eof()
  if  性别='女'
    exit
  endif
  skip
enddo
 例3:不断将记录指针往下移,当遇到数量字段的值大于等于600时退出循环,如遇到值小于400的,将其值乘2,然后再检验一次是否大于600,如是则退出循环。程序如下:
do while 数量<600
  if  数量<400
    replace 数量 with 数量*2
    loop
  endif
  skip
enddo
分支语句(do case...endcase)
根据不同的条件执行不同的程序组。
语法
DO CASE
    CASE 逻辑表达式1
        程序组1
    [CASE 逻辑表达式2
        程序组2
    ...
    CASE 逻辑表达式n
        程序组n]
    [OTHERWISE
        程序组0]
ENDCASE
参数
CASE 逻辑表达式 程序组 ... 当程序执行到 do case 时,便检验第一个 case 的逻辑表达式(逻辑表达式1)是否为真,如不为真,接着检验第二个 case 的逻辑表达式,以此类推直到逻辑表达式n。当检测到第一个为真的逻辑表达式时,便执行跟随在其后面的程序组,执行完后跳过后面所有的 case ,接着执行 endcase 后面的语句,也就是说即使后面还有为真的逻辑表达式也不执行了。
如果所有 case 后面的逻辑表达式都为假,则执行 otherwise 后面的程序组(如果有的话, otherwise 及其程序组是可以没有的,如果没有则什么都不做,直接执行 endcase 以后的程序)
备注
在do case 和 endcase 之间可以有任意多个 case 。
do case 和 endcase 必须配对使用,即有一个 do case 必须有一个 endcase ,否则程序会出错。
举例
如果工资在200元以下,增加50%;300元以下,增加30%;500元以下,增加20%;其它增加10%。程序如下:
do case
  case 工资<=200
    replace 工资 with 工资*1.5
  case 工资<=300
    replace 工资 with 工资*1.3
  case 工资<=500
    replace 工资 with 工资*1.2
  otherwise
    replace 工资 with 工资*1.1
endcase
在这里要注意一个问题,不能把300元的 case 放在200元之前,否则一个100元的,按理应加50%,但当遇到小于300元的 case 时,其逻辑表达式为真(小于200元的肯定小于300元),因此就会执行后面的程序,加30%,并且执行完后,就不再执行其它的 case 了,这样就产生了错误的结果,所以应按从小到大的顺序来排列。
赋值语句(store...to)
将一个数据赋给一个变量。
语法
STORE 表达式 TO 变量名表
参数
表达式的值即为要赋给变量的数据。
变量名表即为要被赋值的各变量。在这里可以是一个变量,也可以是多个变量,如果有多个变量,其间用“,”(逗号)隔开。
备注
如果是给一个变量赋值,该语句可写成如下形式:
变量名=表达式
表达式可以是一个数值,也可以是一个算术式。
举例
例1:将3赋给ab、xyz、jfz三个变量,程序如下:
store 3 to ab,xyz,jfz
例2:将变量gz的值加100赋给yfgz。程序如下:
yfgz=gz+100
调用表单语句
运行一个由表单设计器设计的表单文件。该文件是经编译过的。
语法
DO FORM 表单文件名 [NAME 变量名 [LINKED]]
参数
表单文件名即是要运行的由表单设计器设计的表单文件名称。
变量名为调用该表单所用的变量名称,做为表单,不能直接用这的名称去调用它,必须将其赋给一个变量,然后用这个变量来调用它。如果您不会在这个表单之外调用它,也可以不要这个变量。
在程序中产生的所有变量在程序运行结束后将被释放,即这些变量不再存在,因此也就无法继续调用这些变更,如果为了调试程序需要在程序运行结束后在命令窗口中调用这个表单,必须加上 linked 子句。
举例
在程序中调用xy7表单,并将赋给一个变量lucky,程序如下:
do form xy7 name lucky
调用子程序语句(do)
运行一个VFP程序。当我们要在一个程序中调用另一个程序(子程序)时使用此命令。
语法
DO 程序名
参数
程序名即为被调用的程序名称。
备注
如被调用的程序的扩展名是“prg”,调用时可不用带扩展名,否则要带上扩展名。
举例
有一个程序,名称为 xy7.prg,调用它的程序如下:
do xy7
返回调用程序语句(return)
返回调用本程序(该语句所在程序)的程序。
前面讲过调用子程序的语句,从一个程序A调用另一个程序B后,系统便开始执行B程序中的语句,到一定时候往往要从程序B返回程序A,便可使用该语句。
语法
RETURN
备注
程序A调用程序B,当从B返回A后,系统接着执行调用语句(do b)下面的一条语句。
举例
程序a.prg如下:
do while .not. eof()
if 工资<100
do b
endif
skip
enddo
程序b.prg如下:
replace 工资 with 工资*1.5 &&将工资增加50%
display &&显示出该记录,这样可以将所有增加了工资的记录显示出来
return
首先执行程序a.prg,当程序执行到 do b 语句时,便转去执行程序 b.prg ,在程序 b 中执行到 return语句时,又返回程序 a ,并接着执行 do b 的下一条语句 endif 。
启动事件处理语句(read events)
启动VFP的事件处理程序。
语法
READ EVENTS
备注
当该命令执行后,系统即停止继续执行后续的语句,这时我们可以调用之前所启动的菜单、表单等对象,并用这些对象的事件程序去完成相应的任务,直到发出 clear events 命令,系统才接着执行 read events 后面的命令语句。
可能初学者看了上面的内容还不是很清楚,不要紧,我们在后面课程中会进一步讲解。
清除事件处理语句(clear events)
终止由 read events 语句启动的事件处理程序。
语法
clear events
备注
发出该命令后,系统将继续执行 read events 之后的语句。
开关命令执行状态语句(set talk on/off)
确定是否显示VFP命令执行的状态。
语法
SET TALK ON | OFF
参数:
ON 显示VFP命令执行的状态。
OFF 不显示VFP命令执行的状态。
备注:
使用时,on、off 二者之中必须选择一个。
很多VFP命令执行后,会显示执行后的结果状态,如 locate for 命令执行,如找到记录的会显示被找到的记录号,否则会显示“已到文件尾”,但一般我们在程序中是不需要这些显示的,比如找到了记录就直接显示出来,没找到一般用一个对话框来给出更清楚的提示,所以在程序一开始往往要将 set talk 关闭。
结束程序语句(cancel)
结束当前正在运行的所有程序,返回VFP或操作系统。
语法
cancel
备注
数值转换字符函数(str())
返回与指定数值表达式对应的字符。
语法
str(数值表达式[,长度[,小数位数]])
返回值的类型
字符型
参数
数值表达式:要被转换为字符的数值表达式。
长度:转换后字符的长度。该长度等于小数点和小数点右边第个数字所占字符的数目总和。
如果指定长度大于所需长度,自动在前面加空格补齐。
如果指定长度小于所需长度,返回一串星(*)号,表示数值溢出。
如省略长度,则默认长度为10。
小数位数:指定返回字符串中的小数位数。
如指定位数小于实际位数,则返回值四舍五入。
如指定位数大于实际位数,则加0补齐。
如省略小数位数,默认为0。
在指定了小数位数的情况下,如指定长度(第二个参数)小于总长度,但大于整数长度,则返回对小数部分做了四舍五入的字符。
备注
返回后的值看起来还是数的形式,但它的数据类型已经变了,不再是一个数值,也就是不能再用来做加、减、乘、除的算术运算,但可以和字符进行加减。比如:
? '季度'+1
就会出错,因为一个字符是不能和一个数值相加的。写成如下形式就可以了:
? '季度'+str(1,1)
结果是:
季度1
注意,这里一定要指定长度,否则由于默认长度是10,就会出现如下结果:
季度         1
假如不知道数值有几位数怎么办呢?请参见ltrim()函数。
字符转换数值函数(val())
将数字组成的字符表达式转换成数字值。
语法
val(字符表达式)
返回值的类型
数值型
参数
字符表达式:要被转换为数值的字符表达式。该表达式由最多16位的数字组成,若超过16位,则对其圆整。
备注
val()函数从左到右返回字符表达式中的数字,直到遇到非数值型字符(忽略前面的空格)时为止。
若字符表达式的第一个字符不是数字,也不是正、负号,则返回0。
举例
a='123'
如果按下面的写法,就会出错,因为一个字符不能与一个数值相加:
? a+3
写成如下形式便可以了:
? val(a)+3
结果是126。
取系统日期函数(date())
返回由操作系统控制的当前系统日期。
语法
date()
返回值的类型
日期型
取年份函数(year())
从指定的日期表达式中返回年份。
语法
year(日期表达式)
返回值的类型
数值型
参数
日期表达式:指定的日期表达式,该函数即是返回其年份值。
举例
? year(date())
如果当前的系统日期是1999年2月5日,则显示的结果为1999。
取月份函数(month())
从指定的日期表达式中返回月份。
语法
month(日期表达式)
返回值的类型
数值型
参数
日期表达式:指定的日期表达式,该函数即是返回其月份值。
举例
? month(date())
如果当前的系统日期是1999年2月5日,则显示的结果为2。
取天日函数(day())
以数值型返回日期表达式是当月的第几天。
语法
day(日期表达式)
返回值的类型
数值型
参数
日期表达式:指定的日期表达式,该函数返回该日期是当月的第几天。
举例
? day(date())
如果当前的系统日期是1999年2月5日,则显示的结果为5。
取整函数(int())
返回数值表达式值的整数部分。
语法
int(数值表达式)
返回值的类型
数值型
参数
数值表达式:指定的数值表达式,该函数返回其整数部分。
举例
? int(123.47)
结果是123。
四舍五入函数(round())
对指定表达式进行四舍五入运算,并把结果返回。
语法
round(数值表达式小数位数)
返回值的类型
数值型
参数
数值表达式:指定的数值表达式,该函数返回其四舍五入后的值。
小数位数:保留的小数位数。
举例
? int(123.457,2)
结果是123.46。
删除标记函数(delete())
确定当前记录是否已做删除标记,若已做删除标记,返回真(.t.),否则返回假(.f.)。
语法
delete([表别名|工作区])
返回值的类型
逻辑型
参数
表别名|工作区:该参数指定所要确定的表的别名或所在工作区,即您可以在一个工作区去检测另一工作区的表的记录是否做了删除标记,而另一工作区的表可用其别名或工作区号来指定。该参数如省略,隐含为当前工作区,如有该参数的话,别名或工作区只选一个。
记录号函数(recno())
返回当前表或指定表中的当前记录号。
语法
recno([表别名|工作区])
返回值的类型
数值型
参数
表别名|工作区:该参数指定所要确定的表的别名或所在工作区,即您可以在一个工作区去得到另一工作区的表的当前记录号,而另一工作区的表可用其别名或工作区号来指定。该参数如省略,隐含为当前工作区,如有该参数的话,别名或工作区只选一个。
记录数函数(reccount())
返回当前表或指定表中的记录数目。
语法
reccount([表别名|工作区])
返回值的类型
数值型
参数
表别名|工作区:该参数指定所要确定的表的别名或所在工作区,即您可以在一个工作区去得到另一工作区的表的记录数,而另一工作区的表可用其别名或工作区号来指定。该参数如省略,隐含为当前工作区,如有该参数的话,别名或工作区只选一个。
 找到记录函数(found())
如果locate、continue、seek等查找记录的命令成功(即找到了记录),该函数返回“真”(.t.),否则返回“假”(.f.)。
语法
found([表别名|工作区])
返回值的类型
逻辑型
参数
表别名|工作区:该参数指定所要确定的表的别名或所在工作区,即您可以在一个工作区知道另一个工作区上次查找记录是否找到,而另一工作区的表可用其别名或工作区号来指定。该参数如省略,隐含为当前工作区,如有该参数的话,别名或工作区只选一个。
举例
locate for 姓名='庄稼'
?found()
如找到记录,显示结果为.t.,否则为.f.。
表结尾函数(eof())
确定记录指针是否在表的结尾处。
语法
eof([表别名|工作区])
返回值的类型
逻辑型
参数
表别名|工作区:该参数指定所要确定的表的别名或所在工作区,即您可以在一个工作区知道另一个工作区表的指针是否在尾部,而另一工作区的表可用其别名或工作区号来指定。该参数如省略,隐含为当前工作区,如有该参数的话,别名或工作区只选一个。
备注
这里所说的结尾处,并非是指最后一个记录,而是最后一个记录的后面,即没有记录。
举例
go bottom
skip
?eof()
显示结果为.t.。
如果没有skip,仅仅走到最后一个记录,返回的将是.f.,而必须再往下跳一下才会是“真”结尾。
此时如输入命令:display,则没有记录显示。这一点与bof()函数不同。
表开头函数(bof())
确定记录指针是否在表的开头。
语法
bof([表别名|工作区])
返回值的类型
逻辑型
参数
表别名|工作区:该参数指定所要确定的表的别名或所在工作区,即您可以在一个工作区知道另一个工作区表的指针是否在开头,而另一工作区的表可用其别名或工作区号来指定。该参数如省略,隐含为当前工作区,如有该参数的话,别名或工作区只选一个。
备注
这里所说的开头,并非是指第一个记录,而是第一个记录的前面。
举例
go top
skip -1
?bof()
显示结果为.t.。
如果没有skip -1,仅仅走到第一个记录,返回的将是.f.,而必须再往上跳一下才会是“真”开头。
但与eof()函数不同,此时如输入命令display,将显示第一条记录。
消除后续空格函数(trim())
将字符表达式的后续空格全部删除,并将结果返回。
语法
trim(字符表达式)
返回值的类型
字符型
参数
字符表达式:指定的字符表达式,该函数将删除其后续空格。
备注
trim()函数等同于rtrim()函数。
举例
如表的“姓名”字段长度为10,当前内容为“张三”,那么如下语句:
? 姓名+'先生:'
其显示结果是:
张三 先生:
而语句:
? trim(姓名)+'先生:'
显示结果是:
张三先生
消除前导空格函数(ltrim())
将字符表达式前面的空格全部删除,并将结果返回。
语法
trim(字符表达式)
返回值的类型
字符型
参数字符表达式:指定的字符表达式,该函数将删除其后续空格。
举例
在讲str()函数时我们举过一个例,即把一个字符与一个数值相加时,应采用如下形式:
? '季度'+str(1,1)
但是当我们没有办法确定这个数值的位数时,这个办法就行不通了,您不可能规定它的长度只能是1或2等等。那么怎么办呢,采用ltrim()函数可以解决了:
? '项目'+ltrim(str(sz))
这里假设事先已有一个数值存入了sz变量,但不知道这个数值是几,也就更不知道有几位数,那么在隐含状态下,str()函数将把sz转换为长度为10的字符,其前面可能有若干空格,通过ltrim()函数的处理,不论前面有多少空格,都可以得到如下结果(假设数值是15):
项目15
更多编程命令

BLANK 命令
如果发出该命令时不带任何参数,则清除当前记录中所有字段的数据。
语法
BLANK
  [FIELDS 字段名列表]
  [范围]
  [FOR 条件]
  [WHILE 条件]
  [NOOPTIMIZE]
 说明
如果使用了字段名列表,则清除指定字段中的数据,另外也可以在字段名前面加上别名,以清除其它工作区的表中数据,但要注意一点,如果当前工作区的记录指针指在了表的末尾,那么它也不能清除您所指定的其它工作区中的字段,即使其它工作区的记录指针没有指向末尾。
COPY FILE 命令
复制文件。
语法
COPY FILE 文件名1 TO 文件名2
说明
文件名中可以用“*”和“?”,如 *.prg。
要注意的一点是“文件名2”必须有,比如:
copy file *.prg to c:\temp\*.prg
不象 DOS 的 COPY 命令可以没有目的文件名,而且命令中的“FILE”也一定要有。
COPY STRUCTURE EXTENDED 命令
将当前工作区的表结构信息复制到另一表中。
语法
COPY STRUCTURE EXTENDED TO 表文件名 [FIELDS 字段名表]

参数说明
FileName :指定结构信息所存放的表文件名。

FIELDS 字段名表 :指定需要复制表结构中哪些字段的信息,如省略,则复制所有字段的信息。
说明
该命令将一个表的各字段信息复制到一个新表中,每个字段为一个记录,这是一个专门的表,其字段是固定,这些字段分别记录各字段的字段名、长度、数据类型等等,各字段的字段名及其意义如下:

字段名
数据类型
意义
FIELD_NAME
字符
字段名。
FIELD_TYPE
字符
数据类型。C=字符型,Y=货币型,N=数值型,F=浮点型,I=整数型,B=双精度型,D=日期型,T=日期时间型,L=逻辑型,M=备注型,G=通用型
FIELD_LEN
数值
字段长度。
FIELD_DEC
数值
小数位数。
FIELD_NULL
逻辑
字段是否可用 null 值。
FIELD_NOCP
逻辑
字段是否允许代码页翻译。
FIELD_DEFA
备注
字段的隐含值。
FIELD_RULE
备注
字段验证规则。
FIELD_ERR
备注
字段验证规则出错时的提示信息。
TABLE_RULE
备注
表验证规则。
TABLE_ERR
备注
表验证规则出错时的提示信息。
TABLE_NAME
字符
长表名。
INS_TRIG
备注
插入触发器代码。
UPD_TRIG
备注
更新触发器代码。
DEL_TRIG
备注
删除触发器代码。
TABLE_CMT
备注
表说明。

图1,即为用 COPY STRUCTURE EXTENDED 命令生成的 rsda 表的结构信息表,在这个表中就可以看到 rsda.dbf 表的各字段情况。
那么用这个命令产生的结构信息表有什么用呢?这可以让我们在编程时改变一个表的结构,因为信息结构表也是个表,我们可以象操作一般表一样去修改它的内容,当然修改时要遵从表结构的有关规定,比如字段名不能超过10个字符,字段类型必须是上表中的字符之一,字段名不能重名等等。
当然我们修改了这个表后不会对原表马上产生作用,因为它们已经是分别独立的两个表,在物理上没有任何联系,但我们可以首先使用以下命令:
CREATE 表文件名 FROM 结构信息表文件名
根据结构信息表创建一个新表,再将原表的数据加到这个新表中,删除原表,将新表改为原表名,这样就修改了表的结构。比如,我们要将 rsda 表部门字段的长度改为20,程序如下:

select rsda  &&选择 rsda 表。
copy structure to rsdajg extended  &&将 rsda 表的结构信息复制到 rsdajg 表。
use rsdajg  &&打开 rsdajg 表,同时关闭 rsda 表。
replace field_len with 20 for trim(field_name)= ='部门'  &&将部门字段的字段长度修改为20。
*实际上上一句的意思是将 rsdajg 表的 field_name 为“部门”的记录的 field_len 字段值改为20。
use  &&关闭 rsdajg 表,要从这个表创建新表,必须将其关闭。
create rsda2 from rsdajg  &&从 rsdajg 中提取信息创建 rsda2 表。
append form rsda  &&从 rsda 表中获取数据。
erase rsda.dbf  &&删除 rsda.dbf 表。
rename rsda2.dbf to rsda.dbf  &&将 rsda2.dbf 改名为 rsda.dbf。

 FLUSH 命令
将表和索引中的数据存入磁盘。
语法
FLUSH
说明
当我们修改一个表时,修改完后,我们可能并不马上将表关闭,那么这时所做的修改可能只是在内存中,而没有真正存到盘,如果这时死机或停电,那么......,所以我们可以在程序适当的地方用此命令确保数据存盘。
比如,我们可以在菜单中加个“保存”命令,该命令就调用 FLUSH 语句,供操作者在适当的时候调用此命令来保存数据。
还可以用一个表单,在其中放一个计时器,在计时器的 timer   事件中用此命令,这样就做成一个具有自动保存数据的功能,甚至可以在菜单中加一个设置计时器时间的命令,以供操作者设置自动保存数据的间隔时间。
FOR...ENDFOR 命令
按指定的次数循环执行一组命令。
语法
FOR 变量=初始值 TO 结束值 STEP 步长值
  命令组
  [EXIT]
  [LOOP]
ENDFOR | NEXT
参数描述
变量:指定一个变量作为计数器,该变量可以不预先存在,FOR 命令会自动创建。
初始值 TO 结束值:即计数器的初始值和结束值,也就是指定循环的次数。
STEP 步长值:设定计数器每次增加或减少的量,如果省略此子句,则每次增加1,比如 for jsq=1 to 10,那么将会循环10次,如果是 for jsq=1 to 10 step 2,那么循环将会是5次,因为每循环1次计数器增加2,从1到10只需增加5次就行了。
可能有人会问了,既然循环5次,那为什么不 for jsq=1 to 5 呢?这往往是为了在某些情况下使程序编起来方便和易于理解,比如要对数据表中的记录进行某项操作,要求每隔一条记录做一次,起始和结束的记录是根据具体情况变化的,也就是初始值和结束值都是变量,那么我们就可以使用步长子句,并将步长设为2,这样就不用具体去计算到底需要多少次循环了,如果再加上步长也是变量,即有时隔一条记录,有时会隔多条记录,则步长子句就更必要了。

注意
步长的变化是指在不同次的完整循环中步长会变,一般在一次完整的循环中不要更改初始值、结束值和步长。

另外步长可以是负的,这又有什么必要?当我们需要根据一个数值不断减小来进行某项操作时就需要了,比如我们需要对记录进行某项操作,这个操作要求先处理第10条记录,再第9条(因为在处理第9条时需要根据第10条的情况),依次到第1条,那么我们就可以:

for jsq=10 tp 1 step -1
* 操作命令组
  go jsq  &&设上面的操作移动了记录指针,此时已不指在第10条记录上,则不能用 skip -1 将指针移到第9条
endfor

很明显,虽然可以用其它方法,但这样编出的程序最简洁,一目了然。
EXIT 和 LOOP:与 DO WHILE 中的意义一样。
说明
计数器只有在大于结束值时才结束循环,即当计数器等于结束值时仍要循环,也就是说:
for jsq=1 to 10
是循环10次,而不是9次,也就是当 jsq 的值为10时,还要循环一次,当它为11时,则退出循环,执行 endfor 之后的程序。

注意
DO WHILE...ENDDO 与 FOR...ENDFOR 都是循环,它们有什么区别呢?在需要使用计数器的情况下,由于 FOR...ENDFOR 不需要专门的计数语句,所以程序执行的速度快,因此能用 FOR...ENDFOR 时尽量用。

ON ERROR 命令
设置一命令,当系统错误发生时,该命令执行。
这也就是程序员们常说的错误捕获陷井。所谓错误捕获陷井的意思就是在系统中启动一个监控程序,一旦错误发生,这个监控程序就将错误捕获,并不让错误显示出来,可保证程序的继续运行,然后用一个命令去对出现的错误进行处理,比如显示一个错误提示等。如果错误不发生,这个命令则始终不执行。
语法
ON ERROR [命令]
参数说明
命令:此即为指定的在捕获错误后所要执行的程序。一旦程序执行到某个语句发生错误,该命令即被执行,执行完后,接着执行发生错误的下一条语句。

提示
有时为了对错误进行处理,一条命令可能是不够的,则可用该命令调用一个子程序,那么需要在子程序执行完后才接着执行发生错误的下一条语句。
如果子程序中有 RETRY 命令,该命令将使子程序返回,并重新执行发生错误的语句,这个命令一般用在这种情况下,当子程序对错误进行了处理,使得再执行该命令时不会发生错误了,这样就可以使程序按照正常情况运行下去。
 多学一招
当处理错误子程序执行时,您还可以用 ERROR(), MESSAGE(), LINE(), PROGRAM() 等函数返回出错的编号、信息、出错的语句所在行号以及出错的程序等,这些可能对处理错误有帮助。

如省略命令参数,则取消错误捕获陷井。
说明
ON ERROR 命令不能嵌套,也就是说在 ON ERROR 所执行的子程序中不能再有 ON ERROR 命令,否则等于取消错误捕获。
示例
当 DBF 文件的结构化复合索引损坏时,如果您打开 DBF 文件就会出错,下面的例子就捕获错误并自动修复索引:

*主程序
on error do xfsy  &&设置错误陷井
use rsda  &&打开表,如果索引损坏,则会产生错误,xfsy 程序将会执行
on error  &&取消错误陷井
...
cancel
*修复子程序
procedure xfsy  &&修复子程序的过程名
erase rsda.cdx  &&删除索引文件
use rsda  &&打开表
index on 编号 tag of rsda.cdx  &&重建索引“编号”
index on 姓名 tag of rsda.cdx  &&重建索引“姓名”

 ON ESCAPE 命令
设置一命令,当按了ESCAPE时,该命令执行。
语法
ON ESCAPE
    [命令]
参数说明
命令:该命令即为指定的按下 ESC 键后所要执行的命令。
假设当程序执行到第10条语句时您按下了 ESC,那么在命令执行完后,将接着执行第11条语句,如果命令是调用一个子程序,而子程序中有 RETRY 语句,则返回重新执行第10句。
说明
假如 ON KEY LABEL 命令也同时指定了 ESC 键,ON ESCAPE 所指定的命令优先执行。
如 SET ESCAPE OFF 则该命令也不起用。

注意
这个命令常常用来在一个循环中退出循环,比如:
on escape exit
do while .t.
  *循环体中的命令组
enddo
on escape
这样在循环中执行命令时,只要您随时动用您的玉指按下 ESC 键,即可令程序退出循环。
但别高兴得太早,上面这个程序在执行时常常出错,但也不是总出错,为什么呢?因为 exit 命令必须在循环体中,假如程序正执行到 do while 或 enddo,这时您动了玉指,程序就会出错,怎么解决这个问题呢?这样:
on escape tc=.t.
tc=.f.
do while .t.
  *循环体中的命令组
  if tc
    exit
  endif
enddo
on escape
思考题:上面这个程序为什么不会出错?

 ON KEY LABEL 命令
指定一个命令,当键盘上某个键(也可以是组合键或鼠标)被按下后,该命令执行。
语法
ON KEY LABEL 键名 [命令]
参数描述
各键的键名如下:

键名
LEFTARROW
RIGHTARROW
UPARROW
DNARROW
HOME
HOME
END
END
PAGE UP
PGUP
PAGE DOWN
PGDN
DEL
DEL
BACKSPACE
BACKSPACE
SPACEBAR
SPACEBAR
INS
INS
TAB
TAB
SHIFT+TAB
BACKTAB
ENTER
ENTER
F1 to F12
F1, F2, F3 ...
CTRL+F1 to CTRL+F12
CTRL+F1, CTRL+F2 ...
SHIFT+F1 to SHIFT+F12
SHIFT+F1, SHIFT+F2 ...
ALT+F1 to ALT+F12
ALT+F1, ALT+F2, ALT+F3 ...
ALT+0 to ALT+9
ALT+0, ALT+1, ALT+2 ...
ALT+A to ALT+Z
ALT+A, ALT+B, ALT+C ...
CTRL+LEFT ARROW
CTRL+LEFTARROW
CTRL+RIGHT ARROW
CTRL+RIGHTARROW
CTRL+HOME
CTRL+HOME
CTRL+END
CTRL+END
CTRL+PAGE UP
CTRL+PGUP
CTRL+PAGE DOWN
CTRL+PGDN
CTRL+A TO CTRL+Z
CTRL+A, CTRL+B, CTRL+C ...
CTRL+0
CTRL+0
RIGHT MOUSE BUTTON
RIGHTMOUSE
LEFT MOUSE BUTTON
LEFTMOUSE
MOUSE BUTTON
MOUSE
ESC
ESC

ON KEY LABEL 键名 后面不跟命令则解除该键所要执行的命令(即解除所设的程序陷井)。
说明
该命令的用法与 ON ESCAPE 的用法基本上是一样的。

注意
该命令对于在系统菜单及系统对话框上的按键不起作用。
对于 on mouse 等命令,在鼠标点击件时不起作用。

示例
on key label ctrl+e tc=.t.
tc=.f.
do while .t.
  *循环程序组
  if tc
    exit
  endif
enddo
on key label ctrl+e
PACK DATABASE 命令
将数据库所有表中作了删除标记的记录物理删除,即真正从磁盘上删除。
语法
PACK DATABASE
说明
执行该命令时,数据库必须以独占方式打开。
该命令可用于在程序中定期清理数据库各表中的删除记录,因为软件可能经常要作删除,对大的表会影响运行速度,所以可以在软件中设置 set delete on,即隐去作了删除标记的记录,但这些记录并没有真正删除,但这些垃圾记录太多了也对系统不利,故可设一功能由操作人员在需要时运行此命令来清理整个数据库。
 SCAN...ENDSCAN 命令
将记录指针由头到尾扫描一遍,每移动一次记录指针可执行一组命令。
类似于循环 DO...ENDDO 或 FOR...ENDFOR,只是这是专门针对表的循环。
语法
SCAN [范围] [FOR 条件1] [WHILE 条件2]
    [命令组]
    [LOOP]
    [EXIT]
ENDSCAN
参数描述
范围:在指定范围内扫描,即不从开头扫描到最后。
FOR 条件1 及 WHILE 条件2:只扫描符合条件的记录。
如果不带范围和条件子句,则隐含扫描表中的全部记录。
示例
库存日记帐表(字段有日期、入库数、出库数,库存数),每记录一笔出入库,则计算相应的库存数并放入表中,有时意外情况可能导致库存数出错,因此需要有一功能将库存数重新计算一遍,下面这个程序就是完成这个功能:

go top
dqkc=库存数  &&记下第一条记录的库存数
skip  &&跳到第2条记录
scan  rest &&只需扫描从第2条记录起的所有记录
  replace 库存数 with dqkc+入库数-出库数
  dqkc=库存数  &&记下当前记录的库存数
endscan

上面这段程序与下面这段程序是等价的:

go top
dqkc=库存数  &&记下第一条记录的库存数
skip  &&跳到第2条记录
do while .not. eof()
  replace 库存数 with dqkc+入库数-出库数
  dqkc=库存数  &&记下当前记录的库存数
  skip
endscan

所不同的就是不用每次都 skip,系统自动移动记录指针,这样程序运行的速度快,尤其对于按条件扫描的情况就更快了,因为用 do 的话每次都要用 locate 或 continue 进行查询,速度很受影响。
与这段程序也是等价的:

go top
dqkc=库存数  &&记下第一条记录的库存数
skip  &&跳到第2条记录
for jsq=2 to reccount()
  replace 库存数 with dqkc+入库数-出库数
  dqkc=库存数  &&记下当前记录的库存数
  skip
endfor

您喜欢用哪个呢?

注意
上面第三段程序中有个小问题,在 for 语句中用了 reccount() 函数,而函数都要经过运算才能得到结果,比起直接从一个变量中获取值要慢,假如只执行1次,两者相差无几,但上面是用在循环中,for 这一句可能会执行几十万次,那么就会使速度降低很多,为此可改成下面这样:
...
jls=reccount()
for jsq=2 to jls
...

SET CONFIRM 命令
当输入的内容填满输入区域时是否需要按回车键跳出输入区域。
语法
SET CONFIRM ON | OFF
参数描述
ON 需要按回车键。也可以按 TAB 或其它箭头键等。

提示
对表单上的文本框也起作用。当对于需要慎重输入的地方,可将其设置为 ON,以便让操作者在离开输入之前可再看一下所输入的内容有没有问题,如果不设置为 ON,当输入内容输入满时,光标会自动离开文本框,不利于发现错误。

OFF 不需要按回车键(隐含)。
SET DEFAULT 命令
设置隐含的驱动器及目录。
设置了隐含的路径,我们可以在程序运行打开每个隐含路径下的文件时不用再指定路径了。
语法
SET DEFAULT TO [路径]
参数说明
路径:可以是符合操作系统要求的任何路径,比如:
set default to d:\soft\rsgl
set default to \
set default to ..

注意
如果路径中带有空格,必须用引号将路径括起来,否则会出错。比如:
set default to "d:\soft\rsgl  佳帆"

示例
当我们的程序在运行时,一般我们都需要知道该程序运行所在的目录,因为往往许多数据也是放在这个目录下,我们不能指定一个绝对的路径,因为程序编好后可能拿到另一台电脑上用,其路径可能发生变化,一旦变化便找不到所需要的数据文件了,因此我们必须有一种方法获取其当前所在的路径,有一种办法是这样:
cxlj=sys(5)+sys(2003)+'\'
这在程序编译成独立 EXE 文件时运行是对的,但在 VFP 系统中运行 PRG 程序却不对,它返回的是 VFP 所在的目录,为了解决这个问题,可采用如下程序:

CXLJ=SYS(16)  &&获取当前运行的程序名及其所在路径
FOR JSQ=1 TO LEN(CXLJ)  &&用一个循环找出最右边一个反斜线,将其后的程序名去掉,只剩路径
  CXZF=LEFT(RIGHT(CXLJ,JSQ),1)  &&从 CXLJ 的右边依次取出每个字符
  IF CXZF='\'  &&查看这个字符是不是反斜线
    CXLJ=STUFF(CXLJ,LEN(CXLJ)-JSQ+1,JSQ,'')  &&如果是,就将反斜线之后的字符全删掉并退出循环
    EXIT
  ENDIF
ENDFOR
SET DEFAULT TO "&CXLJ"  &&用宏替换设置隐含路径,用引号是为了带空格的路径也能正确设置

 SET SKIP TO 命令
在表之间建立一对多关系。
语法
SET SKIP TO [表1 [, 表2] ...]
参数描述
表1 [, 表2] ... 为一对多表中的子表的别名。可以有多个子表,它们之间用逗号隔开。
如省略此参数,则取消一对多关系,但一对一关系仍存在。
说明
我们用 SET RELATION 命令可创建表之间的一对一关系,也就是当我们将父表的记录指针移到某一个记录上时,子表的记录指针也会移到相应的关联记录上,比如我们有一个人事档案(rsda,有字段编号、姓名等),另有一个参加项目表(cjxm,有字段编号和项目,项目中记录该人员所参加过的项目,编号建立了索引),然后用如下程序:

close all
use rsda
select 0
use cjxm
set order to 编号
select rsda
set relation to 姓名 into cjxm

那么当我们将 rsda 的记录指针移到一个记录上时,cjxm 也会移到相同编号的记录上,但由于是一对一关系,只有 cjxm 中的相同编号的第1个记录会与 rsda 中的记录对应,其它的记录不会与之对应。
但我们知道一个人可能参加了多个项目,由于一对一的关系,我们没法从父表中知道子表中有多少个记录与之对应,比如这时您在父表中键入如下命令:
display 编号,姓名,cjxm,项目 for 编号='10'
那么只会有一条记录出现,因为 rsda 中只有一个编号为 10 的记录,虽然可能他参加了多个项目,但这时不会显示出来,要想显示出来就必须建立一对多的关系,比如我们在 rsda 所在工作区键入如下命令:
set skip to cjxm
然后再运行上面的 display 命令,子表中所有对应的记录都会显示出来,即可看到 10 号人员所参加的所有项目,而编号和姓名都是一样的。
怎么样,很不错吧!
这时如果您用 browse 看 rsda 表,假如 cjxm 中有多条编号为 10 的记录,您会发现 10 号记录下面会多出几条记录来,它们的内容全部是“*”,且不可修改,这就是系统根据一对多关系增加的假记录,依靠这些记录,上面的 display 命令才能带来我们想要的结果。

注意
如果这时删除子表中的记录,且做了 pack,那么关联将被解除,包括一对多和一对一,如仍需要一对多关系,必须重新用 set relation 和 set skip to 建立一对多关系。

思考题
大家思考下用 set skip to 是否能很好地解决问题。

在中文版VFP6.0中,有一些数据分布在三个数据库中,如A库中有各个企业的基本情况,在B库中有各个企业的董事会组成成员情况,在C库中有各个企业的分支机构情况,三者可以通过企业编号进行索引,其相互关系是:A库中一个记录即一个企业,对应于B库中的至少三个记录(即不同的组成成员),再对应于C库中的至少三个记录(即不同的分支机构),现在的问题是,我如何能在一张页面上,同时打印A库中的一条记录,B库和C库中的三条以上的记录。

 SET SKIP OF 命令
设置菜单或菜单项是否跳过,即是否可用。
语法
SET SKIP OF MENU 主菜单名 逻辑表达式
SET SKIP OF PAD 主菜单项名 OF 主菜单名 逻辑表达式
SET SKIP OF POPUP 子菜单名 逻辑表达式
SET SKIP OF BAR 子菜单项 OF 子菜单名 逻辑表达式
参数描述
MENU 主菜单名 逻辑表达式
设置主菜单是否跳过,逻辑表达式为“真”时跳过,即整个菜单不可用,反之则可用。
例如,可以使用以下命令禁止 VFP 的系统菜单 _MSYSMENU:
set skip of menu _msysmenu .t.
使用以下命令则启用它:
set skip of menu _msysmenu .t.
 PAD 主菜单项名 OF 主菜单名 逻辑表达式
设置主菜单中某个菜单项是否跳过,如果设置为跳过,假如该项下有子菜单,则子菜单不能拉下来。
可用如下命令禁止 VFP 主菜单上的“编辑”菜单:
set skip of pad _msm_edit of _msysmenu .t.
 POPUP 子菜单名 逻辑表达式
设置某个子菜单是否跳过,如设置为跳过,该子菜单可以拉下,但其中的菜单项都不可用。
可用如下命令禁止 VFP 的“编辑”子菜单:
set skip of popup _medit .t.
 BAR 子菜单项 OF 子菜单名 逻辑表达式
设置某个子菜单中的某个菜单项是否跳过。
可以用下面的命令设置 VFP “文件”子菜单中的“新建”不可用:
set skip of bar _mfi_new of _mfile .t.
说明
那么对于菜单设计器设计的我们自己的菜单怎样进行设置呢?其实当我们用菜单设计器设计好一个菜单并完成生成后,就会生成一个扩展名为 mpr 的菜单程序,它与 prg 程序是一样的,我们可以用如下命令将其打开来看:
modify command xxx.mpr
打开后我们就可以看到其中的主菜单名及其相应的主菜单项、子菜单、子菜单项等。下面便是一个 mpr 文件:

*菜单的说明信息略
SET SYSMENU TO
SET SYSMENU AUTOMATIC
DEFINE PAD _s950ndhs4 OF _MSYSMENU PROMPT "系统录入及查询" COLOR SCHEME 3
DEFINE PAD _s950ndhs6 OF _MSYSMENU PROMPT "系统维护" COLOR SCHEME 3
DEFINE PAD _s950ndhs7 OF _MSYSMENU PROMPT "统计" COLOR SCHEME 3
DEFINE PAD _s950ndhs8 OF _MSYSMENU PROMPT "备份数据" COLOR SCHEME 3
ON PAD _s950ndhs4 OF _MSYSMENU ACTIVATE POPUP 系统录入及
ON PAD _s950ndhs6 OF _MSYSMENU ACTIVATE POPUP 系统维护
ON PAD _s950ndhs7 OF _MSYSMENU ACTIVATE POPUP 统计
ON PAD _s950ndhs8 OF _MSYSMENU ACTIVATE POPUP 备份数据
*主菜单即为 _MSYSMENU
*主菜单中的菜单项分别为 _s950ndhs4, _s950ndhs6 等
*_s950ndhs4 下的子菜单是“系统录入及”
*_s950ndhs6 下的子菜单是“系统维护”
DEFINE POPUP 系统录入及 MARGIN RELATIVE SHADOW COLOR SCHEME 4
DEFINE BAR 1 OF 系统录入及 PROMPT "数据录入" ;
KEY ALT+L, "ALT+L" ;
MESSAGE '录入房地产广告信息'
DEFINE BAR 2 OF 系统录入及 PROMPT "数据查询" ;
KEY ALT+C, "ALT+C" ;
MESSAGE '查询和修改录入的房地产广告信息'
DEFINE BAR 3 OF 系统录入及 PROMPT "数据整理" ;
KEY ALT+Z, "ALT+Z" ;
MESSAGE '如发现数据有问题,可调用此命令进行整理,如整理后仍有问题,请与软件供应商联系'
DEFINE BAR 4 OF 系统录入及 PROMPT "\-"
DEFINE BAR 5 OF 系统录入及 PROMPT "退出系统" ;
KEY ALT+E, "ALT+E"
ON SELECTION BAR 1 OF 系统录入及 do sjlr
ON SELECTION BAR 2 OF 系统录入及 do lrsjcx
ON SELECTION BAR 3 OF 系统录入及 do sjzl
ON SELECTION BAR 5 OF 系统录入及 clear event
*“系统录入及”子菜单中的各菜单项为 1、2、3 等
*以下略

比如可以在程序中使“系统录入及查询”下的“数据整理”不可用,即“系统录入及”子菜单中的第3个菜单项,命令为:
set skip of bar 3 of 系统录入及 .t.
我们知道在菜单设计器可为每个菜单项设置跳过表达式,那么这个命令还有什么用吗?有时用它还是比较方便,比如我们要让一个子菜单中的所有菜单项不可用,我们可以设置该子菜单不可用,如用跳过要一个个菜单项去设,或者每个菜单项都设一个比较复杂的表达式,再要不就设调这个子菜单的主菜单项不可用,那样又看不到子菜单中有什么内容了(有时虽然不可用但也想看看),总之是比较麻烦,那么用这个命令就好一些。
不好的地方是要把 mpr 程序打开来记住那些菜单名、菜单项,你们也看到上面了,不是那么好记的。反正这个世界没有十全十美的东西,我们只好将就点了。
VALIDATE DATABASE 命令
保证当前库中表和索引位置的正确性。
语法
VALIDATE DATABASE
  [RECOVER]
  [NOCONSOLE]
  [TO PRINTER [PROMPT] | TO FILE 文件名]
参数描述
RECOVER:显示一个对话框,该对话框允许您定位表和索引,这些表和索引不在被检查的数据库, 中。必须在命令窗口中发出 VALIDATE DATABASE RECOVER 命令,在程序中发布该命令会产生错误信息。
NOCONSOLE:如发现数据库中有错误,不向屏幕输出错误信息。
TO PRINTER [PROMPT]:打印错误信息。
TO 文件名:将错误信息输出到一个文件。
说明
该命令确保数据库包含的表和索引处于正确位置,确保数据库中的表包含正确的字段,以及确定数据库中索引标识是否存在。
该命令对当前数据起作用,而且要求数据库必须是以独占方式打开。
虽然该命令一般不用在程序中,但由于一些初学者是第一次接触数据库(不是表),故这个命令还是有必要讲一下。当我们发现数据库有不正常的情况时,就可用这个命令检测和恢复数据库。
示例
下面的示例打开 testdata 数据库,并使用 VALIDATE DATABASE 命令,以确保表和索引的位置在数据库中是正确的。

close databases
set path to (home(2)+'data\')  &&设置数据库路径
open database testdata exclusive  &&以独占方式打开数据库

validate database

 CALCULATE 命令
对字段或带有字段的表达式进行统计计算。
语法
CALCULATE 表达式列表
 
[范围][FOR 条件][WHILE 条件]
  [TO 变量列表| TO ARRAY 数组名]
  [NOOPTIMIZE]
 参数描述
表达式列表
这里所用到的表达式与其它命令中所用到的表达式不一样,这里必须是规定的几个函数表达式,如下:

函数
含义
AVG(表达式)
计算表达式的平均值。
CNT()
返回表中记录的个数。
MAX(表达式)
返回表达式中的最大值。
MIN(表达式)
返回表达式中的最小值。
NPV(表达式1,表达式2,[表达式3])
计算一个固定周期利率下,一系列现金流的净现值。表达式1为十进制表示利率;表达式2代表一系列现金流的字段、字段表达式或数值表达式,每个现金流可正可负,当它是字段时,每个记录的字段值都认为是一个现金流;表达式3指定可选的初始投资,如果不包括初始投资,则假定初始投资发生在第一阶段末,这个初始投资就是第一个记录,而且是负的,代表现金流出。
STD(表达式)
计算表达式的标准偏差。
SUM(表达式)
对表达式的值求和。
VAR(表达式)
计算表达式的均方差。

注意:这里的函数是不能单独使用的,它们与单独同名的函数也是不一样的,比如,CALCULATE MIN() 与 MIN() 是不同的,前者计算某一字段中各记录的最小值,函数只需写一个字段名,后者则是计算一系列表达式中的最小值,所有的表达式都必须写入函数中。
 TO 变量列表
将计算结果存入指定的变量。注意变量列表中的变量数必须与表达式列表中的表达式个数相同。
 TO ARRAY 数组名
将计算结果存入指定的数组。数组可存在,系统会自动创建,数组中的元素也不一定与表达式列表中的个数一样,如果不够,系统会自动增加,如果多了,则多出的元素保持不变。
 示例
CALCULATE SUM(工资),AVG(工资),MIN(工资),MAX(工资) TO HJ,PJ,ZX,ZD
计算出工资合计、平均工资、最少工资、最多工资,并分别存入各变量。
从这个例子可以看出,用这个命令比分别用 SUM、AVERAGE 等命令方便,且执行的速度快,因为它只需要将表扫描一次就能将各值计算出来了,而分别用命令就要扫描几次,对于一个很大表这可不能等闲视之。