`
lilisalo
  • 浏览: 1108338 次
文章分类
社区版块
存档分类
最新评论

与众不同的Forcal

 
阅读更多

欢迎访问 Forcal程序设计

与众不同的Forcal

本文罗列了Forcal与其他脚本的一些不同之处。

目 录

1 什么是Forcal Forcal是一个可对字符串表达式进行动态编译和运行的动态链接库(dll)。
2 Forcal中如何定义函数 Forcal用等号定义一个函数。
3 Forcal中的变量及定义 Forcal有五种变量:自变量、动态变量、静态变量、模块变量、全局变量。
4 Forcal有三种表达式(函数) 整数、实数和复数表达式。整数、实数和复数数据。
5 Forcal中的括号 Forcal中可使用三对等效的括号( )、[ ]、{ }。
6 Forcal语法基础 常量、变量和函数是Forcal语法的三块基石,各种运算符特别是逗号(冒号)运算符和括号运算符是基石之间的粘结剂。
7 Forcal中的模块 Forcal模块对私有数据的访问提供了良好支持。
8 模块命名空间 模块命名空间可以继承,甚至可以循环继承。模块命名空间是一棵树或者是一个连通图。
9 动态编译和运行程序 Forcal程序的编译和运行不是截然分开的,在编译中有运行,在运行时可编译。
10 运算符及函数重载 函数oo{..., a+b, ...}中的运算符将被重载。
11 动态内存管理 既可以手动回收垃圾以提高运行效率,也可以完全依赖于Forcal的垃圾自动回收机制。
12 错误处理 使用错误(异常)处理的恢复模型。
13 创建任意的对象 Forcal核心库没有任何内建对象,甚至连动态数组也不支持,但Forcal核心库为创建任意对象提供了良好的支持。
14 类模块 类模块=数据结构+算法。
15 轻量级计算引擎 FORCAL32W.DLL文件大小:约130K;不使用MSVC运行库的静态库版本,约260K~300K。
16 编译运行效率 一级函数速度约为FORTRAN(或C/C++)执行速度的50%左右;其他情况,速度稍有下降。
17 形式的优势 Forcal以动态链接库的形式存在,用Win32标准函数调用方式(stdcall调用协议)输出了动态库函数,使用极为方便。
18 无限的功能扩展 很容易对Forcal进行功能扩展,可用C/C++、delphi、FORTRAN等语言设计Forcal扩展动态库。
19 核心库和扩展库 所有的扩展库都是根据核心库的输出函数设计的。
20 对私有数据的保护 彻底地保护私有数据是Forcal的基本特色。
21 二级函数设计精要 二级函数是Forcal功能扩展的基本方式,二级函数必须检测并报告所有可能出现的错误,以实现错误处理的恢复模型。
22 句点的用法 若标识符后面有句点“.”,表示将该标识符联系到一个函数,或者产生一个函数调用。

1 什么是Forcal [返回页首]

通常,使用各种高级语言如C/C++、VB、delphi、FORTRAN等设计的程序,不能对源代码进行动态编译,Forcal可为所有这些应用程序增加对字符串源代码的动态编译功能。Forcal可用于各类数学软件的设计,也可用作报表处理、web、组态软件、游戏等的脚本,具有很高的执行效率。

Forcal32W.dll是Forcal的32位Unicode版本,是一个对Unicode字符型表达式进行编译计算的动态库。表达式中可以使用的运算符有+、-、*、/、^、>、>=、<、<=、==、!=、&、|、!、++、--等等,Forcal具有数值计算、逻辑运算、关系运算、字符数据处理、流程控制、函数调用等许多的可编程功能。

2 Forcal中如何定义函数 [返回页首]

Forcal用等号定义一个函数,与数学中定义函数的方式一致。例如:

f(x,y)=x+y;

3 Forcal中的变量及定义 [返回页首]

Forcal有五种变量:自变量、动态变量、静态变量、模块变量、全局变量。自变量、动态变量和静态变量只能被定义该变量的表达式所访问;模块变量可被同一模块的所有表达式所访问,其他模块的表达式无法访问;全局变量可被所有的表达式所访问。自变量用于向表达式传递参数,因此自变量也称为形式参数。动态变量只在表达式执行时起作用,一旦表达式执行完毕,动态变量也随之失效。静态变量存在于表达式的整个生命周期,每次表达式执行完毕,静态变量的值被保留。FORCAL在编译表达式时,将所有静态变量初始化为0,其余的变量均不进行初始化。

定义格式如下所示:

F(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;

4 Forcal有三种表达式(函数) [返回页首]

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等。

Forcal的三种表达式各有所长,且可以相互调用,以方便快捷地满足各种需要。

5 Forcal中的括号 [返回页首]

Forcal中可使用三对等效的括号( )、[ ]、{ },符合数学习惯,故代码清晰易懂。例如:

f(a,b)=2+sin{1.2-sqrt[a+(b-c)^2]};

6 Forcal语法基础 [返回页首]

常量、变量和函数是Forcal语法的三块基石,各种运算符特别是逗号(冒号)运算符和括号运算符是基石之间的粘结剂,Forcal语法大厦由此而构成。

Forcal中没有关键字,但有些符号常量、变量名或函数名使用很频繁,可当作“关键字”来使用,这又另当别论。

逗号(冒号)运算符和括号运算符使得Forcal语法简洁规范而又完整,因而是理解Forcal语法的钥匙。

表达式(或函数)中如果有多个语句,可以用逗号或冒号进行分隔,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中的流程控制是用函数来实现的,且只有如下5个函数:

(1)立即返回函数 return(x)

结束计算并立即返回表达式的值为x。

(2)判断函数 if(x,y1,y2,... ...,yn)

当逻辑语句x的值为真时,依次执行语句y1,y2,... ...,yn,否则,不执行语句y1,y2,... ...,yn。

(3)自定义分段函数

which{
逻辑语句1,语句1,
逻辑语句2,语句2,
... ...,
逻辑语句n,语句n,
缺省语句
};

FORCAL从前往后计算并检测逻辑语句的值,当检测到逻辑真时,计算与此逻辑真对应的语句并返回该语句的值,如果没有检测到逻辑真,则计算缺省语句的值作为返回值,若此时没有缺省语句,则产生一个运行错误(错误代码为0)。

(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处继续执行。

(5)until循环函数

until循环是“直到型”循环,其特点是:先执行循环体,再判断条件是否成立,当条件成立时,退出循环体,否则继续执行循环体,即“执行循环直到条件成立”。“直到型”循环的循环体至少执行一次。until循环函数的格式如下:

until{x1,x2,
...,
break(),
...,
continue(),
...,
y
};

until为先执行后判断的循环函数。即先执行循环体语句x1,x2,...,break(),...,continue(),...,然后计算逻辑语句y的值,直到y的值为真时退出循环。当执行到break()函数时,跳出until循环,执行until循环后面的语句部分;当执行到continue()函数时,返回到until循环的开始语句x1处继续执行。

以下是一个Forcal程序的例子:

// 据测定,以下八皇后问题,Forcal的运行速度约为C++的1/10。
// 八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是19世纪著名的数学家高斯1850年提出:
// 在8×8格的国际象棋盘上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
// 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
// 以下算法是从网上搜来的,该算法没有最终给出排列组合,仅仅给出有多少种组合,但是算法确实十分奥妙。

//Forcal源程序
i:(::sum,upperlim)= sum=0,upperlim=1,SetIntStackMax(1000);
i:test(row, ld, rd : pos,p : sum,upperlim)=
{
which
{ row != upperlim,
{ pos = and{upperlim , not[row.or(ld).or(rd)]},
while{ pos,
p = and(pos,-pos),
pos = pos -p,
test(row+p, shl(ld+p,1), shr(rd+p,1))
}
},
sum++
}
};
i:main(:tm,n:sum,upperlim)=
{
n=15,
tm=sys::clock(),
printff("Queens:{1,i}, ",n),
upperlim=shl(upperlim,n)-1,
test(0,0,0),
printff("sum:{1,i}, {2,i}毫秒.\r\n",sum,sys::clock()-tm)
};

Forcal运行结果:

Queens:15, sum:2279184, 59547毫秒.

完成相同功能的C++程序:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

long sum=0,upperlim=1;

void test(long row, long ld, long rd)
{
if (row != upperlim)
{
long pos = upperlim & ~(row | ld | rd);
while (pos){
long p = pos& -pos;
pos -= p;
test(row+p, (ld+p)<<1, (rd+p)>>1);
}
}
else
sum++;
}

int main(int argc, char *argv[])
{
time_t tm;
int n=15;

if(argc!=1)n=atoi(argv[1]);
tm=time(0);
if((n<1)||(n>32))
{
printf(" heh..I can’t calculate that.\n");
exit(-1);
}
printf("%d Queens\n",n);
upperlim=(upperlim<<n)-1;

test(0,0,0);
printf("Number of solutions is %ld, %d seconds\n", sum,(int)(time(0)-tm));
}

VC运行结果:

15 Queens
Number of solutions is 2279184, 6 seconds

7 Forcal中的模块 [返回页首]

通常,可以用编译符#MODULE##END#定义一个模块,用编译符~输出模块中的全局表达式,否则为私有表达式。另外,若表达式名称前有编译符!(首先解释为立即执行编译符,而不是逻辑非运算符),在编译后将立即执行;若表达式名称前有编译符:,只编译,不执行。如下例:

#MODULE# //定义一个子模块
mvar:
//编译符mvar:说明该模块中没有声明的变量为模块变量
aa=11, bb=22;
//定义了模块变量aa、bb,在模块外部无法访问
!a()= printff("字符串!");
//模块私有表达式,编译后立即执行
f(x)= x+1;
//模块私有表达式
:g()= 100;
//模块私有表达式,只编译,不执行
~h(x)= f(x)+g()+aa+bb;
//全局表达式
#END#
//子模块定义结束

mvar:
//编译符mvar:说明该模块中没有声明的变量为模块变量
aa=55, bb=66;
//定义了模块变量aa、bb,在模块外部无法访问
f(x)= x+aa;
//主模块中的私有表达式,可以使用与其他模块中的表达式相同的名字
f[3];
//调用主模块中的函数f
h[3];
//调用子模块中的全局表达式

可以看出,Forcal的模块化编译功能对私有数据的访问提供了良好支持。

8 模块命名空间 [返回页首]

使用命名空间可以有效地避免函数重名问题。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所在的表达式后,立即执行该表达式。

例子:


9 动态编译和运行程序 [返回页首]

Forcal程序的编译和运行不是截然分开的,在编译中有运行,在运行时可编译。例如:

#MODULE# //定义一个子模块
!Module("AA");
//创建模块命名空间AA,该表达式编译后将立即执行
Set(x::xx)= xx=x;
//模块私有表达式
Get(::xx)= xx;
//模块私有表达式
!OutFun("Set","Get");
//输出模块命名空间中的表达式,该表达式编译后将立即执行
#END#
//子模块定义结束

AA::Set(55);
//调用模块空间AA中的表达式Set,结果为:55
AA::Get();
//调用模块空间AA中的表达式Get,结果为:55

以上模块中,若函数ModuleOutFun不设置为编译后立即执行,则下面的函数AA::SetAA::Get在编译时就会不可识别。

再如下面的例子:

!using("sys"); //使用命名空间sys,编译后立即执行
main(:pf,a)=
oo{pf=fcfor()},
//申请保存Forcal表达式句柄的FcData数据指针,
comfor(pf,"f(x)=x+1",2,100,0,0),
//将Forcal静态字符串动态编译为全局的实数表达式
a=HFor("f"),
//获得函数f的句柄
a[5];
//动态调用函数f

10 运算符及函数重载 [返回页首]

在Forcal脚本中,函数oo{..., a+b, ...}中的运算符将被重载(该函数不可嵌套使用),运算符的第一操作数(例如a+b中的a)将被看作是一个指针(指向一个对象),Forcal在运算时将查找该指针的运算符重载函数,如果找到就调用该函数,否则将返回一个运行错误。有些重载运算符会返回一个临时对象,甚至某些二级函数也会返回一个临时对象,所有临时对象在生命期结束时自动销毁。

所有在oo函数中产生的临时对象的生命期不大于oo函数所在表达式运行时的生命期,即当一个表达式运行结束时,所有在该表达式中由oo函数产生的临时对象都将被销毁(若用函数to将临时对象转换为一般对象,则表达式的运行生命期结束时不会被销毁)。

注意:在Forcal脚本中无法定义运算符的重载,运算符重载是由设计该对象的程序员实现的。

对于C/C++或者Delphi程序员,或者只要使用的语言支持指针,都可以设计自己的对象加入Forcal系统,并为这些对象添加运算符重载功能。

以下是FcMath库中矩阵运算的例子,注意使用了运算符重载:

!using["math","sys"];
(:t0,k,i,a,b)=
{
t0=clock(),
oo{k=zeros(5,5)},
//生成5×5矩阵k,初始化为0
i=0,(i<1000).while{
//循环计算1000次
oo{
a=rand(5,7), b=rand(7,5),
//生成5×7矩阵a,7×5矩阵b,用0~1之间的随机数初始化
k.=k+a*b+a(0,4:1,5)*b(1,5:0,4)+a(neg:6)*b(3:neg)
//计算k=k+a*b+a(0,4:1,5)*b(1,5:0,4)+a(neg:6)*b(3:neg)
},
i++
},
k.outm(),
//输出矩阵k,然后销毁k
[clock()-t0]/1000
//得到计算时间,秒
};

11 动态内存管理 [返回页首]

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)垃圾回收是有运行开销的,但如果用户有效地管理了内存,垃圾回收器可以一次也不启动。

例如以下例子:

//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
};

12 错误处理 [返回页首]

错误有编译错误和运行错误两种。通常,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()清除运行错误,重启程序代码,通常需借助循环完成该处理过程。

13 创建任意的对象 [返回页首]

在Forcal核心库中,只有整数、实数和复数三种简单的数据,以及针对这三种简单数据进行运算的整数表达式、实数表达式和复数表达式。虽然在表达式中可以使用静态数组,但静态数组是通过字符串来定义的,故不是严格意义上的对象。

Forcal核心库没有任何内建对象,甚至连动态数组也不支持,但Forcal核心库为创建任意对象提供了良好的支持。C/C++、Delphi等程序员可以设计自己的对象及操作这些对象的函数,并把它们注册到Forcal中。“自己动手,丰衣足食”是使用Forcal的基本法则。

FcData是一个Forcal扩展库,提供了动态数组、类等丰富的数据类型,并支持通过FcData向Forcal注册自己的数据类型,许多Forcal扩展库在提供自己的数据类型时,也以FcData库作为基础。

例如扩展库FcString(以FcData库为基础)中提供了动态字符串对象,函数ws用来生成一个字符串对象,函数outs用来输出一个字符串对象,下例计算并输出了三个字符串的和:

!using["string"];
oo{[ws("ab")+ws("cd")+ws("ef")].outs()};

再例如扩展库FcMath(以FcData库为基础)中提供了矩阵对象,函数matrix用来生成一个矩阵并可进行初始化,函数outm用来输出一个矩阵,下例计算并输出了两个矩阵的乘积:

!using["math"];
oo{[matrix(2,3:1,2,3,4,5,6)*matrix(3,2:1,2,3,4,5,6)].outm()};

注意FcString库中的字符串对象和FcMath库中的矩阵对象都是运算符重载的。设计对象及操作函数,对对象进行运算符重载等等,都是由程序员来实现的。“自己动手,丰衣足食”是使用Forcal的基本法则。

14 类模块 [返回页首]

著名的瑞士计算机科学家沃思(N.Wirth)教授曾提出:算法+数据结构=程序。在Forcal中,算法是用函数来描述的,而模块是函数的集合,因而Forcal的模块即代表了算法。Forcal模块由Forcal核心库来实现,但Forcal的数据结构却是由Forcal扩展动态库(特别是FcData)实现的。在FcData中主要通过类的概念实现Forcal的数据结构,所有的FcData数据都通过指针进行标识。

在C++中通过类实现了面向对象设计,在Forcal中与此相关的概念是类模块(FcData中的类与Forcal模块的结合)。与C++中的类定义不同,FcData中的类与Forcal模块是相互独立的。C++是高效的静态语言,类函数必须知道类的结构才能工作,而Forcal是动态编译的,在运行前无需也不可能知道类的结构,因而也没有必要将类结构与模块函数绑定到一起。因而有理由认为:FcData中的类与Forcal模块相互独立是Forcal的一个优点。

Forcal模块命名空间通常是一棵树或者是一张连通图。

FcData类通常是一棵树或者是一张连通图。

15 轻量级计算引擎 [返回页首]

Forcal是轻量级绿色免安装的计算引擎。核心库FORCAL32W.DLL文件大小:约130K;不使用MSVC运行库的静态库版本,约260K~300K。

16 编译运行效率 [返回页首]

对脚本来说,编译效率和运行效率同样重要,Forcal在这两方面都有不俗表现,Forcal是追求效率的。

Forcal一级函数速度约为FORTRAN(或C/C++)执行速度的50%左右;其他情况,速度稍有下降。

以下Fibonacci递归程序(n取40)体现了Forcal脚本的运行效率:

SetRealStackMax(1000);
F(n)= which{
n == 0,
return(0),
n == 1,
return(1),
return [F(n - 1) + F(n - 2)]
};
main(:t,n)=
{
t=sys::clock(),
n=F(40),
t=sys::clock()-t,
printff{"\r\nfibonacci={1,i}, fibonacci_Time={2,i}毫秒={3,i}秒\r\n",n,t,t/1000}
};

结果:

fibonacci=102334155, fibonacci_Time=41859毫秒=41秒

17 形式的优势 [返回页首]

动态库(DLL)是一个包含可由多个程序同时使用的代码和数据的库,具有以下优点:(1)扩展了应用程序的特性;(2)可以用许多种编程语言来编写;(3)简化了软件项目的管理;(4)有助于节省内存;(5)有助于资源共享;(6)有助于应用程序的本地化;(7)有助于解决平台差异;(8)可以用于一些特殊的目的。

Forcal以动态链接库的形式存在,用Win32标准函数调用方式(stdcall调用协议)输出了动态库函数,使用极为方便。

核心库 Forcal 9.0 的输出函数有30多个,主要功能有表达式编译计算、错误处理、常量或函数注册、判断表达式是否有效、从表达式获得信息、向Forcal注册(对象)信息、垃圾收集等等。

18 无限的功能扩展 [返回页首]

Forcal的内核很小,除提供超强的表达式编译计算功能外,着重强化了对功能扩展的支持,用户可以很方便地将自定义的对象、函数、常量等注册到Forcal系统。

很容易对Forcal进行功能扩展,功能扩展的方式可以灵活多样,但通过动态库方式进行功能扩展是最常用的方式,这种方式便于被所有的Forcal支持的程序所共享。Forcal扩展动态库中仅有一个输出函数 FcDll32W(...),设计和应用都很方便。可用C/C++、delphi、FORTRAN等语言设计Forcal扩展动态库。

19 核心库和扩展库 [返回页首]

Forcal 9.0 的核心库是Forcal32W.dll,共有30多个输出函数,利用这些输出函数,可以设计功能丰富的各种Forcal扩展库。所有的扩展库都是根据核心库的输出函数设计的。

使用Forcal核心库的输出函数,用户可构建自己的处理系统。但作者也根据Forcal核心库的输出函数,构建了一套系统--以Forcal扩展动态库的形式对Forcal进行了功能扩展,FcData、MForcal、FcString、FcSystem等即是这套系统的重要组成部分;这套系统是以FcData和MForcal两个库为基础的,FcData提供了基本数据类型扩展功能,MForcal可对Forcal源程序进行模块化编译。如果这些Forcal扩展库能满足您的要求,您可以直接使用它们。

20 对私有数据的保护 [返回页首]

彻底地保护私有数据是Forcal的基本特色。

在Forcal脚本中,模块私有函数、模块变量等就体现了对私有数据的保护。

在设计Forcal扩展库(或其他形式的功能扩展模块)时,向Forcal注册的数据,有些只允许该扩展库自己的函数访问,而不允许其他外部函数访问,此时,可以私有键的方式向Forcal注册数据,这样其他功能扩展模块就无法访问这些数据,保证了扩展库的安全性。

在编译表达式时,可以对表达式所在的模块加锁;在向Forcal注册键值时,可以锁定键的类型。这些都体现了Forcal对私有数据的保护。

21 二级函数设计精要 [返回页首]

二级函数(外部函数)是Forcal功能扩展的基本方式,二级函数必须检测并报告所有可能出现的错误,以实现错误处理的恢复模型。

在FORCAL脚本中,函数的一般形式为:fun(x1,x2,...,xn)
与此相对应,FORCAL二级函数的通用形式为:double _stdcall fc_fun(fcINT nn,double *xx,void vFor);
参数说明:

nn:整数。nn指出自变量的个数。
xx:自变量数组的指针。若nn=-1,表示该函数没有自变量,若nn=0,表示有一个自变量,若nn=1,表示有2个自变量,... ...,依次类推。xx[i]即该函数的第i+1个自变量。
vFor:表达式句柄。通过该句柄可以获得表达式的详细信息。

根据函数所实现的功能,每一个自变量可以代表不同的意义,可以是一个双精度实数,也可以对它取整((int)xx[i])或使用部分字节表示其他意义,例如表示一个整数,或者表示一个指向不同对象的指针:数组、字符串、表达式或者其他任何一种对象类型。

复杂的二级函数设计必须注意以下几点:

(1)用GetForStr(...)函数获得表达式中的字符串,因为表达式的名称就是一个字符串。该函数只能在二级函数内部使用。

(2)用GetFor(...)函数获得各个表达式并进行计算。该函数只能在二级函数内部使用。

(3)用TestRunErr(void)获得动态库运行错误的类型。

(4)用SetRunErr(int ErrType,char *FunName,int FunCode,int ForType,void *ForHandle)设置外部函数的运行错误。

(5)通常不用GetRunErr(int &ErrType,char *&FunName,int &FunCode,int &ForType,void *&ForHandle)获取FORCAL运行错误,因为该函数将FORCAL设为无错状态。

(6)如果二级函数的参数中包含了对象,在对对象操作之前,必须验证其是否有效,并且要检查对象操作的条件是否具备。即:只有对象是有效的,且操作条件是完备的,才可以进行对象的操作,这样做到了万无一失。每个二级函数都要对自己操作的对象负责(也仅对自己操作的对象负责),这样整个Forcal系统就是安全的。

以下是Forcal标准输入输出系统扩展库FcIO中的二级函数feof的设计(功能相当于C语言中的文件操作函数feof),可以体会到二级函数设计的特点。

Forcal脚本中,函数feof的用法如下:

io::feof(hFile):判断是否到文件尾

hFile:文件句柄。
返回值:没有到达文件尾时返回0,否则返回非0值。

运行错误:(1)文件没有打开;(2)非法的文件句柄。

扩展库FcIO中函数feof的C/C++代码如下:

fcIFOR _stdcall ifc_feof(fcINT m,fcIFOR *xx,void *vFor)
{
static wchar_t ErrName[]=L"io::feof";
fcdTYPE BType;
fcVOID *file;

IsFcData(*xx,BType);
//获取参数的类型,文件类型数据已被注册为FcData数据
if(BType==IdFcFile)
//该参数是一个文件
{
file=(fcVOID *)*(fcVOID *)xx;
//取出文件指针
if(*file)
{
return feof((FILE *)*file);
}
else
{
if(TestRunErr()==0) SetRunErr(1,ErrName,1,0,vFor);
//文件没有打开
}
}
else
{
if(TestRunErr()==0) SetRunErr(1,ErrName,2,0,vFor);
//非法的文件句柄
}
return 0;
}

22 句点的用法 [返回页首]

若标识符后面有句点“.”,表示将该标识符联系到一个函数,例如: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[]

上式中,23max[]的参数,而max[2,3]5min[]的参数,即: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,...}函数中实现运算符重载功能。请参考每一个类的说明。


版权所有© 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 -->

分享到:
评论

相关推荐

    Forcal数据库应用

    让Forcal的控制台应用程序能够读取数据库中的表达式字符串并计算,你只要双击\ForcalConsole\Forcal数据库应用\ForcalConsole.exe文件,就可以完成创建数据库,读取数据库中的表达式字符串并运算的功能了。...

    forcal编程软件

    这个是forcal进行编程的工具,对于需要此程序的人,希望能提供帮助

    FORCAL V7.0 字符表达式编译运行库

    内容索引:VC/C++源码,控件相关,Forcal,编译 Forcal是一个对字符表达式进行编译计算的动态链接库,具有数值计算、关系运算、逻辑运算、字符处理、流程控制、函数调用等许多的可编程功能。它的部分开发文档是由C++...

    FORCAL数值计算扩展动态库FcIMSL V1.0测试版.pdf

    FORCAL数值计算扩展动态库FcIMSL V1.0测试版.pdf

    VC Forcal7调用例子.rar

    VC Forcal7调用例子,请在此输入表达式!一次只计算一个表达式!  可在多行中输入表达式。  如果有自变量,只进行编译,但将保留编译结果,自定义的函数随时可以调用。  重新初始化将清除以前的编译结果。  ...

    通用的数学表达式编译计算动态库 V5.1

    令人欣喜的是,现在有了FORCAL.DLL,您可以在软件中自由地添加各种数值计算功能,享受到一劳永逸之乐趣。 该动态库支持实数、复数和整数三种类型的数学表达式,这三种表达式可以单独使用,也可同时使用,在同时使用...

    开放式计算程序OpenFC

     OpenFC是一个开放式的数值计算程序,由Forcal32.dll和MForcal32.dll提供支持。  OpenFC能够编译运行具有固定格式的源程序(字符串表达式),源程序中可以使用C++风格的注释。源程序中可以使用的运算符有+、-、*、...

    数学计算系统.rar

    数学和工程计算平台,采用forcal语言和内核,内置有丰富的范例可供参考,可广泛应用于数学建模,工程设计与计算,作图,程式设计等等,计算速度快而准确,让你解决问题不再感觉困难。

Global site tag (gtag.js) - Google Analytics