FORCAL32W.DLL V9.0 用户指南
1 简单的例子 2 标识符 3 表达式和数据 4 常量和变量 5 赋值语句 6 算术运算符 7 关系运算符和逻辑运算符 8 逗号(冒号)运算符和括号运算符 9 运算符及优先级 10 函数概述 11 函数的传值调用和传址调用 12 用表达式作为函数的参数 13 表达式的初始化及销毁 14 表达式的完整定义及表达式句柄 15 流程控制 16 字符串 17 静态数组 | 18 递归调用 19 模块化编译 20 模块命名空间 21 二级函数命名空间 22 常量命名空间 23 访问命名空间 24 类成员运算符(函数参数运算符)及对象赋值运算符 25 标识符解释规则 26 二级函数 27 关键字 28 指针 29 运算符及函数重载 30 动态内存管理 31 错误处理 32 效率 33 主要技术指标 |
正文:
F(x,y)=x+y; //函数定义;
2+F[2,3]+5; //简单的计算式;
在这个例子中,分号表示一个表达式的结束,两个反斜杠//后的内容表示注释,本文中将一直采用这种表示和注释方法。
在第一个表达式中定义了一个函数 F ,小括号( )内的x,y为函数的自变量,等号后为函数体,即可执行的部分,该函数计算并返回两个参数的和。在第二个表达式中调用了上面定义的函数 F 。
需要注意,在一般的Forcal程序中,只计算无自变量的表达式。因此,对第一个表达式只编译,不计算。
在FORCAL中,一个标识符可以用来表示一个表达式的名字,或者是一个函数的名字,或者是一个变量,或者是一个符号常量。标识符可由一个或多个字符组成,可以任意一个英文字母、中文字符或者下划线开头,后面可以接英文字母、中文字符、数字或者下划线(下划线的使用可以增加标识符的可读性,如first_name)。注意:英文字母的大写和小写是不同的,例如:count和COUNT是两个不同的名字。下面给出一些合法的标识符:
first last Addr1 top _of_file name23 _temp a23e3 MyVar 您好 数1 My数
表达式是FORCAL的编译单元,如果没有语法错误,编译后将生成可执行的代码。FORCAL表达式可以很简单,例如只有一个数字;FORCAL表达式也可以很复杂,例如是一段复杂的程序。
以下是一些简单的表达式的例子:
2;
2+3;
2+sin(3)-ln[6];
可以给表达式起一个名字,这样做的好处是:以后可以用函数调用的方式通过该名字调用该表达式。表达式的名字是一个标识符,必须位于表达式的开头。例如,上面三个表达式可按如下方式定义:
A()=2;
B()=2+3;
C()=2+sin(3)-ln[6];
表达式定义的一般形式是:
Name(a,b)={a=a+2,b=10:a+b}
其中 Name 为表达式的名字,不可与其它已经定义的表达式名字相同,但可以缺省;如果定义了表达式名字,表达式名字后必须跟一对小括号(只能用小括号),用来定义自变量,自变量个数可以为零,也可以有任意多个,有多个自变量时,自变量间以逗号分隔;如果用小括号定义了自变量,小括号后必须跟一个等号,该等号标志表达式可执行部分的开始,等号及等号后的可执行部分均不可缺省。表达式的可执行部分由多个语句组成,多个语句之间用逗号或冒号分隔,表达式总是返回最后一个语句的值;另外花括号不是必须的,但有了花括号后,表达式更易读。
用这种方式定义表达式时,实际上就是自定义了一个函数,可以在任意的表达式中使用该函数(只要给该函数起了名字),由于这个原因,我们常常将表达式说成是函数,或者说表达式就是一个函数,但反过来并不成立。
下面是一个函数定义及使用的例子。
相加(加数1,加数2)=加数1+加数2; //函数定义;
2+相加[2,3]+5; //函数调用;
以下是一些合法的表达式例子:
2; //常量(无参)表达式(函数);
()=2; //常量(无参)表达式(函数);
A()=2; //常量(无参)表达式(函数),但定义了函数名字,在其它表达式中可以调用该函数;
B(x)=2; //有一个自变量的表达式(函数);
C(x,y)=x+y; //有二个自变量的表达式(函数);
下面说一下表达式的类型和数据的类型,FORCAL表达式有三种类型:整数表达式、实数表达式和复数表达式;相应地,FORCAL中有三种基本的数据类型:整数、实数和复数。例如:
i:2+3; //整数表达式,在一般的Forcal程序中,整数表达式以i:开头;
r:2.2+3; //实数表达式,在一般的Forcal程序中,实数表达式以r:开头;
c:2+3i; //复数表达式,在一般的Forcal程序中,复数表达式以c:开头;
3.2+3; //实数表达式,在一般的Forcal程序中,缺省是实数表达式;
FORCAL编译器在编译表达式时,将整数表达式中的数据都转换成整数(64位有符号整数,范围从-9223372036854775808~9223372036854775807);将实数表达式中的数据都转换成实数(64位双精度实数,范围大致从±1.7E-308~±1.7E+308);将复数表达式中的数据都转换成复数(128位双精度复数,复数的实部和虚部都是64位双精度实数,范围大致从±1.7E-308~±1.7E+308),如果数字后有i,表示一个虚数。
在FORCAL实数或复数表达式中,数字可以带小数点,也可以不带小数点,还可以用科学记数法表示数字。小数的表示非常灵活,小数点前面的零或后面的零可省,例如:5.6、5.、.6都是合法的数据表示。用科学记数法表示的数字,例如:10.3E8、2E-10等,其中用E表示以10为底的指数部分,但指数不能为小数,例如:3E5.6、8.6E-6.7不合法。
在FORCAL整数表达式中,数字既可以是10进制数,也可以是16进制数,但数字中不能包含小数点,也不能用科学记数法表示数字。16进制整数以0x开头,并用A~F表示10~16,例如:0x1A、0xB、0x123D等。
常量是指那些不需要程序计算的固定值。有数值常量和符号常量两种。如100就是一个数值常量。在FORCAL中,也可以用标识符表示一个符号常量。用函数const可以定义一个永久性符号常量或暂时性符号常量,但只有const函数被执行后,定义的常量才会起作用。如下例:
const("aa",111); //定义永久性常量,不可删除
const("bb",222,1); //定义暂时性常量,可以删除
编译运行以上表达式,然后输入下例代码并执行:
aa; //aa=111
bb; //bb=222
const("aa",0,0); //试图删除永久性常量,但失败了
const("bb",0,0); //删除暂时性常量
有些软件会利用Forcal的输出函数定义一些符号常量,但在这里无法给出具体的例子。
变量是指在程序运行时,其值可以改变的量。FORCAL有五种变量,即:自变量、动态变量、静态变量、模块变量和全局变量。自变量、动态变量和静态变量只能被定义该变量的表达式所访问;模块变量可被同一模块的所有表达式所访问,其他模块的表达式无法访问;全局变量可被所有的表达式所访问。自变量用于向表达式传递参数,因此自变量也称为形式参数。动态变量只在表达式执行时起作用,一旦表达式执行完毕,动态变量也随之失效。静态变量存在于表达式的整个生命周期,每次表达式执行完毕,静态变量的值被保留。FORCAL在编译表达式时,将所有静态变量初始化为0,其余的变量均不进行初始化。
如果表达式中的变量名与一个常量名相同,则常量名被忽略。
在FORCAL表达式中,通常情况下,变量要先定义后使用,变量在表达式的开头进行定义,格式如下:
F(a,b:x,y,static,u:s,t,common,v)=
{x=1,y=2,
a+b+x+y+static+u+s+t+common+v
}
F是表达式的名字,a和b是自变量,x和y是动态变量,static和u是静态变量,s和t是模块变量,common和v是全局变量。自变量、动态变量和静态变量以及模块变量和全局变量之间用冒号分隔,即第一个冒号前为自变量,两个冒号之间为动态变量和静态变量,第二个冒号后为模块变量和全局变量。两个冒号之间用关键字static分隔动态变量和静态变量,static之前为动态变量,static及以后变量均为静态变量,关键字static只能用在两个冒号之间。第二个冒号后用关键字common分隔模块变量和全局变量,common之前为模块变量,common及以后变量均为全局变量,关键字common只能用在第二个冒号后。FORCAL中的所有变量均可缺省。以下都是合法的变量定义的例子:
F()= ... ... //没有使用任何变量,称无参表达式;
F(::)= ... ... //没有使用任何变量,称无参表达式;
F(a,b)= ... ... //定义了两个自变量a和b;
F(:x,y)= ... ... //定义了两个动态变量x和y;
F(:static,u)= ... ...//定义了两个静态变量static和u;
F(::s,t)= ... ... //定义了两个模块变量s和t;
F(::common,v)= ... ...//定义了两个全局变量common和v;
F(a,b:x,y)= ... ... //定义了两个自变量a和b,两个动态变量x和y;
F(a,b::s,t)= ... ... //定义了两个自变量a和b,两个模块变量s和t;
F(:x,y:s,t)= ... ... //定义了两个动态变量x和y,两个模块变量s和t;
变量的使用见下面的例子:
F(a,b:x,y,static,u:s,t,common,v)={a=1,b=2,x=3,y=4,static=5,u=6,s=7,t=8,common=9,v=10};//函数定义及变量赋值;
A(a,b:x,y,static,u:s,t,common,v)=a;//函数定义;
B(a,b:x,y,static,u:s,t,common,v)=b;//函数定义;
X(a,b:x,y,static,u:s,t,common,v)=x;//函数定义;
Y(a,b:x,y,static,u:s,t,common,v)=y;//函数定义;
_S(a,b:x,y,static,u:s,t,common,v)=static;//函数定义;
U(a,b:x,y,static,u:s,t,common,v)=u;//函数定义;
S(a,b:x,y,static,u:s,t,common,v)=s;//函数定义;
T(a,b:x,y,static,u:s,t,common,v)=t;//函数定义;
_C(a,b:x,y,static,u:s,t,common,v)=common;//函数定义;
V(a,b:x,y,static,u:s,t,common,v)=v;//函数定义;
F(11,22);//函数调用,进行变量赋值,返回值=10;
A(11,22);//函数调用,返回值=11;
B(11,22);//函数调用,返回值=22;
X(11,22);//函数调用,返回值=随机数值;
Y(11,22);//函数调用,返回值=随机数值;
_S(11,22);//函数调用,返回值=0;
U(11,22);//函数调用,返回值=0;
S(11,22);//函数调用,返回值=7;
T(11,22);//函数调用,返回值=8;
_C(11,22);//函数调用,返回值=9;
V(11,22);//函数调用,返回值=10;
可以在FORCAL表达式使用未定义的模块变量。在MForcal(MForcal是一个Forcal扩展动态库,可对源代码进行模块化编译)中,可使用编译符mvar:通知编译器使用未定义的模块变量,使用编译符unmvar:通知编译器取消这种设置,格式如下:
mvar: //通知编译器使用未定义的模块变量
s,t,u=5,v=6; //如果不存在同名的常量,就解释为模块变量
s=1, 2+s+u; //可以正确编译,返回值=8
unmvar: //通知编译器取消使用未定义的模块变量
2+s+u; //编译出错,变量不可识别
若要确保编译器将未定义的标识符解释为模块变量,则应在前面编译的表达式中,将该标识符明确地定义为模块变量并至少使用一次,如下例:
!const["v",66,1]; //创建一个常量v=66
v; //v为常量,返回值=66
mvar: //通知编译器使用未定义的模块变量
(::s,t,u,v)= s,t,u=5,v=6; //将s,t,u,v定义为模块变量并至少使用一次
s=1, 2+s+u; //s,u将解释为模块变量,返回值=8
v; //v将解释为模块变量,返回值=6
赋值语句的作用是将一个值赋给一个变量。在FORCAL中,可以用等号对变量进行赋值。例如:
F(x)= x=5; //函数定义,将数值5赋给变量x;
F[2]; //函数调用,返回值为5;
上面进行变量赋值的例子很简单,但等号后部分可以是任意复杂的表达式。
对象及对象成员的赋值用“.=”并最终通过函数实现,参考:类成员运算符(函数参数运算符 )及对象赋值运算符。
FORCAL中共有八个算术运算符,即:+(加或正)、-(减或负)、*(乘)、/(除)、%(求模)、^(乘方)、++(自增)、--(自减)。其中%(求模)仅在整数表达式中使用;^(乘方)仅在实数和复数表达式中使用。
运算符*、/、%和^是双目运算符。注意数字与变量相乘时,乘号不可省略;在进行乘方运算时,底数应为非负数。
运算符+、-作加、减运算时,是二元运算符,当作正、负运算时,是单目运算符。
运算符++、--是单目运算符,仅能对变量使用。++使变量的值加1,如果++在变量之前,那么运算符在程序语句访问该值之前执行加法运算,这时的++运算符称为“前置自增运算符”;如果把该运算符放在变量之后,那么运算符在程序语句访问该值之后执行加法运算,这时的++运算符被称为“后置自增运算符”。--使变量的值减1,如果--在变量之前,那么运算符在程序语句访问该值之前执行减法运算,这时的--运算符称为“前置自减运算符”;如果把该运算符放在变量之后,那么运算符在程序语句访问该值之后执行减法运算,这时的--运算符被称为“后置自减运算符”。例如:
(:x)= x=2,++x; //返回值为3;
(:x)= x=2,++x,x; //返回值为3;
(:x)= x=2,x++; //返回值为2;
(:x)= x=2,x++,x; //返回值为3;
(:x)= x=2,--x; //返回值为1;
(:x)= x=2,--x,x; //返回值为1;
(:x)= x=2,x--; //返回值为2;
(:x)= x=2,x--,x; //返回值为1;
如果在复数表达式中使用自增减运算符,则仅对复数的实部作运算,复数的虚部保持不变。例如:
c:(:x)= x=2+3i,++x; //返回值为3+3i;
c:(:x)= x=2+3i,--x; //返回值为1+3i;
单目运算符的优先级比双目运算符的优先级高,后置单目运算符的优先级比前置单目运算符的优先级高。对于同一优先级的运算,按从左到右的优先顺序进行。
注意:单目运算符-(负)与双目运算符^(乘方)需用括号区分计算的先后顺序。例如:
(-2)^2; //返回值为4;
-(2^2); //返回值为-4;
-2^2; //编译出错;
-(2+3)^2; //编译出错;
-sin(2+3)^2;//编译出错;
算术运算符的优先级如表6-1所示。在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。
表6-1:算术运算符及优先级
运 算 符 | 说 明 |
++、-- | 后置单目运算符,自增减运算符 |
+、-、++、-- | 前置单目运算符,“++、--”为自增减运算符 |
^ | 乘方 |
*、/、% | 乘、除、求模 |
+、- | 加、减 |
7 关系运算符和逻辑运算符 [返回页首] [返回目录]
关系运算是对两个值的大小进行比较,返回一个逻辑值。逻辑值只有两个:逻辑真和逻辑假。在FORCAL中用0表示逻辑假,其他任何非0值表示逻辑真。例如:
3>2; //返回逻辑真;
2>3; //返回逻辑假;
关系运算符共有6个:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于)。
逻辑值之间的运算称逻辑运算,逻辑运算的结果仍然是一个逻辑值。有三个逻辑运算符:&(逻辑与)、|(逻辑或)、!(逻辑非)。通过表7-1给出的真值表可以掌握这三种运算。表中用1代表逻辑真,0代表逻辑假。
表7-1 真值表
p | q | p&q | p|q | !p |
0 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 0 |
在FORCAL中,仅使用复数的实部表示逻辑值,逻辑值的虚部没有任何意义。所以,如果在复数表达式中使用关系运算符或逻辑运算符,则仅取复数的实部作运算,复数的虚部对运算结果没有任何影响;在运算结果中,仅复数的实部有意义,虚部没有意义,但其值为参与运算的第一个参数的虚部值。例如:
c:2+3i>5+6i; //返回值为0+3i;
c:!(2+3i); //返回值为0+3i;
c:2+3i==2+6i; //返回值为1+3i;
c:2+3i!=2+6i; //返回值为0+3i;
FORCAL的这种规定使得不能直接用==、!=运算符对两个复数作相等或不等的比较。如果要比较两个复数相等或者不等,应对复数的实部和虚部都进行比较,见下面的例子。
c:(:a,b)= a=2+3i,b=2+3i,a==b&xy(a)==xy(b); //返回值为1+3i;
c:(:a,b)= a=2+3i,b=2+5i,a==b&xy(a)==xy(b); //返回值为0+3i;
在上面的例子中,xy(a)是一个复数函数,该函数用于交换复数a的实部和虚部。
关系运算符和逻辑运算符的优先级如表7-2所示。在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。
表7-2 关系运算符和逻辑运算符及优先级
运 算 符 | 说 明 |
! | 逻辑非 |
>、>=、<、<=、==、!= | 关系运算符 |
& | 逻辑与 |
| | 逻辑或 |
8 逗号(冒号)运算符和括号运算符 [返回页首] [返回目录]
表达式中如果有多个语句,可以用逗号或冒号进行分隔,FORCAL将按从左到右的顺序计算各个语句,并返回最后一个语句的值。也可以将多个用逗号或冒号分隔的语句放在一个括号内,FORCAL也将按从左到右的顺序计算各个语句,并返回最后一个语句的值。例如:
(:x)= x=2,x=5,x; //返回值为5;
(:x)={x=2,x=5,x}; //返回值为5;
(:x,y)={x=2,y=5,x=[x=x+1,y=y+1,x+y]:x}; //返回值为9;
(:x,y)={x=2,y=5,[x=x+1,y=y+1:x+y]}; //返回值为9;
虽然逗号或冒号运算符是通用的,但逗号的用法很常见。冒号运算符仅用在一些特殊场合,例如分隔一些不同类别的变量。本文中除了对冒号运算符进行说明外,只使用逗号运算符。
FORCAL中的运算符及优先级如表9-1所示,在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。
表9-1:FORCAL运算符及优先级
运 算 符 | 说 明 |
:= | 括号连接运算符,冒号前和等号后都必须是括号 |
( )=、[ ]=、{ }= | 括号连接运算符,等号后是一个表达式 |
( )、[ ]、{ } | 括号运算符 |
:: | 命名空间成员访问符 |
. | 类成员运算符(函数参数运算符) 、变量函数调用运算符 |
++、-- | 后置单目运算符(自增、自减) |
!、+、-、++、-- | 前置单目运算符(非、正、负、自增、自减) |
^ | 算术运算符(乘方) |
*、/、% | 算术运算符(乘、除、求模) |
+、- | 算术运算符(加、减) |
>、>=、<、<=、==、!= | 关系运算符(大于、大于等于、小于、小于等于、等于、不等于) |
& | 逻辑与 |
| | 逻辑或 |
= | 赋值运算符 |
.= | 变量函数调用运算符与赋值运算符的结合,一般用于对象赋值 |
,、: | 逗号、冒号运算符 |
括号连接运算符应用举例:
(1,2):=[6,5] 等价于 (1,2,6,5) 或者 [1,2,6,5]
(1,2)=[6,5] 等价于 (1,2,[6,5])
(1,2)=5 等价于 (1,2,5)
括号连接运算符常用在函数调用中,例如有对数组操作的函数A,可接受变参数,使用格式如下:
A[me,i] //获得数组me的第i个单元的值
A[me,i,t] //设置数组me的第i个单元的值为t
将数组me的第i个单元的值加2,用函数可表示为:
A[me,i,A(me,i)+2]
用括号连接运算符可表示为:
A[me,i]:=[A(me,i)+2] 或者 A[me,i]=A(me,i)+2
再如有对对象操作的函数B,使用格式如下:
B[me,t0,t1,...,tn] //将对象me的值设置为t0,t1,...,tn
用括号连接运算符可表示为:
B[me]:=[t0,t1,...,tn]
注意在FORCAL中,通常运算符不能连用,例如 !-2 是错误的,应当添加括号分隔开各个运算符,即应当写成 !(-2) 形式。
还有一个FORCAL运算符在表9-1中没有列出:&(取变量的地址)。取地址运算符&只能用于(被逗号或冒号隔开的)单独的变量,与其他运算符不发生任何关系,所以这个运算符在表9-1中没有列出,其用法我们将在下面详细介绍。
类成员运算符(函数参数运算符)“.”、对象赋值运算符“.=”及命名空间成员访问符“::”也将在下面详细介绍。
函数是FORCAL的构成模块,是FORCAL最为重要的特性。一个函数,通过传递给它的参数完成一个特定的功能。通常函数都有一个名字,可通过调用函数名并传递参数来使用函数。所有的函数都将返回一个函数值。典型的函数调用方法如下:
函数名(参数1,参数2,... ...)
函数可以有零个或多个参数,但即便有零个参数,函数名后的括号也不能缺省。函数调用时,参数一定要匹配。
在FORCAL中可以使用的函数有三种:一级函数、二级函数和自定义函数(表达式)。其中一级函数为系统内置的函数,都是单变量或双变量函数,运算速度快;二级函数部分为系统内置的函数,部分为软件为提高性能扩充的函数,功能非常丰富;自定义函数实际上就是一个表达式,由用户定义,但只有有名字的自定义函数才能被其他表达式所调用。
实际上,FORCAL还有一类为数不多的函数,称为流程控制函数,不过对于它们,函数的意义并不明显,更多的是流程控制的意义,因此我们不把它们包括在上面的分类中。
FORCAL函数有三种类型,即整数函数、实数函数和复数函数,分别对应着FORCAL的三种表达式。FORCAL规定:一级函数和二级函数只能应用在同类型的表达式中,而自定义函数可以应用在任意的表达式中,FORCAL不会为自定义函数的调用进行类型转换,用户可根据需要自行转换。例如:
i:f(a,b)=a+b; //整数表达式定义
f(2,3); //实数表达式调用整数表达式,但未进行数据转换
itor{f[rtoi(2),rtoi(3)]}; //实数表达式调用整数表达式,函数rtoi将一个实数转换成一个整数,函数itor将一个整数转换成一个实数,计算值是正确的
f(a,b)=a+b; //实数表达式定义
i:f(2,3); //整数表达式调用实数表达式,但未进行数据转换
i:rtoi{f[itor(2),itor(3)]};//整数表达式调用实数表达式,函数rtoi将一个实数转换成一个整数,函数itor将一个整数转换成一个实数,计算值是正确的
如果不同类型的表达式之间相互调用时传递指针数据(例如函数句柄),将不必进行类型转换。
如果在整数或实数表达式中调用复数自定义函数,传递的参数个数应是复数自定义函数的自变量个数的两倍,即对复数自变量参数的实部和虚部都要进行赋值,且只能返回复数自定义函数的实部的值。如果在复数表达式中调用整数或实数类型的自定义函数,则整数或实数表达式的自变量个数应为复数表达式自变量个数的两倍,即调用时需将复数的实部和虚部传递给整数或实数自定义函数,但函数返回值只传递给复数的实部。例如:
c:a(x,y)=x+y; //复数自定义函数,有两个参数;
a(1,2,5,6); //调用函数a时,要传递四个参数;
b(x,y)=x+y; //实数自定义函数,有两个参数;
c:b(1+2i); //调用函数b时,只传递1个参数;
FORCAL中的一级函数见表10-1。
表10-1:一级函数
整数函数 | 整数函数说明 | 实数函数 | 实数函数说明 | 复数函数 | 复数函数说明 |
opp(x) | 相反数函数 | opp(x) | 相反数函数 | sin(x) | 正弦函数 |
abs(x) | 绝对值函数 | sin(x) | 正弦函数 | cos(x) | 余弦函数 |
not(x) | 按位非函数 | cos(x) | 余弦函数 | sqrt(x) | 平方根函数 |
or(x,y) | 按位或函数 | tan(x) | 正切函数 | exp(x) | 指数函数 |
and(x,y) | 按位与函数 | asin(x) | 反正弦函数 | ln(x) | 自然对数函数 |
xor(x,y) | 按位异或函数 | acos(x) | 反余弦函数 | floor(x) | 返回值的实部为不大于x的实部的最大整数,虚部不变 |
shl(x,i) | 位左移函数,相当于C语言中的x<<i | atan(x) | 反正切函数 | ceil(x) | 返回值的实部为不小于x的实部的最小整数,虚部不变 |
shr(x,i) | 位右移函数,相当于C语言中的x>>i | sqrt(x) | 平方根函数 | abs(x) | 绝对值函数 |
itor(x) | 将整数类型格式转换成实数类型格式,大数转换时有精度损失 | exp(x) | 指数函数 | con(x) | 共轭函数 |
rtoi(x) | 将实数类型格式转换成整数类型格式,大数转换时有误差 | ln(x) | 自然对数函数 | xy(x) | 实部虚部交换函数 |
padd(p,i) | 指针加函数,p为旧指针,i是一个整数,返回新指针为(p+i) | lg(x) | 常用对数函数 | x0(x) | 实部为0函数 |
ptoi(x) | 将指针转换成一个整数 | sinh(x) | 双曲正弦函数,[exp(x)-exp(-x)]/2 | y0(x) | 虚部为0函数 |
itop(x) | 将整数转换成一个指针 | cosh(x) | 双曲余弦函数,[exp(x)+exp(-x)]/2 | itor(x) | 将x的实部整数类型格式转换成实数类型格式,大数转换时有精度损失;虚部不变 |
getc(p) | 获得字符p的ASCII值,p是一个静态单字节字符地址 | tanh(x) | 双曲正切函数,[exp(x)-exp(-x)]/[exp(x)+exp(-x)] | rtoi(x) | 将x的实部实数类型格式转换成整数类型格式,大数转换时有误差;虚部不变 |
setc(p,x) | 设置字符p的ASCII值为x,p是一个静态单字节字符地址,函数仍返回p | abs(x) | 绝对值函数 | getc(p) | 获得字符p的ASCII值,p是一个静态单字节字符地址 |
getw(p) | 获得宽字符p的Unicode值,p是一个静态宽字符地址 | floor(x) | 返回不大于x的最大整数 | setc(p,x) | 设置字符p的ASCII值为x,p是一个静态单字节字符地址,函数仍返回p |
setw(p,x) | 设置宽字符p的Unicode值为x,p是一个静态宽字符地址,函数仍返回p | ceil(x) | 返回不小于x的最小整数 | getw(p) | 获得宽字符p的Unicode值,p是一个静态宽字符地址 |
getn(p) | 获得整数p的值,p是一个静态整数地址 | itor(x) | 将整数类型格式转换成实数类型格式,大数转换时有精度损失 | setw(p,x) | 设置宽字符p的Unicode值为x,p是一个静态宽字符地址,函数仍返回p |
setn(p,x) | 设置整数p的值为x,p是一个静态整数地址,函数仍返回p | rtoi(x) | 将实数类型格式转换成整数类型格式,大数转换时有误差 | getn(p) | 获得复数p的值,p是一个静态复数地址 |
to(p) | 将临时对象转换为一般对象,仅用于oo函数。若在oo函数外使用该函数,或者找不到该临时对象,将返回运行错误。 | getc(p) | 获得字符p的ASCII值,p是一个静态单字节字符地址 | setn(p,x) | 设置复数p的值为x,p是一个静态复数地址,函数仍返回p |
oofree(p) | 将一般对象转换为临时对象,仅用于oo函数。若在oo函数外使用该函数,或者该对象不支持此类转换,将返回运行错误。 | setc(p,x) | 设置字符p的ASCII值为x,p是一个静态单字节字符地址,函数仍返回p | ||
getw(p) | 获得宽字符p的Unicode值,p是一个静态宽字符地址 | ||||
setw(p,x) | 设置宽字符p的Unicode值为x,p是一个静态宽字符地址,函数仍返回p | ||||
getn(p) | 获得实数p的值,p是一个静态实数地址 | ||||
setn(p,x) | 设置实数p的值为x,p是一个静态实数地址,函数仍返回p | ||||
atan2(x,y) | 反正切函数,求x/y的反正切值,所在象限由x和y的符号确定 | ||||
fmod(x,y) | 求x/y的余数 | ||||
sign(x,y) | 符号传送函数,取y的符号,数值取x的绝对值,若y=0无符号传送,返回x值 | ||||
dim(x,y) | 正差函数,当x>y时得x-y,否则返回0 | ||||
padd(p,i) | 指针加函数,p为旧指针,i是一个整数,返回新指针为(p+i) | ||||
to(p) | 将临时对象转换为一般对象,仅用于oo函数。若在oo函数外使用该函数,或者找不到该临时对象,将返回运行错误。 | ||||
oofree(p) | 将一般对象转换为临时对象,仅用于oo函数。若在oo函数外使用该函数,或者该对象不支持此类转换,将返回运行错误。 |
11 函数的传值调用和传址调用 [返回页首] [返回目录]
传值调用是把变量值拷贝到被调函数的形式参数中,函数在执行时,参数的改变不会影响到调用函数时所用的变量。
传址调用(也叫引用调用)是把变量的地址拷贝到被调函数的形式参数中,函数在执行时,参数的改变会影响到调用函数时所用的变量。
通常,FORCAL是按传值调用的,除非你用取地址运算符&(也叫引用运算符)显示地通知FORCAL编译器,要按传址方式使用参数。在使用运算符&时,&只能放到(用逗号或冒号隔开的)单独存在的变量的前面。如下列:
a(x,y)= x=2,y=3;
(:x,y)= x=5,y=6,a(x,y),x; //传值调用,x=5;
(:x,y)= x=5,y=6,a(x,y),y; //传值调用,y=6;
(:x,y)= x=5,y=6,a(&x,y),x; //传址调用,x=2;
(:x,y)= x=5,y=6,a(x,&y),y; //传址调用,y=3;
12 用表达式作为函数的参数 [返回页首] [返回目录]
在函数调用时,有时候需要用表达式(即自定义函数)作为函数的参数。
12.1 用表达式的名称作为字符串(两个双引号"..."之间的内容为一个字符串)传给函数
例子:
F(x,y)=sin[x]+0.8-y; //定义二元函数;
sum("F",1,2,0.01,2,5,0.1); //对函数F循环求和:x自1到2,增量为0.01;y自2到5,增量为0.01。
12.2 用二级函数HFor("ForName",ForType)获得表达式句柄(也称为表达式指针、函数指针)传给函数
例子:
i: aa(x)=x+8; //定义一元函数aa;
i: (:x)=sys::call[1,HFor("aa",1),7,&x],x;//调用一元函数aa,并将参数7传递给该函数;sys::call是Forcal系统扩展库FcSystem32W的一个函数。
一般,在函数的说明中会指定需传递的表达式参数的类型,或者传递字符串形式的表达式名称,或者传递表达式的句柄。
13 表达式的初始化及销毁 [返回页首] [返回目录]
13.1 通过静态变量初始化和销毁数据
通过静态变量,Forcal表达式可以进行初始化,也可以借助专用静态变量free进行销毁表达式前的释放工作(Forcal在销毁表达式前将自动设置free=1,然后自动执行表达式)。如下例:
f(x:y,static,d,e,free,f)= //表达式用printff函数输出字符串,printff是Forcal数据类型扩展库FcData的一个函数。
{ if{!d,printff("初始化!"),d=1},//注意静态变量d使初始化仅执行一次。
if{free,printff("销毁!"),return(0)}, //Forcal在销毁表达式前将自动设置free=1,然后自动执行表达式,使销毁仅执行一次。
x+1 //每次调用使自变量增1。
};
f(0);
f(1);
f(2);
为了充分发挥静态变量free的作用,在表达式中不要对其有任何的赋值运算,以免引起混乱。另外,如果没有将free定义为静态变量,Forcal在销毁表达式前将不会将其自动设置为1,也不会自动执行表达式。
如果一个表达式中直接调用的其他表达式或二级函数被删除,该表达式将不能运行,这样即便在该表达式中定义了静态变量free,在Forcal销毁该表达式前也无法执行该表达式。如下例:
//函数f中调用了函数a,如果函数a先于函数f被销毁,则函数f将无法进行正常的销毁工作。
a(x)=x+1;
f(x:y,static,d,e,free,f)=
{ if{!d,printff("初始化!"),d=1}, //注意静态变量d使初始化仅执行一次。
if{free,printff("销毁!"),return(0)}, //如果函数a先于函数f被销毁,则函数f将无法进行正常的销毁工作。
a(x)+1 //调用函数a。
};
f(0);
f(1);
f(2);
若要上面的程序正常工作,主程序在销毁表达式时必须遵循“后进先出”的原则,即:后编译的表达式先删除。
动态调用表达式或二级函数,能使上面的程序正常工作。如下例:
//以下语句中,sys::call()是Forcal系统扩展动态库FcSystem32W中定义的一个函数,可在运行时调用自定义函数;
//函数f动态调用了函数a,即便函数a先于函数f被销毁,也不影响函数f执行正常的销毁工作。
i: a(x)=x+1;
i: f(x:y,static,d,e,free,f)=
{ if{!d,printff("初始化!"),d=1},//注意静态变量d使初始化仅执行一次。
if{free,printff("销毁!"),return(0)}, //Forcal在销毁表达式前将自动设置free=1,然后自动执行表达式,使销毁仅执行一次。
sys::call(1,HFor("a",1),x,&x),x+1//通过函数sys::call()动态调用函数a,使自动销毁正常进行。因为在sys::call()执行前,不知道函数a是否存在。
};
i: f(0);
i: f(1);
i: f(2);
13.2 通过函数free(p)销毁数据
函数free(p)可将对象p自动记录下来,在销毁表达式时销毁对象p。用法举例如下:
i: ConstObj(:static,obj)=
{
if{!obj, obj=new[...].free()},//第一次调用函数ConstObj()时,用函数new申请一个对象obj,并用函数free()标记在销毁表达式时销毁它
obj //返回对象obj
};
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
注意:函数free(p)并不立即销毁对象p,而是在销毁表达式时销毁该对象。并不是所有的对象都可由函数free销毁,这取决于主程序和Forcal扩展库的设计,在Forcal数据类型扩展库FcData中用函数new申请的对象都可由函数free来销毁。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。
注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。
最后指出,能否进行销毁表达式前的释放工作,取决于主程序的设计,与主程序如何使用Forcal的输出函数有关。即便销毁表达式前没有进行释放工作,Forcal最后也将释放所有资源,因而无需担心太多。
14 表达式的完整定义及表达式句柄 [返回页首] [返回目录]
在这里,我们总结性地给出FORCAL表达式的完整定义,以帮助用户更好地理解FORCAL,为了定义的完整性,部分地重复了前面所叙述过的内容。FORCAL表达式的完整定义如下:
F(a,b:x,y,static,u,free,v:s,t,common,w)=
{x=1,y=2,[x+y,x*y],(x-y):
a+b+x+y+static+u+s+t+common+v
}
F是表达式的名字,必须位于表达式的开头,该名字必须是一个标识符。给表达式起一个名字,主要是以后可以通过该名字来调用该表达式。表达式也可以没有名字,但这样,在其它的表达式中将无法调用它。在FORCAL中,一般表达式的名字是唯一的,不能给两个表达式起同一个名字(模块内表达式的名字除外,模块的定义将在后面介绍)。
如果定义了表达式名字,表达式名字后必须跟一对小括号(只能用小括号),用来定义变量。变量个数可以为零,也可以有任意多个。有多个变量时,变量间以逗号或冒号分隔。用冒号隔开的变量,从前往后依次为自变量、动态变量、静态变量、模块变量和全局变量,即第一个冒号前为自变量,两个冒号之间为动态变量和静态变量,第二个冒号后为模块变量和全局变量。两个冒号之间用关键字static分隔动态变量和静态变量,static之前为动态变量,static及以后变量均为静态变量,关键字static只能用在两个冒号之间。第二个冒号后用关键字common分隔模块变量和全局变量,common之前为模块变量,common及以后变量均为全局变量,关键字common只能用在第二个冒号后。自变量、动态变量和静态变量只能被定义该变量的表达式所访问;模块变量可被同一模块的所有表达式所访问,其他模块的表达式无法访问;全局变量可被所有的表达式所访问。自变量用于向表达式传递参数,因此自变量也称为形式参数。动态变量只在表达式执行时起作用,一旦表达式执行完毕,动态变量也随之失效。静态变量存在于表达式的整个生命周期,每次表达式执行完毕,静态变量的值被保留。FORCAL在编译表达式时,将所有静态变量初始化为0,其余的变量均不进行初始化。如果定义了静态变量free,Forcal在销毁表达式前将自动设置free为1,然后自动执行表达式。所有变量以及冒号均可缺省。在这个例子中,a和b是自变量,x和y是动态变量,static、u、free和v是静态变量,s和t是模块变量,common和w是全局变量。
即便表达式没有名字,也可以用一对小括号(只能用小括号)来定义变量,这时,小括号必须位于表达式的开头。
如果用小括号定义了变量,小括号后必须跟一个等号,该等号标志表达式可执行部分的开始,等号及等号后的可执行部分均不可缺省。
如果表达式中的变量名与一个常量名相同,则常量名被忽略。
表达式中可以不定义变量,这时,表达式中只有可执行部分,称常量表达式,或者称无参表达式。
表达式的可执行部分任何情况下都不可缺省。
表达式的可执行部分由多个语句组成,多个语句之间用逗号或冒号分隔,多个语句可以放在一对括号内。可执行部分中可以使用三对括号( )、[ ]和{ },括号必须成对使用。在FORCAL中,括号是一种运算符,具有一个值,即该括号内最后一个语句的值。表达式总是返回(最外层括号中)最后一个语句的值。另外,最外层的括号不是必须的,但表达式若有逗号或冒号隔开的多个语句,有了最外层的括号后,表达式更易读。
用这种方式定义表达式时,实际上就是自定义了一个函数,可以在任意的表达式中使用该函数(只要给该函数起了名字),由于这个原因,我们常常将表达式说成是函数,或者说表达式就是一个函数,但反过来并不成立。
以下都是合法的表达式定义的例子:
2; //常量(无参)表达式(函数);
()=2; //常量(无参)表达式(函数);
(::)=2; //常量(无参)表达式(函数);
A()=2; //常量(无参)表达式(函数),但定义了函数名字,在其它表达式中可以调用该函数;
A(::)=2; //常量(无参)表达式(函数),但定义了函数名字,在其它表达式中可以调用该函数;
B(x)=2; //有一个自变量的表达式(函数);
(x)=23; //有一个自变量的表达式(函数),但没有函数名,不能在其他表达式中调用该函数;
F(a,b)= ... ... //定义了两个自变量a和b;
F(:x,y)= ... ... //定义了两个动态变量x和y;
F(:static,u)= ... ... //定义了两个静态变量static和u;
F(::s,t)= ... ... //定义了两个模块变量s和t;
F(::common,v)= ... ... //定义了两个全局变量common和v;
F(a,b:x,y)= ... ... //定义了两个自变量a和b,两个动态变量x和y;
F(a,b::s,t)= ... ... //定义了两个自变量a和b,两个模块变量s和t;
F(:x,y:s,t)= ... ... //定义了两个动态变量x和y,两个模块变量s和t;
FORCAL表达式可用句柄(也称为表达式指针、函数指针)进行标识,可以使用二级函数HFor("ForName",ForType)获得表达式的句柄。表达式句柄有很多用途,例如可以作为函数的参数、获得表达式中的静态字符串等等。
在FORCAL中,表达式的各个语句一般是顺序执行的。但是某些函数可以改变语句执行的顺序,称为流程控制函数。
15.1 立即返回函数 return(x)
结束计算并立即返回表达式的值为x。
15.2 判断函数 if(x,y1,y2,... ...,yn)
当逻辑语句x的值为真时,依次执行语句y1,y2,... ...,yn,否则,不执行语句y1,y2,... ...,yn。
该函数至少要有2个自变量参数,其中第一个参数为逻辑语句。
该函数的返回值无意义。
15.3 自定义分段函数
which{逻辑语句1,语句1,
逻辑语句2,语句2,
... ...,
逻辑语句n,语句n,
缺省语句
};
FORCAL从前往后计算并检测逻辑语句的值,当检测到逻辑真时,计算与此逻辑真对应的语句并返回该语句的值,如果没有检测到逻辑真,则计算缺省语句的值作为返回值,若此时没有缺省语句,则产生一个运行错误(错误代码为0)。
该函数至少要有2个自变量参数。
例如下式定义了一个分段函数:
(x)=which{x>0,2*x-1,
x*x-1
};
如果舍弃该函数的返回值,则该函数可以作为一个选择计算函数使用。
15.4 while循环函数
while循环是“当型”循环,其特点是:先判断条件是否成立,当条件成立时,则执行循环体,否则退出循环体,即“当条件成立时执行循环”。“当型”循环的循环体有可能一次也不执行。
while循环函数的格式如下:
while{x,
y1,y2,
...,
break(),
...,
continue(),
...,
yn
};
其中x为逻辑语句;y1,y2,...,break(),...,continue(), ...yn为循环体语句。当x的值为真时,依次执行循环体语句,直到x的值为假时退出循环。当执行到break()函数时,跳出while循环,执行while循环后面的语句部分;当执行到continue()函数时,返回到while循环的开始语句x处继续执行。
在循环体语句中,必须有能修改逻辑语句x的值的语句,使x的值为假,退出循环体,否则将产生无限循环。
该函数至少要有2个自变量参数,其中第一个参数为逻辑语句。
该函数的返回值无意义。
以下是一个while循环的例子:
(:i,k)=
{i=0,k=0,
while{i<=1000000,k=k+i,i++}, //当i<=1000000时,计算k=k+i,然后i++;
k
};
15.5 until循环函数
until循环是“直到型”循环,其特点是:先执行循环体,再判断条件是否成立,当条件成立时,退出循环体,否则继续执行循环体,即“执行循环直到条件成立”。“直到型”循环的循环体至少执行一次。
until循环函数的格式如下:
until{x1,x2,
...,
break(),
...,
continue(),
...,
y
};
until为先执行后判断的循环函数。即先执行循环体语句x1,x2,...,break(),...,continue(),...,然后计算逻辑语句y的值,直到y的值为真时退出循环。当执行到break()函数时,跳出until循环,执行until循环后面的语句部分;当执行到continue()函数时,返回到until循环的开始语句x1处继续执行。
在循环体语句中,必须有能修改逻辑语句y的值的语句,使y的值为真,退出循环体,否则将产生无限循环。
该函数至少要有2个自变量参数,其中最后一个参数为逻辑语句。
该函数的返回值无意义。
以下是一个until循环的例子:
(:i,k)=
{i=0,k=0,
until{k=k+i,i++,i>1000000}, //计算k=k+i,i++,当i>1000000时退出;
k
};
注意:
(1)break()和continue()是两个无参函数,只能用在while和until两个循环函数中。
(2)FORCAL支持多线程,在多线程的程序中,如果不慎进入了无限循环,可以通过另一个线程退出。
在FORCAL中使用Unicode宽字符串,一个宽字符有2个字节,用两个双引号定义一个字符串,即"..."表示一个字符串。在FORCAL字符串中,还可以用反斜杠转义字符输入一些特殊字符,见表16-1。
表16-1 反斜杠转义字符
\\ | 反斜杠“\” |
\" | 双引号“"” |
\% | 0 |
\a | 警告 |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\& | 没有对应的字符。当该转义字符位于字符串最前面时(例如:"\&... ..."),指示编译器在数的边界处存放该字符串,并影响转义字符\[n];该转义字符位于字符串的其他位置时不产生任何作用 |
\NNNNN | 任意字符,NNNNN是该字符Unicode码的10进制值,NNNNN必须是5个数字,例如NNNNN码为9的字符,应写成\00009 |
\xNNNN | 任意字符,NNNN是该字符Unicode码的16进制值,NNNN必须是两个16进制数字(字母A到F以单个数字的形式表示10进制数的10到15),例如Unicode码为11的字符,应写成“\x000B” |
\[n] | n是一个10进制整数,转换成n×k个空格符。 若字符串最前面没有转义字符\&,k=1;若字符串最前面有转义字符\&,则在整数和实数表达式中k=4,在复数表达式中k=8,这种字符串格式可转换为整数、实数或复数数组。例如: 任意表达式中,"\[256]" 是一个256个字符长的空格符字符串。实数表达式中,"\&\[256]" 是一个256×4个字符长的空格符字符串。 |
除了表中定义的之外,FORCAL没有定义其它的转义字符(“\”和其他字符的组合都是非法的)。可以看出,反斜杠“\”和双引号“"”只能通过转义字符输入。
另外,若字符串前有符号@(例如:@"...\r\n..."),将忽略转义字符。
例如:
printff("hello!\r\n字符串!!!"); //用printff函数输出字符串;
printff(@"hello!\r\n字符串!!!");//用printff函数输出字符串;
FORCAL在编译表达式时,将同一个表达式中的字符串连续存放,同时将每一个字符串都转换成一个整数,该整数即该字符串的首字符所在的位置,称字符串的地址。因而,两个相邻字符串地址的差即前一个字符串的长度的负值。
例如:
(:i,j)={i="Hello Forcal !",j="end",printff(i),j-i}; //该函数返回第一个字符串的长度;
可以用字符串标识任意类型的数据值,如前所述,字符串形式的表达式的名称可以作为参数传给函数,此时,该字符串标识一个表达式。
可以用字符串存储任意类型的数据值,如下面将要讲到的静态数组。
Forcal字符串有二种类型:Forcal静态字符串和Forcal动态字符串。Forcal静态字符串即在Forcal表达式中定义的字符串,长度是固定的,不能随意进行销毁(但Forcal静态字符串也并非一成不变);Forcal动态字符串由Forcal扩展动态库FcData进行管理,可由函数new进行申请,由函数delete进行销毁。
Forcal静态字符串又分为近程静态字符串和远程静态字符串。近程静态字符串仅用一个整数(该整数即字符串在表达式中的地址)就可以标识,远程静态字符串需由表达式句柄和一个整数进行标识。Forcal数据扩展动态库FcData中的函数printff可以输出远程静态字符串,如下例:
bb()="adfg"; //定义表达式bb,函数返回一个Forcal静态字符串地址;
printff["{1,fs}",2,HFor("bb",2),bb()];//用函数printff输出表达式bb中的字符串,函数HFor用于获得表达式bb的句柄。
由此可见,可在自定义表达式之间通过表达式句柄和字符串地址传递Forcal远程静态字符串。
通常,涉及字符串的规范的二级函数命名将按以下约定:函数名中含有NStr(Forcal近程静态字符串),表示需要一个Forcal静态字符串地址参数;函数名中含有FStr(Forcal远程静态字符串),表示需要表达式句柄和一个Forcal静态字符串地址参数;函数名中含有Str(Forcal动态字符串),表示需要一个FcData字符串指针参数。
附:标准输出函数printff简介
格式1:printff("hello,Forcal!");
说明:只有一个字符串参数时,直接输出Forcal近程静态字符串。
格式2:printff("aa{1,ns}\r\nbb{2,r,10.5}","hello.",sin[1.2]);
说明:有多个参数时(参数序号以0为基数,依次为0,1,2,...),第0个参数是含格式说明的字符串,其余为要输出的参数列表。第0个字符串参数由两部分组成,第一部分是普通字符,将被输出;第二部分是{...}内的格式说明。格式说明有三项(前2项不可缺省),由逗号隔开,以{2,r,10.5}为例:第一项2指出要输出printff函数的第2个参数;第二项r表示按实数格式输出;第三项10.5表示输出宽度为10个字符,保留5位有效数字,靠右边输出,若是负数表示靠左输出。常见说明符:ns表示Forcal近程静态字符串,fs表示Forcal远程静态字符串,s表示字符串指针,i表示10进制整数,x表示16进制整数,r表示实数等等。另外,i2表示2进制整数,i3表示3进制整数等等。
需要注意,函数printff在Forcal扩展动态库FcData中定义,详细请参考该库的说明。
在Forcal中定义和使用数组是通过一组函数操作静态字符串来实现的。可以将Forcal静态字符串转换为数组来使用,Forcal内置了ANSI字符数组、Unicode字符数组、整数数组、实数数组和复数数组的操作函数。
使用数组之前,须先用转义字符"\[n]"定义足够的静态空间。
17.1 Unicode字符数组
通过函数getw(p)和setw(p,x)操作Unicode字符数组,可用在任意表达式中。如下例:
i:(:i,k,str)={ str="**\[26]**", //定义字符串str,共30个字符,这就是一个Unicode字符数组
k=getw["a"], //用函数getw获得字符"a"的Unicode值并赋值给k
i=0,
(i<26).while{setw(str+2+i,k++),i++}, //将星号之间是字符空间设置为26个英文字母
printff(str)//输出字符数组str
};
17.2 ANSI字符数组
通过函数getc(p)和setc(p,x)操作ANSI字符数组,可用在任意表达式中。一个ANSI字符占1个字节,而一个Unicode字符占2个字节,故将Unicode字符地址乘以2即转换为ANSI字符地址。如下例:
i:(:i,j,k,str)={ str="**\[26]**", //定义字符串str,共30个字符,这是一个Unicode字符数组,但也可以看成是ANSI字符数组
k=getc["a"*2], //用函数getc获得字符"a"的ASCII值(实际上与字符"a"的Unicode值相等)并赋值给k
i=0,j=(str+2)*2,
(i<52).while{setc(j+i,k++),i++,setc(j+i,0),i++}, //将星号之间是字符空间设置为26个英文字母,注意函数setc用了两次,将一个宽字符的低位字节设为ASCII值,而高位字节为0
printff(str)//输出字符数组str
};
17.3 整数、实数及复数数组
通过函数getn(p)和setn(p,x)操作整数、实数或复数表达式中的相关数组,此时字符串必须定义成"\&... ..."格式。整数或实数表达式中,一个数占8个字节,而一个Unicode字符占2个字节,故将Unicode字符地址除以4即转换为数组地址。复数表达式中,一个数占16个字节,故将Unicode字符地址除以8即转换为数组地址。
整数或实数数组的例子:
(:i,j,k,end,str)={ str="\&\[10]", //定义字符串str,共40个字符,这是一个Unicode字符数组,但可以转换成整数 或实数数组
k=str/4,end=k+10, //转换成整数或实数数组地址k,end是数组末地址的下一个地址
i=k,
(i<end).while{setn(i,1),i++}, //将数组的每一个元素都设为1
i=k,j=0,
(i<end).while{j=j+getn(i++)}, //将数组的所有元素都加起来
j //输出相加后的值
};
复数数组的例子:
c:(:m,j,k,end,str)={ str="\&\[10]", //定义字符串str,共80个字符,这是一个Unicode字符数组,但可以转换成 复数数组
k=str/8,end=k+10, //转换成复数数组地址k,end是数组末地址的下一个地址
m=k,
(m<end).while{setn(m,1.1+1.1i),m++},//将数组的每一个元素都设为1
m=k,j=0,
(m<end).while{j=j+getn(m++)}, //将数组的所有元素都加起来
j //输出相加后的值
};
如果一个函数直接或者间接地调用了自己,称作函数的递归调用。FORCAL支持函数的递归调用。
为了在FORCAL中使用递归,需要在相应类型的表达式中设置好相应类型的堆栈。在整数表达式中用SetIntStackMax(n)进行设置,在实数表达式中用SetRealStackMax(n)进行设置,在复数表达式中用SetComplexStackMax(n)进行设置。注意n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出FORCAL的堆栈,但会使系统堆栈溢出,这样会使程序运行中断,丢失数据(作者还没有很好的解决这个问题!希望朋友们能够帮助解决,深表谢意。)。并不需要每次运行程序都进行堆栈的设置,如果堆栈设置的合适,可以只设置一次堆栈。
下面就是递归的最简单的例子:
SetRealStackMax(10000); //设置实数堆栈为10000;
a()=a(); //函数a递归地调用自己,属于无穷递归调用。
直接运行上面的表达式,将会返回一个堆栈溢出的运行错误。虽然溢出FORCAL的堆栈不会中断程序的运行,但对于上面的程序,无论设置多大的堆栈都会溢出,因为函数a的递归定义是错误的。递归函数应当包含一条控制该函数是继续调用其本身还是返回的语句。如果没有这样的语句,递归函数将用完分配给堆栈的所有内存空间,导致堆栈溢出错误。
下面举一个能正常递归调用返回的例子。
SetRealStackMax(1000); //设置实数堆栈为1000;
Fact(n)=which{n<=1,1,n*Fact(n-1)}; //阶乘函数Fact的递归实现;
Fact(3); //计算3!;
Fact(5); //计算5!;
Fact(10); //计算10!;
Fact(100); //计算100!;
以下是一个交叉递归的例子。
i: SetIntStackMax(1000); //设置整数堆栈为1000;
//以下语句中,sys::call()是Forcal系统扩展动态库FcSystem32W中定义的函数,可在运行时调用自定义函数;
i: a(x:k)=printff["a..."],if(x<1,return[x]),sys::call[1,HFor("b",1),x-1,k]; //a(...)函数中调用了b(...)函数;
i: b(x:k)=printff["b..."],if(x<1,return[x]),sys::call[1,HFor("a",1),x-1,k]; //b(...)函数中调用了a(...)函数;
i: a[10]; //启动递归程序;
FORCAL支持表达式的模块化编译。
在用FORCAL编译表达式时,要给该表达式指定模块号,模块号用一个整数进行标识。
在FORCAL中,一个模块由一个或多个表达式组成。模块用一个整数标识,整数可正可负,只要绝对值相等,就属于同一个模块。一般用正整数表示该模块名。模块共有两类,即主模块(0#模块)和普通模块(其他标号的模块)。
同一模块中,模块号为负的表达式称私有表达式,只能被本模块的表达式所访问(即调用),在其他模块中是不可见的;模块号为正的表达式称公有表达式或全局表达式,能被任何一个表达式所访问。主模块(0#模块)中的表达式都是私有表达式。任何一个表达式,既可以访问本模块中的表达式,也可以访问其他模块中的全局表达式,如果本模块中的一个私有表达式与其他模块的一个全局表达式重名,将优先调用本模块中的私有表达式。
由以上规定可以看出,主模块可以访问本模块中的表达式,也可以访问其他模块中的全局表达式。因此,主模块常常用在主程序中。
通常,可以用编译符#MODULE#及#END#定义一个模块,用编译符~输出模块中的全局表达式。另外,若表达式名称前有编译符!(首先解释为立即执行编译符,而不是逻辑非运算符),在编译后将立即执行;若表达式名称前有编译符:,只编译,不执行。如下例:
#MODULE# //定义一个子模块
!a()=printff("字符串!");//模块私有表达式,编译后立即执行
f(x)= x+1; //模块私有表达式
:g()= 100; //模块私有表达式,只编译,不执行
~h(x)= f(x)+g()+2; //全局表达式
#END# //子模块定义结束
f(x)= x+5; //主模块中的私有表达式,可以使用与其他模块中的表达式相同的名字
f[3]; //调用主模块中的函数f
h[3]; //调用子模块中的全局表达式
使用命名空间可以有效地避免函数重名问题。Forcal中可以用函数Module创建模块命名空间,命名空间创建后,可以用函数OutFun输出该模块的表达式,不管是私有表达式还是公有表达式,都可以输出。
Module("Name":"Name1","Name2",... ...) //创建模块命名空间Name,继承自"Name1","Name2",... ...
OutFun("fun1","fun2","fun3",... ...) //输出模块命名空间中的表达式"fun1","fun2","fun3",... ...
模块命名空间只能创建一次,可以继承,甚至可以循环继承,如果确实有必要。模块命名空间是一棵树或者是一个连通图。
当模块中有表达式时,才能创建该模块的命名空间,当该模块中的最后一个表达式被销毁时,将同时销毁该命名空间。
当为一个命名空间指定父空间(基空间)时,该父空间是否存在可以是未知的,即父空间并不一定要先于子空间而存在。
模块命名空间中输出的表达式可以用命名空间成员访问符::调用,如:Name::fun1(...)。如果该命名空间中没有输出指定的表达式,而该空间的父空间中输出了同名表达式,就会调用父空间中的同名表达式。可以连续使用访问符::直接调用指定父空间(或该父空间的父空间)中的表达式,如:Name1::Name2::Name3::fun1(...)。可以看出,模块命名空间中的表达式调用规则类似于C++中的虚函数调用规则。
由于Module和OutFun是两个函数,为了使创建的空间及输出函数立即可用,应在编译完Module或OutFun所在的表达式后,立即执行该表达式。
例子:
#MODULE# //定义一个子模块 !Module("AA"); //创建模块命名空间AA,该表达式编译后将立即执行 Set(x::xx)= xx=x; //模块私有表达式 Get(::xx)= xx; //模块私有表达式 aa()= 111; //模块私有表达式 !OutFun("Set","Get","aa"); //输出模块命名空间中的表达式,该表达式编译后将立即执行 #END# //子模块定义结束 #MODULE# //定义一个子模块 !Module("BB","AA"); //创建模块命名空间BB,继承自"AA",该表达式编译后将立即执行 Set(x::xx)= xx=x; //模块私有表达式 Get(::xx)= xx; //模块私有表达式 !OutFun("Set","Get"); //输出模块命名空间中的表达式,该表达式编译后将立即执行 #END# //子模块定义结束 //以下都是主模块中的表达式 aa()= 999999; aa(); //调用主模块中的表达式aa,结果为:999999 BB::aa(); //通过模块空间BB调用空间AA中的表达式aa,结果为:111 BB::Set(33); //调用模块空间BB中的表达式Set,结果为:33 BB::Get(); //调用模块空间BB中的表达式Get,结果为:33 BB::AA::Set(55); //调用模块空间AA中的表达式Set,结果为:55 BB::AA::Get(); //调用模块空间AA中的表达式Get,结果为:55 BB::Get(); //调用模块空间BB中的表达式Get,结果为:33 AA::Get(); //调用模块空间AA中的表达式Get,结果为:55为了避免二级函数重名,二级函数可以采用模块命名空间中的命名方式,如:Fun2::Set(...),称二级函数命名空间。是否采用二级函数命名方式,取决于提供二级函数的模块。
一般,二级函数名称中不应有空格,所以可用如下方式判断一个函数是一个二级函数,或者是一个模块命名空间中输出的表达式。
name ::Set(...); //注意::前有一空格,若编译通过,一般是模块命名空间中的表达式,若编译未通过,说明是个二级函数
为了避免常量重名,常量可以采用函数命名空间中的命名方式,如:ConstName::MyConst,称常量命名空间。常量命名空间可由程序或库提供,也可用函数const创建。
一般,常量名称中不应有空格,否则该常量将无法正常访问。
例子:
!const("aa::c1",111); //在命名空间aa中定义永久性常量
!const("aa::c2",222,1); //在命名空间aa中定义暂时性常量
aa::c1; //直接访问命名空间中的常量
aa::c2; //直接访问命名空间中的常量
!using("aa"); //访问命名空间aa
c1; //访问命名空间中的常量
c2; //访问命名空间中的常量
命名空间(包括模块命名空间和二级函数命名空间)比较长时,输入比较麻烦,使用using函数可简化命名空间的访问。
执行过函数using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace",... ...)后,如果有一个命名空间中的函数NameSpace2::fun(x,y),不必写出该命名空间,直接使用函数fun(x,y)就可以了;如果有一个常量命名空间中的常量ConstSpace::MyConst,也不必写出该命名空间,直接使用常量MyConst就可以了。函数命名空间若与常量命名空间相同,只写出一个即可。如下列:
using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace");
2+fun(5,6)+MyConst+3;
注意:如果有一个非命名空间中的函数fun(x,y),将优先调用该函数,遇到这种情况,应使用函数的全名:NameSpace2::fun(x,y)。如果有一个非命名空间中的常量MyConst,将优先调用该常量,遇到这种情况,应使用常量的全名:ConstSpace::MyConst。
可使用using函数一次指定多个命名空间,当再一次调用using函数时,以前指定的命名空间将被删除,可访问的命名空间被重新设定。用using函数指定的命名空间仅在本模块中起作用,换句话说,可在不同的模块中使用using函数而互不影响。
例子:
#MODULE# //定义一个子模块 !Module("AA"); //创建模块命名空间AA,该表达式编译后将立即执行 Set(x::xx)= xx=x; //模块私有表达式 Get(::xx)= xx; //模块私有表达式 aa()= 111; //模块私有表达式 !OutFun("Set","Get","aa"); //输出模块命名空间中的表达式,该表达式编译后将立即执行 #END# //子模块定义结束 #MODULE# //定义一个子模块 !Module("BB","AA"); //创建模块命名空间BB,继承自"AA",该表达式编译后将立即执行 Set(x::xx)= xx=x; //模块私有表达式 Get(::xx)= xx; //模块私有表达式 !OutFun("Set","Get"); //输出模块命名空间中的表达式,该表达式编译后将立即执行 #END# //子模块定义结束 //以下都是主模块中的表达式 !using("BB"); //主模块可访问命名空间BB aa(); //通过模块空间BB调用空间AA中的表达式aa,结果为:111 Set(33); //调用模块空间BB中的表达式Set,结果为:33 Get(); //调用模块空间BB中的表达式Get,结果为:33 BB::AA::Set(55); //调用模块空间AA中的表达式Set,结果为:55 BB::AA::Get(); //调用模块空间AA中的表达式Get,结果为:55 BB::Get(); //调用模块空间BB中的表达式Get,结果为:33 AA::Get(); //调用模块空间AA中的表达式Get,结果为:5524 类成员运算符(函数参数运算符)及对象赋值运算符 [返回页首] [返回目录]
若标识符后面有句点“.”,表示将该标识符联系到一个函数,例如:a.sin();或者产生一个函数调用,例如:a.b 相当于 a(b)。若变量是显示说明的,则通过句点将产生隐含的oset函数或oget函数调用,称变量函数调用,例如:a.b 相当于 oget[a,b];a.b=c 相当于 oset[a,b:c]。
在FORCAL中,任何一个数都有可能是一个类或指针,这需要在运行时才能确定,所以FORCAL编译器把任何一个函数或表达式都看作类或指针的成员函数。尽管FORCAL32W.dll中没有提供类或指针的概念,但FORCAL扩展动态库FcData中定义了该概念。为了存取像类或指针等复杂数据类型时,在形式上看起来更美观一些,FORCAL32W.dll中提供了类成员运算符“.”操作类的成员函数或指针数据。如下例:
(:x)= x=2.3, x.sin(); //计算sin(x)。
(:x)= x=2.3, sin.=x; //计算sin(x)。
(:x)= x=2.3, sin.x; //计算sin(x)。
f(x,y)= x.y.atan2().sin();//定义函数f,计算sin(atan2(x,y))。
(:x,y)= x=2.3,y=3.3, x.f(y); //计算f(x,y)。
(:x,y)= x=2.3,y=3.3, f.x.y; //计算f(x,y)。
(:x,y)= x=2.3,y=3.3, f[x]=y; //计算f(x,y)。
(:x,y)= x=2.3,y=3.3, f.x.=y; //计算f(x,y)。
(:x,y)= x=2.3,y=3.3, f.x=y; //计算f(x,y)。
"字符串也可以的".printff[];//执行printff["字符串也可以的"];
printff."字符串也可以的"; //执行printff["字符串也可以的"];
(:f,x,y)= x=2,y=3.3, f.x=y; //因f是一个显示说明的变量,计算oset(f,x:y)。
(:f,x,y)= x=2,y=3, f.x.y; //因f是一个显示说明的变量,计算oget(f,x,y)。
(:f,x,y)= x=2,y=3, f.x.y=f.x.y+1;//因f是一个显示说明的变量,计算oset[f,x,y : oget(f,x,y)+1]。
若句点“.”没有将标识符联系到一个函数,则第一个句点前只能是一个类似变量的标识符,其他句点“.”前允许是常量名、变量名、字符串、括号运算符或函数。
若句点“.”将标识符联系到一个函数,则任一句点“.”前允许是常量名、变量名、字符串、括号运算符或函数。运算符“.”表示它前面的参数是它后面最近的函数的一个参数,所以称为函数参数运算符,该运算符之所以也称为类成员运算符,是因为“.”就是为了表示类的成员关系而设置的。成员函数在调用时,先把类成员运算符“.”前的数据作为参数,并与函数括号内的参数一起合并,然后执行计算。即:函数的参数个数为类成员运算符“.”前的变量个数与函数括号内的参数个数之和。注意:只有成员函数前面的类成员数据才是该函数的参数。例如:
(2).(3).max[].(5).min[]
上式中,2和3是max[]的参数,而max[2,3]和5是min[]的参数,即:min{max[(2),(3)],(5)}。
使用类成员运算符,函数if、while、until等函数有以下用法:
(x).if //x为真时执行花括号内的内容。
{
... ...
};
(x).while//x为真时循环执行花括号内的内容。
{
... ...
};
//循环执行花括号内的内容直到x为真。
{
... ...
}.until(x);
类成员运算符不但可以表示类的成员关系,合理地使用该运算符也可使程序更易读。例如:类ClassA有一个成员函数ClassA_Add(a,b),可以执行类对象a和b相加,类对象a、b、c、d连续相加的表达式为:
ClassA_Add{ClassA_Add[ClassA_Add(a,b),c],d}
用运算符“.”可表示为:
a.ClassA_Add(b).ClassA_Add(c).ClassA_Add(d)
当然,以上还不是执行类对象a和b相加的最简形式,最简形式应是在oo{...,a=a+b+c+d,...}函数中实现运算符重载功能。请参考每一个类的说明。
尽管运算符“.”对程序的运行速度没有影响,但会影响编译速度,因而建议仅在需要的时候使用它。
1)若标识符后面有括号,表示是一个函数,否则是一个变量或常量名。
2)若标识符后面有句点“.”,表示将该标识符联系到一个函数,例如:a.sin(x);或者产生一个函数调用,例如:a.b 相当于 a(b)。若变量是显示说明的,则通过句点将产生隐含的oset函数或oget函数调用,称变量函数调用,例如:a.b 相当于 oget[a,b];a.b=c 相当于 oset[a,b:c]。
3)如果一个变量名与常量名相同,则常量名被忽略。确定变量或常量的顺行是:变量、常量、常量命名空间。
4)如果编译器允许使用未定义的模块变量,当遇到一个未定义的标识符时,将被解释为模块变量或常量。如果该常量不存在,就解释为模块变量。如果该常量存在,但不存在同名的模块变量,将解释为常量。如果常量和同名的模块变量同时存在,将优先解释为模块变量。若要确保编译器将未定义的标识符解释为模块变量,则应在前面编译的表达式中,将该标识符明确地定义为模块变量并至少使用一次。
5)如果是一个普通的函数名,则确定函数的顺行是:变量函数、一级函数或流程控制函数、自定义表达式、二级函数、using指定的命名空间中的函数。
6)如果是一个命名空间中的函数,确定函数的顺行是:模块命名空间、二级函数命名空间。
7)模块私有表达式与一个公有表达式重名时,优先调用本模块中的私有表达式。
在下面的说明中,FcErr表示相应函数的运行错误代码。
26.1 整数二级函数
26.1.1 最大值函数 max(x1,x2,x3,... ...):
若FcErr=1:没有参数。
26.1.2 最小值函数 min(x1,x2,x3,... ...):
若FcErr=1:没有参数。
26.1.3 设置整数堆栈最大数目 SetIntStackMax(n):
在使用递归函数之前,需要先设置一定数目的堆栈。通常n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出FORCAL的堆栈,但会使系统堆栈溢出,这样会使程序运行中断。该函数返回实际设置的堆栈数目。
若FcErr=1:内存分配失败;FcErr=2:在函数递归时使用该函数。
26.1.4 设置常量 const("ConstStr",ConstValue)或const("ConstStr",ConstValue,bMode):
ConstStr:常量名,要符合Forcal标识符的命名规定,否则编译器找不到该常量。可使用常量命名空间的方式命名,例如:"ConstName::MyConst"。
ConstValue:常量的值。当bMode为逻辑假时,不使用该值。
bMode:指出工作方式。若缺省该参数(函数const只有两个参数),创建一个永久性常量,无法删除一个永久性常量,除非Forcal重新初始化。若bMode为逻辑真,创建一个暂时性常量;若bMode为逻辑假,删除一个暂时性常量。暂时性常量保持常量的基本意义,在编译表达式时不可改变其值,但可被const函数删除。
该函数返回值的意义如下:
0:设置成功。
1:已存在该常量。
2:内存分配失败。
3:不能用空字符串作为常量名。
4:参数太多或者太少。
5:需用字符串指出常量名。
6:不能删除该常量。
26.1.5 获得调用其他类型表达式或其他类型二级函数时计算结果的字节值 GetCalByte(&x0,&x1):
在整数表达式中调用实数表达式或者复数表达式之后,紧接着调用该函数,可以获得计算结果的按字节复制来的值。由于Forcal表达式中,整数为8个字节,实数为8个字节,复数为16个字节,所以要获得实数结果的按字节拷贝,需使用1个参数,要获得复数结果的按字节拷贝,需使用2个参数。多余的参数被忽略,参数少将不能获得计算结果的完整拷贝。该函数返回计算结果前8个字节表示的整数值。
26.1.6 创建模块命名空间 Module("NameSpace":"name1","name2",... ...):
该函数为调用该函数的模块创建模块命名空间。NameSpace为空间名称,继承自"name1","name2",... ...。模块命名空间可以循环继承。模块命名空间是一棵树或者是一个连通图。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:创建成功。
1:至少需要一个参数,不能缺省模块命名空间名称。
2:不能重复创建模块命名空间,模块命名空间只能创建一次。
3:须用字符串指出模块命名空间名称。
4:内存错误。
5:该命名空间已被其他模块使用。
6:要创建的命名空间不能是空字符串。
26.1.7 输出模块命名空间中的表达式 OutFun("fun1","fun2",... ...):
该函数为模块命名空间输出表达式。模块命名空间即调用函数OutFun的模块的命名空间。须用字符串参数指出表达式的名称。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:操作成功。
i:其他正整数:前i-1个参数操作成功,第i个参数操作失败,其他参数未进行操作。
26.1.8 获得表达式的句柄 HFor("ForName",ForType,&nPara,&hModule,&bParaModify):
ForName:表达式的名称。可以是简单名称,也可以是包含模块命名空间访问符::(该访问符可简化为一个或多个:)的复杂名称。
ForType:表达式的类型。如果是简单名称,只能取1(标识整数表达式)、2(标识实数表达式)、3(标识复数表达式)。如果名称包含模块命名空间访问符::,只能取-1(标识整数表达式)、-2(标识实数表达式)、-3(标识复数表达式)。若缺省该参数,默认为1。
nPara:返回表达式的参数个数。该参数可以缺省。
hModule:返回表达式所在的模块,模块号保存在hModule的前4个字节中。该参数可以缺省。
bParaModify:返回一个逻辑值,表示表达式的自定义参数在执行过程中是否可能被修改,逻辑真表示可能被修改。该参数可以缺省。
26.1.9 访问命名空间 using("NameSpace1","NameSpace2","NameSpace::name",... ...):
命名空间(包括模块命名空间、二级函数命名空间和常量命名空间)比较长时,输入比较麻烦,使用using函数可简化命名空间的访问。
执行过函数using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace",... ...)后,如果有一个命名空间中的函数NameSpace2::fun(x,y),不必写出该命名空间,直接使用函数fun(x,y)就可以了;如果有一个常量命名空间中的常量ConstSpace::MyConst,也不必写出该命名空间,直接使用常量MyConst就可以了。函数命名空间若与常量命名空间相同,只写出一个即可。如下列:
using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace");
2+fun(5,6)+MyConst+3;
注意:如果有一个非命名空间中的函数fun(x,y),将优先调用该函数,遇到这种情况,应使用函数的全名:NameSpace2::fun(x,y)。如果有一个非命名空间中的常量MyConst,将优先调用该常量,遇到这种情况,应使用常量的全名:ConstSpace::MyConst。
可使用using函数一次指定多个命名空间,当再一次调用using函数时,以前指定的命名空间将被删除,可访问的命名空间被重新设定。用using函数指定的命名空间仅在本模块中起作用,换句话说,可在不同的模块中使用using函数而互不影响。
26.1.10 标记需自动销毁的对象 free(p):
函数free(p)可将对象p自动记录下来,在销毁表达式时销毁对象p。函数free(p)仍然返回对象p,若标记失败返回0。用法举例如下:
i: ConstObj(:static,obj)=
{
if{!obj, obj=new[...].free()},//第一次调用函数ConstObj()时,用函数new申请一个对象obj,并用函数free()标记在销毁表达式时销毁它
obj //返回对象obj
};
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
注意:函数free(p)并不立即销毁对象p,而是在销毁表达式时销毁该对象。并不是所有的对象都可由函数free销毁,这取决于主程序和Forcal扩展库的设计,在Forcal数据类型扩展库FcData中用函数new申请的对象都可由函数free来销毁。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。
注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。
26.1.11 获得对象信息 o(p,... ...):
p是一个对象指针,函数o(p,... ...)用于获得对象p的信息。
若FcErr=1:至少需要一个参数;FcErr=2:没有为对象重载该函数。
不是所有的对象都支持o函数,具体请查看该对象的说明。
26.1.12 对象赋值 oset(p,... ...):
p是一个对象指针,函数oset(p,... ...)可对对象p赋值。oset函数的调用是与等号相关联的。
若FcErr=1:至少需要一个参数;FcErr=2:没有为对象重载该函数。
不是所有的对象都支持oset函数,具体请查看该对象的说明。
例子:设A、B是二维数组(矩阵)对象,i=2,j=3
A(2,3)=1; //相当于 oset[A,2,3 : 1]
A.i.j=1; //相当于 oset[A,i,j : 1]
A.=A+B; //相当于 oset[A : A+B]
26.1.13 获得对象的值 oget(p,... ...):
p是一个对象指针,函数oget(p,... ...)可获得对象p的值;p也可以是一个自定义函数的指针,此时将产生一个函数调用。oget函数的调用不与等号相关联。
该函数的返回值请参考对象的有关说明。若p是整数或实数函数指针,则该函数返回函数调用的值;若p是复数函数指针,则传递参数的个数为复数参数个数的2倍加1,最后一个参数返回复数的虚部,而该函数返回复数的实部。
若FcErr=1:至少需要一个参数;FcErr=2:调用整数表达式时参数不匹配;FcErr=3:调用实数表达式时参数不匹配;FcErr=4:调用复数表达式时参数不匹配;FcErr=5:没有为对象重载该函数。
不是所有的对象都支持oget函数,具体请查看该对象的说明。
例子1:对象指针。设A是一个二维数组(矩阵)对象,i=2,j=3
A(2,3)=A(2,3)+1; //相当于 oset[A,2,3 : oget(A,2,3)+1]
A.i.j=A.i.j+1; //相当于 oset[A,i,j : oget(A,i,j)+1]
例子2:函数指针
a(x)=x+2;
(:p)= p=HFor("a"), p(3); //p(3)相当于oget(p,3),即调用函数a(3)
26.1.14 垃圾收集 gc():
立即扫描并销毁垃圾对象,释放所占空间。
在Forcal中,一个对象用一个指针标识,在32位平台上,一个指针是存放在一个数(整数、实数或复数)的前4个字节中,此时,我们也称这个数为指向该对象的指针。若Forcal系统中的静态变量、模块变量、全局变量以及正在运行的表达式的数据区(自变量、动态变量及数据堆栈)中存在一个数指向一个对象时,该对象是有效的,否则视为垃圾对象,会被垃圾收集器所回收。
26.1.15 设置一个对象是否可以保存子对象 SaveSubObj(p,k):
p是一个对象指针。当k=0时,对象p将不能保存子对象,但子对象可暂时存在,在垃圾回收时将被销毁;当k非0时,对象p将可以保存子对象,不会当作垃圾被回收。
若FcErr=1:不能对该对象设置此属性,该对象也不能保存子对象。
返回值:指针p。
说明:有些对象默认是可以保存子对象的,例如FcData中的类对象;有些对象默认是不保存子对象的,但可以用函数SaveSubObj使其能保存子对象,例如FcData中的64位整数数组、实数数组等;有些对象则无论如何都不能保存子对象。一个对象能否保存子对象,取决于该对象的设计者,请参考该对象的说明。
26.2 实数二级函数
26.2.1 最大值函数 max(x1,x2,x3,... ...):
若FcErr=1:没有参数。
26.2.2 最小值函数 min(x1,x2,x3,... ...):
若FcErr=1:没有参数。
26.2.3 取小数部分函数 modf(x,&y):
该函数把x分解成整数和小数部分,并返回小数部分,整数部分由y返回(必须使用运算符&)。
26.2.4 求和函数 sum("F",y1min,y1max,y1dy,y2min,y2max,y2dy,... ...):
F为求和函数;y1min,y1max,y1dy为第一个自变量的初值、终值和参数增量[初值<终值,参数增量>0],依次类推。
例子:
F(x,y)=sin[x]+0.8-y;
sum("F",1,2,0.01,2,5,0.1);
FcErr=1:应使用字符串传递表达式名称;FcErr=2:指定的表达式不存在;FcErr=3:参数个数不匹配;FcErr=4:常量表达式,无法求和;FcErr=5:内存分配失败;FcErr=6:自变量参数非法。
26.2.5 求积函数 pro("F",y1min,y1max,y1dy,y2min,y2max,y2dy... ...):
用法请参见sum(),用于求积。
FcErr=1:应使用字符串传递表达式名称;FcErr=2:指定的表达式不存在;FcErr=3:参数个数不匹配;FcErr=4:常量表达式,无法求积;FcErr=5:内存分配失败;FcErr=6:自变量参数非法。
26.2.6 数据求和函数 DataSum("F",y11,y12,... ...,y21,y22,... ...):
F为求和函数;y11,y12,... ...为第一组自变量数据,依次类推。
例子:
F(x,y)=x*y;
DataSum["F",1,2,3,4,5,6,7,8,9,10]; //返回值为190;
说明:对于式子F(x,y)=x*y,求x,y分别取1,2、3,4、5,6、7,8、9,10时的值的和。即求F[1,2]+F[3,4]+F[5,6]+F[7,8]+F[9,10]的值。
若FcErr=1:应使用字符串传递表达式名称;FcErr=2:指定的表达式不存在;FcErr=3:参数个数不匹配;FcErr=4:常量表达式,无法求和。
26.2.7 数据求积函数 DataPro("F",y11,y12,... ...,y21,y22,... ...):
用法请参见DataSum(),用于数据求积。
若FcErr=1:应使用字符串传递表达式名称;FcErr=2:指定的表达式不存在;FcErr=3:参数个数不匹配;FcErr=4:常量表达式,无法求积。
26.2.8 设置实数堆栈最大数目 SetRealStackMax(n):
在使用递归函数之前,需要先设置一定数目的堆栈。通常n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出FORCAL的堆栈,但会使系统堆栈溢出,这样会使程序运行中断。该函数返回实际设置的堆栈数目。
若FcErr=1:内存分配失败;FcErr=2:在函数递归时使用该函数。
若FcErr=1:该函数至少使用两个参数,或者参数不匹配;FcErr=2:应使用字符串传递二级函数名称;FcErr=3:函数类型参数m非法;FcErr=4:找不到该二级函数;FcErr=5:内存分配失败。
26.2.9 设置常量 const("ConstStr",ConstValue)或const("ConstStr",ConstValue,bMode):
ConstStr:常量名,要符合Forcal标识符的命名规定,否则编译器找不到该常量。可使用常量命名空间的方式命名,例如:"ConstName::MyConst"。
ConstValue:常量的值。当bMode为逻辑假时,不使用该值。
bMode:指出工作方式。若缺省该参数(函数const只有两个参数),创建一个永久性常量,无法删除一个永久性常量,除非Forcal重新初始化。若bMode为逻辑真,创建一个暂时性常量;若bMode为逻辑假,删除一个暂时性常量。暂时性常量保持常量的基本意义,在编译表达式时不可改变其值,但可被const函数删除。
该函数返回值的意义如下:
0:设置成功。
1:已存在该常量。
2:内存分配失败。
3:不能用空字符串作为常量名。
4:参数太多或者太少。
5:需用字符串指出常量名。
6:不能删除该常量。
26.2.10 获得调用其他类型表达式或其他类型二级函数时计算结果的字节值 GetCalByte(&x0,&x1):
在实数表达式中调用整数表达式或者复数表达式之后,紧接着调用该函数,可以获得计算结果的按字节复制来的值。由于Forcal表达式中,整数为8个字节,实数为8个字节,复数为16个字节,所以要获得整数结果的按字节拷贝,仅需用一个参数,要获得复数结果的按字节拷贝,需使用2个参数。多余的参数被忽略,参数少将不能获得计算结果的完整拷贝。复制复数结果时,函数返回计算结果前8个字节表示的实数值,即复数的实部。
26.2.11 创建模块命名空间 Module("NameSpace":"name1","name2",... ...):
该函数为调用该函数的模块创建模块命名空间。NameSpace为空间名称,继承自"name1","name2",... ...。模块命名空间可以循环继承。模块命名空间是一棵树或者是一个连通图。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:创建成功。
1:至少需要一个参数,不能缺省模块命名空间名称。
2:不能重复创建模块命名空间,模块命名空间只能创建一次。
3:须用字符串指出模块命名空间名称。
4:内存错误。
5:该命名空间已被其他模块使用。
6:要创建的命名空间不能是空字符串。
26.2.12 输出模块命名空间中的表达式 OutFun("fun1","fun2",... ...):
该函数为模块命名空间输出表达式。模块命名空间即调用函数OutFun的模块的命名空间。须用字符串参数指出表达式的名称。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:操作成功。
i:其他正整数:前i-1个参数操作成功,第i个参数操作失败,其他参数未进行操作。
26.2.13 获得表达式的句柄 HFor("ForName",ForType,&nPara,&hModule,&bParaModify):
ForName:表达式的名称。可以是简单名称,也可以是包含模块命名空间访问符::(该访问符可简化为一个或多个:)的复杂名称。
ForType:表达式的类型。如果是简单名称,只能取1(标识整数表达式)、2(标识实数表达式)、3(标识复数表达式)。如果名称包含模块命名空间访问符::,只能取-1(标识整数表达式)、-2(标识实数表达式)、-3(标识复数表达式)。若缺省该参数,默认为2。
nPara:返回表达式的参数个数。该参数可以缺省。
hModule:返回表达式所在的模块,模块号保存在hModule的前4个字节中。该参数可以缺省。
bParaModify:返回一个逻辑值,表示表达式的自定义参数在执行过程中是否可能被修改,逻辑真表示可能被修改。该参数可以缺省。
在实数表达式中,表达式句柄保存在实数的前4个字节中。
26.2.14 访问命名空间 using("NameSpace1","NameSpace2","NameSpace::name",... ...):
命名空间(包括模块命名空间、二级函数命名空间和常量命名空间)比较长时,输入比较麻烦,使用using函数可简化命名空间的访问。
执行过函数using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace",... ...)后,如果有一个命名空间中的函数NameSpace2::fun(x,y),不必写出该命名空间,直接使用函数fun(x,y)就可以了;如果有一个常量命名空间中的常量ConstSpace::MyConst,也不必写出该命名空间,直接使用常量MyConst就可以了。函数命名空间若与常量命名空间相同,只写出一个即可。如下列:
using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace");
2+fun(5,6)+MyConst+3;
注意:如果有一个非命名空间中的函数fun(x,y),将优先调用该函数,遇到这种情况,应使用函数的全名:NameSpace2::fun(x,y)。如果有一个非命名空间中的常量MyConst,将优先调用该常量,遇到这种情况,应使用常量的全名:ConstSpace::MyConst。
可使用using函数一次指定多个命名空间,当再一次调用using函数时,以前指定的命名空间将被删除,可访问的命名空间被重新设定。用using函数指定的命名空间仅在本模块中起作用,换句话说,可在不同的模块中使用using函数而互不影响。
26.2.15 标记需自动销毁的对象 free(p):
函数free(p)可将对象p自动记录下来,在销毁表达式时销毁对象p。函数free(p)仍然返回对象p,若标记失败返回0。用法举例如下:
i: ConstObj(:static,obj)=
{
if{!obj, obj=new[...].free()},//第一次调用函数ConstObj()时,用函数new申请一个对象obj,并用函数free()标记在销毁表达式时销毁它
obj //返回对象obj
};
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
注意:函数free(p)并不立即销毁对象p,而是在销毁表达式时销毁该对象。并不是所有的对象都可由函数free销毁,这取决于主程序和Forcal扩展库的设计,在Forcal数据类型扩展库FcData中用函数new申请的对象都可由函数free来销毁。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。
注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。
26.2.16 获得对象信息 o(p,... ...):
p是一个对象指针,函数o(p,... ...)用于获得对象p的信息。
若FcErr=1:至少需要一个参数;FcErr=2:没有为对象重载该函数。
不是所有的对象都支持o函数,具体请查看该对象的说明。
26.2.17 对象赋值 oset(p,... ...):
p是一个对象指针,函数oset(p,... ...)可对对象p赋值。
若FcErr=1:至少需要一个参数;FcErr=2:没有为对象重载该函数。
不是所有的对象都支持oset函数,具体请查看该对象的说明。
例子:设A、B是二维数组(矩阵)对象,i=2,j=3
A(2,3)=1; //相当于 oset[A,2,3 : 1]
A.i.j=1; //相当于 oset[A,i,j : 1]
A.=A+B; //相当于 oset[A : A+B]
26.2.18 获得对象的值 oget(p,... ...):
p是一个对象指针,函数oget(p,... ...)可获得对象p的值;p也可以是一个自定义函数的指针,此时将产生一个函数调用。
该函数的返回值请参考对象的有关说明。若p是整数或实数函数指针,则该函数返回函数调用的值;若p是复数函数指针,则传递参数的个数为复数参数个数的2倍加1,最后一个参数返回复数的虚部,而该函数返回复数的实部。
若FcErr=1:至少需要一个参数;FcErr=2:调用整数表达式时参数不匹配;FcErr=3:调用实数表达式时参数不匹配;FcErr=4:调用复数表达式时参数不匹配;FcErr=5:没有为对象重载该函数。
不是所有的对象都支持oget函数,具体请查看该对象的说明。
例子1:对象指针。设A是一个二维数组(矩阵)对象,i=2,j=3
A(2,3)=A(2,3)+1; //相当于 oset[A,2,3 : oget(A,2,3)+1]
A.i.j=A.i.j+1; //相当于 oset[A,i,j : oget(A,i,j)+1]
例子2:函数指针
a(x)=x+2;
(:p)= p=HFor("a"), p(3); //p(3)相当于oget(p,3),即调用函数a(3)
26.2.19 垃圾收集 gc():
立即扫描并销毁垃圾对象,释放所占空间。
在Forcal中,一个对象用一个指针标识,在32位平台上,一个指针是存放在一个数(整数、实数或复数)的前4个字节中,此时,我们也称这个数为指向该对象的指针。若Forcal系统中的静态变量、模块变量、全局变量以及正在运行的表达式的数据区(自变量、动态变量及数据堆栈)中存在一个数指向一个对象时,该对象是有效的,否则视为垃圾对象,会被垃圾收集器所回收。
26.2.20 设置一个对象是否可以保存子对象 SaveSubObj(p,k):
p是一个对象指针。当k=0时,对象p将不能保存子对象,但子对象可暂时存在,在垃圾回收时将被销毁;当k非0时,对象p将可以保存子对象,不会当作垃圾被回收。
若FcErr=1:不能对该对象设置此属性,该对象也不能保存子对象。
返回值:指针p。
说明:有些对象默认是可以保存子对象的,例如FcData中的类对象;有些对象默认是不保存子对象的,但可以用函数SaveSubObj使其能保存子对象,例如FcData中的64位整数数组、实数数组等;有些对象则无论如何都不能保存子对象。一个对象能否保存子对象,取决于该对象的设计者,请参考该对象的说明。
26.3 复数二级函数
26.3.1 设置复数堆栈最大数目 SetComplexStackMax(n):
在使用递归函数之前,需要先设置一定数目的堆栈。通常n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出FORCAL的堆栈,但会使系统堆栈溢出,这样会使程序运行中断。该函数返回实际设置的堆栈数目。
若FcErr=1:内存分配失败;FcErr=2:在函数递归时使用该函数。
26.3.2 设置常量 const("ConstStr",ConstValue)或const("ConstStr",ConstValue,bMode):
ConstStr:常量名,要符合Forcal标识符的命名规定,否则编译器找不到该常量。可使用常量命名空间的方式命名,例如:"ConstName::MyConst"。
ConstValue:常量的值。当bMode为逻辑假时,不使用该值。
bMode:指出工作方式。若缺省该参数(函数const只有两个参数),创建一个永久性常量,无法删除一个永久性常量,除非Forcal重新初始化。若bMode为逻辑真,创建一个暂时性常量;若bMode为逻辑假,删除一个暂时性常量。暂时性常量保持常量的基本意义,在编译表达式时不可改变其值,但可被const函数删除。
该函数返回值的意义如下:
0:设置成功。
1:已存在该常量。
2:内存分配失败。
3:不能用空字符串作为常量名。
4:参数太多或者太少。
5:需用字符串指出常量名。
6:不能删除该常量。
26.3.3 获得调用其他类型表达式或其他类型二级函数时计算结果的字节值 GetCalByte(&x0):
在复数表达式中调用整数表达式或者实数表达式之后,紧接着调用该函数,可以获得计算结果的按字节复制来的值。由于Forcal表达式中,整数为8个字节,实数为8个字节,复数为16个字节,所以要获得整数或实数结果,仅需用一个参数就可以了,多余的参数被忽略。若复制整数(或实数)结果,则整数(或实数)结果仅占复数的前8个字节,其余字节无意义,且函数返回时,整数(或实数)结果也保存在返回值的前8个字节中(复数的实部)。
26.3.4 创建模块命名空间 Module("NameSpace":"name1","name2",... ...):
该函数为调用该函数的模块创建模块命名空间。NameSpace为空间名称,继承自"name1","name2",... ...。模块命名空间可以循环继承。模块命名空间是一棵树或者是一个连通图。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:创建成功。
1:至少需要一个参数,不能缺省模块命名空间名称。
2:不能重复创建模块命名空间,模块命名空间只能创建一次。
3:须用字符串指出模块命名空间名称。
4:内存错误。
5:该命名空间已被其他模块使用。
6:要创建的命名空间不能是空字符串。
26.3.5 输出模块命名空间中的表达式 OutFun("fun1","fun2",... ...):
该函数为模块命名空间输出表达式。模块命名空间即调用函数OutFun的模块的命名空间。须用字符串参数指出表达式的名称。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:操作成功。
i:其他正整数:前i-1个参数操作成功,第i个参数操作失败,其他参数未进行操作。
26.3.6 获得表达式的句柄 HFor("ForName",ForType,&nPara,&hModule,&bParaModify):
ForName:表达式的名称。可以是简单名称,也可以是包含模块命名空间访问符::(该访问符可简化为一个或多个:)的复杂名称。
ForType:表达式的类型。如果是简单名称,只能取1(标识整数表达式)、2(标识实数表达式)、3(标识复数表达式)。如果名称包含模块命名空间访问符::,只能取-1(标识整数表达式)、-2(标识实数表达式)、-3(标识复数表达式)。若缺省该参数,默认为3。
nPara:返回表达式的参数个数。该参数可以缺省。
hModule:返回表达式所在的模块,模块号保存在hModule的前4个字节中。该参数可以缺省。
bParaModify:返回一个逻辑值,表示表达式的自定义参数在执行过程中是否可能被修改,逻辑真表示可能被修改。该参数可以缺省。
在复数表达式中,表达式句柄保存在复数的前4个字节中。
26.3.7 访问命名空间 using("NameSpace1","NameSpace2","NameSpace::name",... ...):
命名空间(包括模块命名空间、二级函数命名空间和常量命名空间)比较长时,输入比较麻烦,使用using函数可简化命名空间的访问。
执行过函数using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace",... ...)后,如果有一个命名空间中的函数NameSpace2::fun(x,y),不必写出该命名空间,直接使用函数fun(x,y)就可以了;如果有一个常量命名空间中的常量ConstSpace::MyConst,也不必写出该命名空间,直接使用常量MyConst就可以了。函数命名空间若与常量命名空间相同,只写出一个即可。如下列:
using("NameSpace1","NameSpace2","NameSpace::name","ConstSpace");
2+fun(5,6)+MyConst+3;
注意:如果有一个非命名空间中的函数fun(x,y),将优先调用该函数,遇到这种情况,应使用函数的全名:NameSpace2::fun(x,y)。如果有一个非命名空间中的常量MyConst,将优先调用该常量,遇到这种情况,应使用常量的全名:ConstSpace::MyConst。
可使用using函数一次指定多个命名空间,当再一次调用using函数时,以前指定的命名空间将被删除,可访问的命名空间被重新设定。用using函数指定的命名空间仅在本模块中起作用,换句话说,可在不同的模块中使用using函数而互不影响。
26.3.8 标记需自动销毁的对象 free(p):
函数free(p)可将对象p自动记录下来,在销毁表达式时销毁对象p。函数free(p)仍然返回对象p,若标记失败返回0。用法举例如下:
i: ConstObj(:static,obj)=
{
if{!obj, obj=new[...].free()},//第一次调用函数ConstObj()时,用函数new申请一个对象obj,并用函数free()标记在销毁表达式时销毁它
obj //返回对象obj
};
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
注意:函数free(p)并不立即销毁对象p,而是在销毁表达式时销毁该对象。并不是所有的对象都可由函数free销毁,这取决于主程序和Forcal扩展库的设计,在Forcal数据类型扩展库FcData中用函数new申请的对象都可由函数free来销毁。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。
注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。
在一般的编程语言中,关键字是事先定义的有特别意义的字,关键字是保留字,不能用来做标识符(如变量名),使用关键字来做变量名是一种语法错误,不能通过编译。按此定义,则Forcal中没有关键字。Forcal中只有常量、变量和函数。但有些符号常量、变量名或函数名使用很频繁,可当作“关键字”来使用,不过不符合上述关键字的定义,例如:
f(return) = return(return+1); //return是自变量,同时也是一个二级函数。
f(2);
Forcal允许符号常量、变量和函数用同一个标识符表示,参考标识符解释规则。但尽量避免这种用法。
Forcal核心库中的“关键字”见下表(Forcal核心库中未定义任何符号常量,但一些Forcal扩展库中定义的符号常量可当作“关键字”来使用,如FcData中定义的符号常量char、int等等。该表仅收录Forcal核心库中的“关键字”)。
关键字 | 类型 | 功能 |
static | 静态变量 | 定义静态变量。 |
free | 静态变量、二级函数 | 专用静态变量或函数,进行销毁表达式前的释放工作。 |
common | 全局变量 | 定义全局变量。 |
const | 二级函数 | 定义永久性符号常量或暂时性符号常量。 |
return | 二级函数 | 结束计算并立即返回表达式的值。 |
if | 二级函数 | 条件满足时执行计算多个语句。 |
which | 二级函数 | 自定义分段函数,选择计算函数。 |
while | 二级函数 | “当型”循环函数。 |
until | 二级函数 | “直到型”循环函数。 |
continue | 二级函数 | 返回while或until循环的开始。 |
break | 二级函数 | 跳出while或until循环。 |
Module | 二级函数 | 创建模块命名空间。 |
OutFun | 二级函数 | 输出模块命名空间中的表达式。 |
HFor | 二级函数 | 获得表达式的句柄。 |
using | 二级函数 | 访问命名空间。 |
oo | 二级函数 | 支持运算符重载 |
to | 一级函数 | 将临时变量转换为一般变量 |
oofree | 一级函数 | 将一般变量转换为临时变量 |
o | 二级函数 | 获得对象信息 |
oset | 二级函数 | 对象赋值 |
oget | 二级函数 | 获得对象的值 |
指针是数据对象的内存地址,对象的成员函数通过指针可以操作对象,例如表达式句柄就是一个指针。Forcal扩展库中存在很多指针对象,例如Forcal数据扩展动态库FcData中,用函数new申请的数据对象都是指针。需要说明的是,静态字符串地址和静态数组地址不是指针,而是一个偏移量,但似乎也可以把它们看作指针一样进行各种操作。
在Forcal脚本中,函数oo{..., a+b, ...}中的运算符将被重载(该函数不可嵌套使用),运算符的第一操作数(例如a+b中的a)将被看作是一个指针(指向一个对象),Forcal在运算时将查找该指针的运算符重载函数,如果找到就调用该函数,否则将返回一个运行错误。有些重载运算符会返回一个临时对象,甚至某些二级函数也会返回一个临时对象,所有临时对象在生命期结束时自动销毁。
所有在oo函数中产生的临时对象的生命期不大于oo函数所在表达式运行时的生命期,即当一个表达式运行结束时,所有在该表达式中由oo函数产生的临时对象都将被销毁(若用函数to将临时对象转换为一般对象,则表达式的运行生命期结束时不会被销毁)。
如果某对象对运算符进行了重载,就要用到oo函数。例如:
oo{ //这段代码仅说明oo函数的用法,并不能正常运行
a=newo().oofree(), //申请一个对象,函数oofree将其转换为临时对象,由oo函数销毁它(一般情况下,在oo函数中生成的对象即为临时对象,因而无需使用oofree函数)
b=newo().oofree(), //申请一个对象,函数oofree将其转换为临时对象,由oo函数销毁它(一般情况下,在oo函数中生成的对象即为临时对象,因而无需使用oofree函数)
a+(a+b)*(a-b).show(), //两个对象运算后输出结果,产生的临时对象由oo函数销毁
p=[a+(a+b)/(a-b)].to() //两个对象运算后的结果由函数to转换为一般对象,并赋值给p,可在oo函数外使用该对象并销毁它
}
oo函数使用栈管理临时对象,故如果在oo函数中存在一个循环,而在循环中又不断生成新的对象,若不对新对象使用to函数,则只有最后一次循环的新对象被保留,其余的都将销毁。
oo函数中的运算符重载代码只是为了代码更清晰简洁,但运行效率较低。
在Forcal中可重载的运算符及函数见下表(实际的返回值请参考该对象的说明):
运算符 | 功 能 | 默认返回值 | 说 明 |
+ | 加 | 临时对象 | 双目运算符。 |
- | 减 | 临时对象 | 双目运算符。 |
* | 乘 | 临时对象 | 双目运算符。 |
/ | 除 | 临时对象 | 双目运算符。 |
% 或 ^ | 求模 或 乘方 | 临时对象 | 双目运算符。运算符%用在整数表达式中;运算符^用在实数表达式中。 |
- | 负 | 临时对象 | 单目运算符。 |
++ | 前置自增 ++i | 当前对象 | 单目运算符。 |
++ | 后置自增 i++ | 临时对象 | 单目运算符。 |
-- | 前置自减 --i | 当前对象 | 单目运算符。 |
-- | 后置自减 i-- | 临时对象 | 单目运算符。 |
> | 大于 | 逻辑值 | 双目运算符。 |
>= | 大于等于 | 逻辑值 | 双目运算符。 |
< | 小于 | 逻辑值 | 双目运算符。 |
<= | 小于等于 | 逻辑值 | 双目运算符。 |
== | 等于 | 逻辑值 | 双目运算符。 |
!= | 不等于 | 逻辑值 | 双目运算符。 |
& | 与 | 逻辑值 | 双目运算符。 |
| | 或 | 逻辑值 | 双目运算符。 |
! | 非 | 逻辑值 | 单目运算符。 |
o{...} | 重载函数 | 当前对象或一般对象 | 该函数一般用于获得对象信息。 |
oset{...} | 重载函数 | 当前对象或一般对象 | 该函数一般用于对象赋值。 |
oget{...} | 重载函数 | 当前对象或一般对象 | 该函数一般用于获得对象的值。 |
注意:在Forcal脚本中无法定义运算符的重载,运算符重载是由设计该对象的程序员实现的。
C程序员要自己管理内存的分配和回收,而Python具有垃圾自动回收的机制,Forcal的动态内存管理兼有二者的特点:既可以手动回收垃圾以提高运行效率,也可以完全依赖于Forcal的垃圾自动回收机制。通常,Forcal中用类似new的函数生成动态对象,而用类似delete的函数销毁动态对象,这一点类似于C,用户可以高效地管理内存;所有用户没有销毁的对象,会由Forcal的垃圾收集器管理,并最终会被Forcal安全地回收,这一点类似于Python。
Forcal核心库中并没有提供生成动态对象的函数,Forcal核心库只对创建动态对象提供了良好的支持。这意味着,支持模块可以使用C/C++、Delphi等语言创建任意的动态对象并加入Forcal系统。例如,Forcal数据类型扩展动态库FcData就是一个能创建动态对象的功能模块。
在Forcal中,一个对象用一个指针标识,在32位平台上,一个指针是存放在一个数(整数、实数或复数)的前4个字节中,此时,我们也称这个数为指向该对象的指针。若Forcal系统中的静态变量、模块变量、全局变量以及正在运行的表达式的数据区(自变量、动态变量及数据堆栈)中存在一个数指向一个对象时,该对象是有效的,否则视为垃圾对象,会被垃圾收集器所回收。
由于只要有一个指针(直接或间接)指向某对象,该对象就不会被垃圾收集器回收,故若要确保立即销毁某对象,应使用delete之类的专用函数,而不要依赖垃圾收集器。
Forcal的内存管理特点:(1)与C/C++类似,用户可立即销毁一个对象,这是推荐使用的方法;(2)被用户忽略的垃圾将被自动回收;(3)任何时候,可手动立即启动垃圾收集器;(4)垃圾回收是有运行开销的,但如果用户有效地管理了内存,垃圾回收器可以一次也不启动。
30.1 Forcal垃圾收集器何时启动
(1)用户调用函数gc()将立即启动垃圾收集器。
(2)提供某对象的模块检测到对象太多时将自动启动垃圾收集器。例如在FcData库中,在使用函数new创建对象时(以FcData为基础的库都是直接或间接地调用new函数创建对象的),若new发现当前的对象数达到了允许的最大数,将先启动垃圾收集器回收垃圾,再创建新的对象。一般来说,C/C++、Delphi程序员在设计动态对象时,会用一个计数器记住生成的动态对象数目,当达到规定值时,就启动垃圾收集器;当然,允许用户修改对象计数器允许的最大值。
(3)主程序可能会按一定的算法在处理器空闲时调用Forcal垃圾收集器,这取决于主程序的设计。
注意:与其他语言的垃圾收集器不同,Forcal垃圾收集器总是立即启动的。
30.2 子对象与垃圾回收
如果对象A的元素(或成员)包含了对象B,称对象B为对象A的子对象。在垃圾回收时,若对象A有效而子对象B被回收,称对象A是不能保存子对象的(在垃圾回收之前,子对象B可暂时保存);若只要对象A有效则子对象B就不会被回收,称对象A是能保存子对象的。
有些对象默认是可以保存子对象的,例如FcData中的类对象;有些对象默认是不保存子对象的,但可以用函数SaveSubObj使其能保存子对象,例如FcData中的64位整数数组、实数数组等;有些对象则无论如何都不能保存子对象。一个对象能否保存子对象,取决于该对象的设计者,请参考该对象的说明。
以下例子中数组A中的子对象被垃圾收集器gc()销毁了,故不能正常运行(不启动垃圾收集器时,可以正常运行):
//array[3]用于申请存放3个变量的一维动态数组,该一维动态数组是一个FcData对象
!using["math"]; //使用命名空间math,来自于数学库FcMath,该库由FcData提供支持
a(:b:A)= A=array[3], A[1]=array[3], b=A[1], b[2]=333;
b(:b:A)= gc(), //启动垃圾收集器
b=A[1], b[2]; //得到b[2]的值333
使用函数SaveSubObj使对象A可保存子对象,程序即可正常运行:
!using["math"];
a(:b:A)= A=array[3].SaveSubObj[1], A[1]=array[3], b=A[1], b[2]=333;
b(:b:A)= gc(),
b=A[1], b[2];
30.3 自己管理内存
以下程序运行时,将导致垃圾收集器多次启动,效率较低:
//arrayinit[1,3 : 1,2,3]用于申请存放3个变量的一维动态数组并初始化,该一维动态数组是一个FcData对象
!using["math"];
main(:i,s,a,b,c)=
{
s=0,i=0, while{++i<=10000, //循环10000次,将多次自动启动垃圾收集器,效率低
a=arrayinit[1,3 : 1,2,3], b=arrayinit[1,3 : 1,2,3], c=arrayinit[1,3 : 1,2,3],
s=s+a[0]+b[1]+c[2]
},
s //返回结果
};
改成手动管理内存可提高运行效率:
!using["math"];
main(:i,s,a,b,c)=
{
s=0,i=0, while{++i<=10000,
a=arrayinit[1,3 : 1,2,3], b=arrayinit[1,3 : 1,2,3], c=arrayinit[1,3 : 1,2,3],
s=s+a[0]+b[1]+c[2], delete[a,b,c] //用delete函数销毁对象a、b、c
},
s
};
在设计大的程序时,大多数人对使用类似delete的函数都心存畏惧,那么,使用Forcal特有的oo函数(在oo函数中产生的动态对象会被自动销毁)可以避免这一点,为此约定:
1、所有生成动态对象的函数和语句都放在oo函数中,因为这些动态对象会被自动销毁。
2、任何自定义函数不要返回动态对象。所有需要的动态对象由调用者生成,传给函数。
下面是一个由oo函数自动管理动态对象的例子:
!using["math"];
Add(a,b,c,d) = oo{d.=a+b+c}, d; //定义一个函数,计算并返回a、b、c三个对象的和
AddSub(a,b,c,d) = oo{c.=a+b, d.=a-b}; //定义一个函数,计算并返回a、b的和c与差d,返回值无意义
main(:a,b,c,d) =
{
oo{
a=matrix[2,3 : 1,2,3,1,2,3], b=matrix[2,3 : 1,2,3,4,5,6], //生成2×3矩阵并初始化
c=matrix[2,3], d=matrix[2,3] //生成2×3矩阵,工作矩阵
},
AddSub(a,b,&c,&d), //调用函数AddSub,注意使用了引用参数
outm[c], outm[d], //输出矩阵c、d
Add(a,b,c,d).outm() //调用函数Add,并输出结果
};
结果:
2. 4. 6.
5. 7. 9.
0. 0. 0.
-3. -3. -3.
4. 8. 12.
10. 14. 18.
如果确实需要全局对象,可使用以下格式:
!using["math"];
init(::a,b) = //全局动态对象用模块变量或全局变量保存,这里是用模块变量保存的。free函数指出在销毁自定义函数init时才销毁对象a,b
{
a=matrix[2,3 : 1,2,3,1,2,3].free(), b=matrix[2,3 : 1,2,3,4,5,6].free() //生成2×3矩阵并初始化
};
Add(a,b,c,d) = oo{d.=a+b+c}, d;
AddSub(a,b,c,d) = oo{c.=a+b, d.=a-b};
main(:c,d:a,b) =
{
oo{c=matrix[2,3], d=matrix[2,3]},
AddSub(a,b,&c,&d),
outm[c], outm[d],
Add(a,b,c,d).outm()
};
结果:
2. 4. 6.
5. 7. 9.
0. 0. 0.
-3. -3. -3.
4. 8. 12.
10. 14. 18.
错误有编译错误和运行错误两种。通常,Forcal程序总是准确定位编译期错误的。若程序编译通过,在运行时仍会发生错误,通常,Forcal程序总是记住并给出第一个运行错误的信息,错误信息包括运行出错的表达式的类型和名称(可见,给每一个表达式都起一个名字是非常重要的)、表达式所在的模块、运行出错的函数名、错误代码等等,用户可根据这些信息查找并修改错误。
Forcal运行时使用错误(异常)处理的恢复模型。模块化编译运行库MForcal中提供了一组函数以支持该功能:
1、检测输出并清除Forcal运行错误:err();
2、获得Forcal运行错误:geterr(&ErrType,FunName,FunNameLen,&FunCode,&ForType,&ForHandle);
ErrType:返回运行错误的类型。
FunName:Forcal静态字符串地址。返回出错函数名。
FunNameLen:Forcal静态字符串长度。
FunCode:返回函数错误代码。
ForType:返回出错表达式的类型。
ForHandle:返回出错表达式的句柄,该句柄即编译表达式时获得的句柄。
3、设置Forcal运行错误:seterr(ErrType,FunName,FunCode,ForType,ForHandle);
ErrType:设置运行错误的类型。
FunName:设置出错的函数名,要求传递一个Forcal静态字符串(长度小于80)。
FunCode:设置函数错误代码。
ForType:设为0。
ForHandle:未使用。
4、清除Forcal运行错误:clearerr();
以下例子用函数seterr设置了一个Forcal运行错误,并用函数geterr捕获了该错误:
f(:a,b,c,d,e)= seterr[66,"aaa",23,0,0], geterr[&a,b="\[10]",10,&c,&d,&e], printff[b];
Forcal运行错误可由Forcal系统、二级函数,或者由Forcal脚本用户进行设置,可在任意位置设置运行错误,同时可在任意位置捕获该运行错误。运行错误一经设置,将始终存在,直到遇到函数clearerr()或err()为止。在执行MForcal模块的最后,将自动调用函数err()。
捕获Forcal运行错误并进行处理后,可调用函数clearerr()清除运行错误,重启程序代码,通常需借助循环完成该处理过程。
FORCAL中有以下提高效率的方法。
1、将表达式中可以计算的部分放到括号中。
例如,需要将表达式:
F(x,y)=x-5-7+y
写成:F(x,y)=x-[5+7]+y或F(x,y)=x+[-5-7]+y。
2、尽量使用自增减运算符++和--。
例如要把 i=i+1 写成 i++ 形式。
3、until循环函数的速度比while循环函数要快。
4、模块变量和全局变量的访问速度比自动变量快。
5、尽量避免使用全局变量,全局变量会增加代码维护的困难。
6、类成员运算符“.”将降低编译速度,对运行速度没有影响,如果参数不是一个类,也没有增加程序的易读性,不要用该运算符。
7、避免使用转义字符"\[n]"开辟大的静态空间,将影响编译速度。
1、表达式最大长度:约2G。
2、自变量个数:不限。
3、动态变量、静态变量、模块变量、全局变量个数:不限。
4、最多可用的实数表达式:不限。
最多可用的复数表达式:不限。
最多可用的整数表达式:不限。
5、表达式中最多可用的字符串数:不限。
6、自定义外部函数个数:不限。
7、while循环、until循环最大循环次数:不限。
8、表达式递归调用最多层数:受系统堆栈和自定义堆栈大小的限制,自定义堆栈最大为2G。
9、最多可存储的用户自定义数据类型:约2G。
10、执行速度:一级函数速度约为FORTRAN(或C/C++)执行速度的50%左右;其他情况,速度稍有下降;
11、FORCAL32W.DLL文件大小:约130K;不使用MSVC运行库的静态库版本,约260K~300K。
12、内存需求:视FORCAL运行情况而定。
版权所有© Forcal程序设计 2002-2011,保留所有权利
E-mail: forcal@sina.com QQ:630715621
最近更新: <!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%Y年%m月%d日" startspan -->2011年07月21日<!--webbot bot="Timestamp" i-checksum="1254" endspan -->