Lu32.DLL V1.0 用户指南(测试版)
1 概述 2 简单的例子 3 标识符 4 表达式简介 5 数据类型 6 常量和变量 7 赋值语句 8 算术运算 9 关系运算符和逻辑运算符 10 逗号(冒号、分号)运算符和括号运算符 11 运算符及优先级 12 函数概述 13 函数的传值调用和传址调用 14 用表达式作为函数的参数 15 表达式的完整定义及表达式句柄 16 流程控制 17 字符串 18 数组 | 19 Lu表 20 动态对象与指针 21 对象成员运算符(函数参数运算符)及对象赋值运算符 22 递归调用 23 模块化编译 24 模块命名空间 25 二级函数命名空间 26 常量命名空间 27 访问命名空间 28 二级函数 29 运算符及函数重载 30 动态内存管理 31 表达式的初始化及销毁 32 错误处理 33 标识符解释规则 34 关键字 35 效率 36 主要技术指标 |
正文:
Lu来源于Forcal,可以说,没有Forcal就没有Lu,但学习Lu并不需要了解Forcal。
Lu是对Forcal的完善和发展,但与Forcal相比,Lu更简洁实用。Lu的运行效率,从单纯的数值计算和循环来说,是下降的,其他方面,特别在涉及动态对象的地方,Lu的效率会提高。Lu与Forcal本质的区别在于:Forcal以整数、实数、复数三种简单数据类型为基础,在描述复杂对象时,本身不带有数据类型信息,故是弱类型的;Lu以一种结构体作为基本数据类型,携带数据类型信息,故是强类型的。
Lu,路也。路连接城市,Lu用于连接各种语言。
Lu是一个可对字符串表达式进行动态编译和运行的动态链接库(dll),是一种易于扩展的轻量级嵌入式脚本,提供自动内存管理,也可以手动管理内存。Lu的优势在于简单易用和可扩展性强。
Lu语法由核心库(Lu32.dll)、扩展库及主程序提供。本文主要介绍核心库提供的语法,非核心库语法部分将会特别说明(标记为红色部分)。
可以用任何一个加载Lu核心库的程序演示本文的代码,因不同程序对Lu语法的扩展不同,故非核心库语法部分不能保证一定运行成功,请参考程序的说明。
推荐的演示程序:(1)简单演示程序 DemoLu32.exe;(2)开放式演示程序 OpenLu32.exe。
F(x,y)=x+y; //函数定义
2+F[2,3]+5; //简单的计算式
在这个例子中,分号表示一个表达式的结束,两个反斜杠//后的内容表示注释,本文中将一直采用这种表示和注释方法。
在第一个表达式中定义了一个函数 F ,小括号( )内的x,y为函数的自变量,等号后为函数体,即可执行的部分,该函数计算并返回两个参数的和。在第二个表达式中调用了上面定义的函数 F 。
需要注意,在一般的Lu程序中,只计算无自变量的表达式。因此,对第一个表达式只编译,不计算。
在Lu中,一个标识符可以用来表示一个表达式的名字,或者是一个函数的名字,或者是一个变量,或者是一个符号常量。标识符可由一个或多个字符组成,可以任意一个英文字母、中文字符或者下划线开头,后面可以接英文字母、中文字符、数字或者下划线(下划线的使用可以增加标识符的可读性,如first_name)。注意:英文字母的大写和小写是不同的,例如:count和COUNT是两个不同的名字。下面给出一些合法的标识符:
first last Addr1 top _of_file name23 _temp a23e3 MyVar 您好 数1 My数
表达式是Lu的编译单元,如果没有语法错误,编译后将生成可解释执行的中间代码。
Lu表达式可以很简单,例如只有一个数字;Lu表达式也可以很复杂,例如是一段复杂的程序。以下是一些简单的表达式例子:
2;
2+3;
2.0+sin(3.0)-ln[6.0];
Lu表达式有无名表达式和有名表达式两种,有名表达式即函数,如下例:
A()=2;
B()=2+3;
C()=2.0+sin(3.0)-ln[6.0];
给表达式起一个名字,使之成为函数,这样做的好处是:以后可以用函数调用的方式通过该名字调用该表达式。表达式的名字是一个标识符,必须位于表达式的开头。
表达式(函数)定义的一般形式是:
Name(a,b)={a=2,b=10:b=b+a;a+b}
其中 Name 为表达式的名字,不可与其它已经定义的表达式名字相同,但可以缺省;如果定义了表达式名字,表达式名字后必须跟一对小括号(只能用小括号),用来定义自变量,自变量个数可以为零,也可以有任意多个,有多个自变量时,自变量间以逗号分隔;如果用小括号定义了自变量,小括号后必须跟一个等号,该等号标志表达式可执行部分的开始,等号及等号后的可执行部分均不可缺省。表达式的可执行部分由多个语句组成,多个语句之间用逗号、冒号或分号分隔,表达式总是返回最后一个语句的值;另外花括号不是必须的,但有了花括号后,表达式更易读。
下面是一个函数定义及使用的例子。
相加(加数1,加数2)=加数1+加数2; //函数定义
2+相加[2,3]+5; //函数调用
以下是一些合法的表达式例子:
2; //常量(无参)表达式
()=2; //常量(无参)表达式
A()=2; //常量(无参)表达式(函数),在其它表达式中可以调用该函数
B(x)=2; //有一个自变量的表达式(函数)
C(x,y)=x+y; //有二个自变量的表达式(函数)
Lu数据类型如下表所示:
表5-1 Lu数据类型
类别 | 基本数据类型 | 标识符 | 例子1 | 例子2 | 例子3 | 说 明 |
静态类型 | nil | nil | nil | 未定义的Lu数据或某个运算失败的返回值。 | ||
表达式(函数)句柄 | @fun | HFor("fun") | 由编译符@或函数HFor获得。 | |||
64位整数 | int | 12 | 0x1A | 0x123D | 64位整数(范围从-9223372036854775808~9223372036854775807),既可以是10进制数,也可以是16进制数,但数字中不能包含小数点,也不能用科学记数法表示数字。16进制整数以0x开头,并用A~F表示10~16。 | |
64位双精度实数 | real | 1.2 | 3. | 10.3E8 | 64位双精度实数(范围大致从±1.7E-308~±1.7E+308),含小数点,可以用科学记数法表示数字。 | |
复数 | complex | 2i | 1+2i | (2$3) | 复数(实部和虚部都是64位双精度实数),以i结尾的数字,或者由运算符$产生。 | |
三维向量 | vector | (1$2$3) | [1$(2$3)] | (2i$3) | 三维向量(三个分量都是64位双精度实数),由运算符$产生。 | |
长精度实数 | 未启用。 | |||||
逻辑值 | true / false | true | false | 2>3 | 逻辑值,只有逻辑真和逻辑假两个值,关系运算或逻辑运算将返回逻辑值。 | |
特殊常量 | all | all | 由系统定义,直接在脚本中使用,其意义由使用这些常量的函数解释。 | |||
data | data | |||||
public | public | |||||
private | private | |||||
protected | protected | |||||
virtual | virtual | |||||
row | row | |||||
rank | rank | |||||
inf / INF | inf | INF | ||||
nan / NaN | nan | NaN | ||||
self | self | |||||
静态字符串 | "..." | @"..." | @"\[22]" | 静态字符串。 | ||
静态64位一维整数数组 | intss("...") | intss("\&\[10]") | 静态64位一维整数数组。 | |||
静态64位一维实数数组 | realss("...") | realss("\&\[10]") | 静态64位一维实数数组。 | |||
系统定义动态类型 | ||||||
类 | class | 未启用。 | ||||
动态64位实数数组 | reals | new[reals,5] | new[reals,2,5] | new[reals,data:2.0,3.6] | 动态64位实数数组。可以是多维数组。 | |
动态64位整数数组 | ints | new[ints,5] | new[ints,2,5] | new[ints,data:2,3] | 动态64位整数数组。可以是多维数组。 | |
动态字符串 | string | new[string,5] | new[string,5,"..."] | new[string,"..."] | 动态字符串。 | |
动态Lu数据 | lu | lu[2,3.5,"..."] | lu{2,lu[3.5,"..."]} | new[lu,5:2,3.5,"..."] | 动态Lu表。可以存放任意类型的数据。 | |
外部扩展动态类型 | 私有动态数据类型 | 由扩展用户提供 | 用户自定义的私有动态数据类型。 | |||
公有动态数据类型 | 由扩展用户提供 | 用户自定义的公有动态数据类型。 |
核心库对Lu数据进行的运算符支持参考运算符及优先级。
在Lu表达式中各种数值数据进行混合运算时,其隐式转换规则如下表:
表5-2 混合运算隐式转换规则
数据类型 | 整数 | 实数 | 复数 | 三维向量 | 长实数 |
整数 | 整数 | 实数 | 复数 | 长实数 | |
实数 | 实数 | 实数 | 复数 | 长实数 | |
复数 | 复数 | 复数 | 复数 | ||
三维向量 | 三维向量 | ||||
长实数 | 长实数 | 长实数 | 长实数 |
常量是指那些不需要程序计算的固定值。有数值常量和符号常量两种。如100就是一个数值常量。在Lu中,也可以用标识符表示一个符号常量。用函数const可以定义一个永久性符号常量或暂时性符号常量,但只有const函数被执行后,定义的常量才会起作用。如下例:
const("aa",111); //定义永久性常量,不可删除
const("bb",222,true);//定义暂时性常量,可以删除
编译运行以上表达式,然后输入下例代码并执行:
aa; //aa=111
bb; //bb=222
const("aa",0,false);//试图删除永久性常量,但失败了
const("bb",0,false);//删除暂时性常量
有些软件会利用Lu的输出函数定义一些符号常量,但在这里无法给出具体的例子。
变量是指在程序运行时,其值可以改变的量。Lu有五种变量,即:自变量、动态变量、静态变量、模块变量和全局变量。自变量、动态变量和静态变量只能被定义该变量的表达式所访问;模块变量可被同一模块的所有表达式所访问,其他模块的表达式无法访问;全局变量可被所有的表达式所访问。自变量用于向表达式传递参数,因此自变量也称为形式参数。动态变量只在表达式执行时起作用,一旦表达式执行完毕,动态变量也随之失效。静态变量存在于表达式的整个生命周期,每次表达式执行完毕,静态变量的值被保留。Lu在编译表达式时,自变量不进行初始化;动态变量初始化为nil;静态变量初始化为0;模块变量和全局变量在第一次生成时初始化为nil,以后使用不再进行初始化。
如果表达式中的变量名与一个常量名相同,则常量名被忽略。
在Lu表达式中,通常情况下,变量要先定义后使用,变量在表达式的开头进行定义,格式如下:
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只能用在第二个冒号后。Lu中的所有变量均可缺省。
以下都是合法的变量定义的例子:
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;
可以在Lu表达式使用未定义的模块变量。在MLu(MLu是一个Lu扩展动态库,可对源代码进行模块化编译)中,可使用编译符mvar:通知编译器使用未定义的模块变量,使用编译符unmvar:通知编译器取消这种设置,格式如下:
mvar: //通知编译器使用未定义的模块变量
s,t,u=5,v=6; //如果不存在同名的常量,就解释为模块变量
s=1, 2+s+u; //可以正确编译,返回值=8
unmvar: //通知编译器取消使用未定义的模块变量
2+s+u; //编译出错,变量不可识别
若要确保编译器将未定义的标识符解释为模块变量,则应在前面编译的表达式中,将该标识符明确地定义为模块变量并至少使用一次,如下例:
!const["v",66,true]; //创建一个常量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
赋值语句的作用是将一个值赋给一个变量。在Lu中,可以用等号对变量进行赋值。例如:
F(x)= x=5; //函数定义,将数值5赋给变量x;
F[2]; //函数调用,返回值为5;
上面进行变量赋值的例子很简单,但等号后部分可以是任意复杂的表达式。
对象及对象成员的赋值用“.=”并最终通过函数实现,参考:对象成员运算符(函数参数运算符 )及对象赋值运算符。
常用的算术运算符有:+(加或正)、-(减或负)、*(乘)、/(除)、%(求模)、^(乘方)、++(自增)、--(自减)等等。
运算符*、/、%和^是双目运算符。注意数字与变量相乘时,乘号不可省略;在进行乘方运算时,底数应为非负数。
运算符+、-作加、减运算时,是二元运算符,当作正、负运算时,是单目运算符。
运算符++、--是单目运算符,仅能对变量使用。++使变量的值加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;
单目运算符的优先级比双目运算符的优先级高,后置单目运算符的优先级比前置单目运算符的优先级高。对于同一优先级的运算,按从左到右的优先顺序进行。
注意:单目运算符-(负)与双目运算符^(乘方)需用括号区分计算的先后顺序。例如:
(-2)^2; //返回值为4;
-(2^2); //返回值为-4;
-2^2; //编译出错;
-(2+3)^2; //编译出错;
-sin(2.+3)^2;//编译出错;
算术运算符的优先级如表8-1所示。在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。
表8-1:常用算术运算符及优先级
运 算 符 | 说 明 |
++、-- | 后置单目运算符,自增减运算符 |
+、-、++、-- | 前置单目运算符,“++、--”为自增减运算符 |
^ | 乘方 |
*、/、% | 乘、除、求模 |
+、- | 加、减 |
9 关系运算符和逻辑运算符 [返回页首] [返回目录]
关系运算是对两个值的大小进行比较,返回一个逻辑值。逻辑值只有两个:逻辑真true和逻辑假false。在Lu中用0表示逻辑假,其他任何非0值表示逻辑真。例如:
3>2; //返回true;
2>3; //返回false;
关系运算符共有6个:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于)。
逻辑值之间的运算称逻辑运算,逻辑运算的结果仍然是一个逻辑值。有四个逻辑运算符:&(逻辑与)、|(逻辑或)、!(逻辑非)、~(逻辑异或)。通过表9-1给出的真值表可以掌握这几种运算。表中用1代表逻辑真,0代表逻辑假。
表9-1 真值表
p | q | p&q | p|q | !p | p~q |
0 | 0 | 0 | 0 | 1 | 1 |
0 | 1 | 0 | 1 | 1 | 0 |
1 | 1 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 | 0 |
关系运算符和逻辑运算符的优先级如表9-2所示。在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。
表9-2 关系运算符和逻辑运算符及优先级
运 算 符 | 说 明 |
! | 逻辑非 |
>、>=、<、<=、==、!= | 关系运算符 |
& | 逻辑与 |
~ | 逻辑异或 |
| | 逻辑或 |
10 逗号(冒号、分号)运算符和括号运算符 [返回页首] [返回目录]
表达式中如果有多个语句,可以用逗号、冒号或分号进行分隔,Lu将按从左到右的顺序计算各个语句,并返回最后一个语句的值。也可以将多个用逗号、冒号或分号分隔的语句放在一个括号内,Lu也将按从左到右的顺序计算各个语句,并返回最后一个语句的值。例如:
(: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;
虽然逗号、冒号或分号运算符是通用的,但逗号的用法很常见。冒号和分号运算符仅用在一些特殊场合,例如分隔一些不同类别的变量。
注意:一般表达式之间用分号进行分隔,故分号运算符必须放到括号内才有效。另外,不是所有的Lu程序都支持分号运算符。由模块化编译运行库MLu支持的程序都支持分号运算符,如下例:
(: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;
Lu中的运算符及优先级如表11-1所示,在表中,同一类型的运算符优先级相同,不同类型运算符的优先级从上往下依次降低。
表11-1:Lu运算符及优先级
运算符类型 | 运算符 | 名称 | 是否允许重载 | 核心库支持的运算 | 说 明 |
双括号连接运算符 | := | 双括号连接 | 冒号前和等号后都必须是括号 | ||
单括号连接运算符 | ( )= | 单括号连接 | 等号后是一个表达式 | ||
[ ]= | 单括号连接 | ||||
{ }= | 单括号连接 | ||||
括号运算符 | ( ) | 小括号 | 括号运算 | 返回最后一个语句的值 | |
[ ] | 中括号 | 括号运算 | |||
{ } | 大括号 | 括号运算 | |||
命名空间成员访问符 | :: | 双冒号 | 访问命名空间成员 | 访问命名空间成员 | |
对象成员运算符 | . | 点 | 访问对象成员或传递函数参数 | 也称为函数参数运算符,或者变量函数调用运算符 | |
后置单目运算符 | ++ | 后置自增 | 是 | 整数、实数 | 后置单目运算符(自增、自减、转置、点转置) |
-- | 后置自减 | 是 | 整数、实数 | ||
' | 转置 | 是 | 未定义 | ||
.' | 点转置 | 是 | 未定义 | ||
前置单目运算符 | ! | 非 | 是 | 逻辑值、整数、实数 | 前置单目运算符(非、正、负、自增、自减、按位非) 对整数或实数求非时,返回逻辑值,且规定!0=true,!0.0=true,其他情况均返回false。 |
+ | 正 | 被忽略 | |||
- | 负 | 是 | 整数、实数、复数、三维向量 | ||
++ | 前置自增 | 是 | 整数、实数 | ||
-- | 前置自减 | 是 | 整数、实数 | ||
!! | 按位非 | 是 | 整数 | ||
乘方算术运算符 | ^ | 乘方 | 是 | 整数、实数、复数 | 算术运算符(乘方、点乘方) |
.^ | 点乘方 | 是 | 未定义 | ||
乘除算术运算符 | * | 乘 | 是 | 整数、实数、复数、三维向量 | 算术运算符(乘、左除、右除、求模、点乘、点左除、点右除) |
/ | 左除 | 是 | 整数、实数、复数 | ||
\ | 右除 | 是 | 未定义 | ||
% | 求模 | 是 | 整数 | ||
.* | 点乘 | 是 | 未定义 | ||
./ | 点左除 | 是 | 未定义 | ||
.\ | 点右除 | 是 | 未定义 | ||
加减算术运算符 | + | 加 | 是 | 整数、实数、复数、三维向量、字符串 | 算术运算符(加、减) |
- | 减 | 是 | 整数、实数、复数、三维向量 | ||
移位运算符 | << | 左移位 | 是 | 整数 | 左移位、右移位 |
>> | 右移位 | 是 | 整数 | ||
关系运算符 | > | 大于 | 是 | 整数、实数 | 关系运算符(大于、大于等于、小于、小于等于、等于、不等于) |
>= | 大于等于 | 是 | 整数、实数 | ||
< | 小于 | 是 | 整数、实数 | ||
<= | 小于等于 | 是 | 整数、实数 | ||
== | 等于 | 是 | 整数、实数 | ||
!= | 不等于 | 是 | 整数、实数 | ||
按位与 | && | 按位与 | 是 | 整数 | 按位与 |
按位异或 | ~~ | 按位异或 | 是 | 整数 | 按位异或 |
按位或 | || | 按位或 | 是 | 整数 | 按位或 |
逻辑与 | & | 逻辑与 | 是 | 逻辑值 | 逻辑与 |
逻辑异或 | ~ | 逻辑异或 | 是 | 逻辑值 | 逻辑异或 |
逻辑或 | | | 逻辑或 | 是 | 逻辑值 | 逻辑或 |
并 | $ | 并 | 是 | 整数、实数、复数 | 并。核心库并运算的结果为复数或三维向量。 |
赋值运算符 | = | 赋值 | 赋值 | 赋值运算符 | |
对象赋值运算符 | .= | 对象赋值 | 对象赋值 | 变量函数调用运算符与赋值运算符的结合,一般用于对象赋值 | |
语句分隔符 | , | 逗号 | 分隔语句 | 逗号、冒号、分号运算符 | |
: | 冒号 | 分隔语句 | |||
; | 分号 | 分隔语句 |
括号连接运算符应用举例:
(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]
注意在Lu中,通常运算符不能连用,例如 !-2 是错误的,应当添加括号分隔开各个运算符,即应当写成 !(-2) 形式。
还有一个Lu运算符在表11-1中没有列出:&(取变量的地址)。取地址运算符&只能用于(被逗号或冒号隔开的)单独的变量,与其他运算符不发生任何关系,所以这个运算符在表11-1中没有列出,其用法我们将在下面详细介绍。
对象成员运算符(函数参数运算符)“.”、对象赋值运算符“.=”及命名空间成员访问符“::”也将在下面详细介绍。
函数是Lu最为重要的特性。一个函数通过传递给它的参数完成一个特定的功能。函数都有一个名字,可通过调用函数名并传递参数来使用函数。所有的函数都将返回一个函数值。典型的函数调用方法如下:
函数名(参数1,参数2,... ...)
函数可以有零个或多个参数,但即便有零个参数,函数名后的括号也不能缺省。函数调用时,参数一定要匹配。
在Lu中可以使用的函数有三种:一级函数、二级函数和自定义函数(表达式)。其中一级函数为系统内置的函数,运算速度快;二级函数部分为系统内置的函数,部分为外部扩充的函数,功能非常丰富;自定义函数实际上就是一个有名表达式,由用户定义。
实际上,Lu还有一类为数不多的函数,称为流程控制函数,不过对于它们,函数的意义并不明显,更多的是流程控制的意义,因此我们不把它们包括在上面的分类中。
Lu中的一级函数见表12-1。
表12-1:一级函数
函 数 | 参数类型 | 是否允许重载 | 说 明 |
global(p) | 局部动态对象 /true/false | 否 | 将局部动态对象p转换为全局动态对象。另外,在global(true),... ...,global(false)之间生成的动态对象都为全局动态对象。参考动态对象与指针。 |
local(p) | 全局动态对象 | 否 | 将全局动态对象p转换为局部动态对象。参考动态对象与指针。 |
sin(x) | 实数、复数 | 是 | 正弦函数 |
cos(x) | 实数、复数 | 是 | 余弦函数 |
tan(x) | 实数、复数 | 是 | 正切函数 |
asin(x) | 实数、复数 | 是 | 反正弦函数 |
acos(x) | 实数、复数 | 是 | 反余弦函数 |
atan(x) | 实数、复数 | 是 | 反正切函数 |
sqrt(x) | 实数、复数 | 是 | 平方根函数 |
exp(x) | 实数、复数 | 是 | 指数函数 |
ln(x) | 实数、复数 | 是 | 自然对数函数 |
lg(x) | 实数、复数 | 是 | 常用对数函数 |
sinh(x) | 实数、复数 | 是 | 双曲正弦函数,[exp(x)-exp(-x)]/2 |
cosh(x) | 实数、复数 | 是 | 双曲余弦函数,[exp(x)+exp(-x)]/2 |
tanh(x) | 实数、复数 | 是 | 双曲正切函数,[exp(x)-exp(-x)]/[exp(x)+exp(-x)] |
abs(x) | 实数、复数 | 是 | 绝对值函数 |
floor(x) | 实数 | 是 | 返回不大于x的最大整数 |
ceil(x) | 实数 | 是 | 返回不小于x的最小整数 |
itor(x) | 整数 | 是 | 将整数转换成实数,大数转换时有精度损失 |
rtoi(x) | 实数 | 是 | 将实数转换成整数,大数转换时有误差 |
con(x) | 复数 | 是 | 求复数的共轭复数 |
atan2(x,y) | 实数 | 是 | 反正切函数,求x/y的反正切值,所在象限由x和y的符号确定 |
fmod(x,y) | 实数 | 是 | 求x/y的余数 |
13 函数的传值调用和传址调用 [返回页首] [返回目录]
传值调用是把变量值拷贝到被调函数的形式参数中,函数在执行时,参数的改变不会影响到调用函数时所用的变量。
传址调用(也叫引用调用)是把变量的地址拷贝到被调函数的形式参数中,函数在执行时,参数的改变会影响到调用函数时所用的变量。
通常,Lu是按传值调用的,除非你用取地址运算符&(也叫引用运算符)显示地通知Lu编译器,要按传址方式使用参数。在使用运算符&时,&只能放到(用逗号、冒号或分号隔开的)单独存在的变量的前面。如下列:
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;
14 用表达式作为函数的参数 [返回页首] [返回目录]
在函数调用时,有时候需要用表达式(即自定义函数)作为函数的参数。
14.1 用编译符@获得表达式句柄(也称为表达式指针、函数指针、函数句柄)传给函数
例子:
aa(x)=x+8;//定义一元函数aa
bb(f,x)= f(x)+2;//定义函数bb,接受一个一元函数句柄f
bb[@aa,2]; //调用函数bb
14.2 用二级函数HFor("ForName")获得表达式句柄(也称为表达式指针、函数指针、函数句柄)传给函数
例子:
aa(x)=x+8;//定义一元函数aa
bb(f,x)= f(x)+2;//定义函数bb,接受一个一元函数句柄f
bb[HFor("aa"),2];//调用函数bb
14.3 用表达式的名称作为字符串(两个双引号"..."之间的内容为一个字符串)传给函数
例子:核心库中没有例子。
一般,在函数的说明中会指定需传递的表达式参数的类型,或者传递表达式的句柄,或者传递字符串形式的表达式名称。
15 表达式的完整定义及表达式句柄 [返回页首] [返回目录]
在这里,我们总结性地给出Lu表达式的完整定义,以帮助用户更好地理解Lu,为了定义的完整性,部分地重复了前面所叙述过的内容。Lu表达式的完整定义如下:
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是表达式的名字,必须位于表达式的开头,该名字必须是一个标识符。给表达式起一个名字,主要是以后可以通过该名字来调用该表达式。表达式也可以没有名字,但这样,在其它的表达式中将无法调用它。在Lu中,一般表达式的名字是唯一的,不能给两个表达式起同一个名字(模块内表达式的名字除外,模块的定义将在后面介绍)。
如果定义了表达式名字,表达式名字后必须跟一对小括号(只能用小括号),用来定义变量。变量个数可以为零,也可以有任意多个。有多个变量时,变量间以逗号或冒号分隔。用冒号隔开的变量,从前往后依次为自变量、动态变量、静态变量、模块变量和全局变量,即第一个冒号前为自变量,两个冒号之间为动态变量和静态变量,第二个冒号后为模块变量和全局变量。两个冒号之间用关键字static分隔动态变量和静态变量,static之前为动态变量,static及以后变量均为静态变量,关键字static只能用在两个冒号之间。第二个冒号后用关键字common分隔模块变量和全局变量,common之前为模块变量,common及以后变量均为全局变量,关键字common只能用在第二个冒号后。所有变量以及冒号均可缺省。
在这个例子中,a和b是自变量,x和y是动态变量,static、u、free和v是静态变量,s和t是模块变量,common和w是全局变量。
自变量、动态变量和静态变量只能被定义该变量的表达式所访问;模块变量可被同一模块的所有表达式所访问,其他模块的表达式无法访问;全局变量可被所有的表达式所访问。自变量用于向表达式传递参数,因此自变量也称为形式参数。动态变量只在表达式执行时起作用,一旦表达式执行完毕,动态变量也随之失效。静态变量存在于表达式的整个生命周期,每次表达式执行完毕,静态变量的值被保留。
Lu在编译表达式时,自变量不进行初始化;动态变量初始化为nil;静态变量初始化为0;模块变量和全局变量在第一次生成时初始化为nil,以后使用不再进行初始化。
如果定义了静态变量free,Lu在销毁表达式前将自动设置free为1,然后自动执行表达式。
即便表达式没有名字,也可以用一对小括号(只能用小括号)来定义变量,这时,小括号必须位于表达式的开头。
如果用小括号定义了变量,小括号后必须跟一个等号,该等号标志表达式可执行部分的开始,等号及等号后的可执行部分均不可缺省。
如果表达式中的变量名与一个常量名相同,则常量名被忽略。
表达式中可以不定义变量,这时,表达式中只有可执行部分,称常量表达式,或者称无参表达式。
表达式的可执行部分任何情况下都不可缺省。
表达式的可执行部分由多个语句组成,多个语句之间用逗号、冒号或分号分隔,多个语句可以放在一对括号内。可执行部分中可以使用三对括号( )、[ ]和{ },括号必须成对使用。在Lu中,括号是一种运算符,具有一个值,即该括号内最后一个语句的值。表达式总是返回(最外层括号中)最后一个语句的值。另外,最外层的括号不是必须的,但表达式若有逗号、冒号或分号隔开的多个语句,有了最外层的括号后,表达式更易读。
用这种方式定义表达式时,实际上就是自定义了一个函数,可以在任意的表达式中使用该函数。
以下都是合法的表达式定义的例子:
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;
Lu表达式可用句柄(也称为表达式指针、函数指针、函数句柄)进行标识,可以使用编译符@或二级函数HFor("ForName")获得表达式的句柄。用编译符@获得函数句柄是在编译期进行的,而用二级函数HFor("ForName")获得表函数句柄是在运行期进行。
在Lu中,表达式的各个语句一般是顺序执行的。但是某些函数可以改变语句执行的顺序,称为流程控制函数。
16.1 立即返回函数 return(x)
结束计算并立即返回表达式的值为x。
16.2 判断函数 if(x,y1,y2,... ...,yn)
当逻辑语句x的值为真(或者对象x值非0)时,依次执行语句y1,y2,... ...,yn,否则,不执行语句y1,y2,... ...,yn。
该函数至少要有2个自变量参数,其中第一个参数为逻辑语句。
该函数的返回值无意义。
16.3 自定义分段函数
which{
逻辑语句1 : 语句1,
逻辑语句2 : 语句2,
... ...,
逻辑语句n : 语句n,
缺省语句
};
Lu从前往后计算并检测逻辑语句的值,当检测到逻辑真(或者对象值非0)时,计算与此逻辑真对应的语句并返回该语句的值,如果没有检测到逻辑真,则计算缺省语句的值作为返回值,若此时没有缺省语句,则产生一个运行错误(错误代码为0)。
该函数至少要有2个自变量参数。
例如下式定义了一个分段函数:
(x)=which{x>0, 2*x-1,
x*x-1
};
如果舍弃该函数的返回值,则该函数可以作为一个选择计算函数使用。
16.4 while循环函数
while循环是“当型”循环,其特点是:先判断条件是否成立,当条件成立时,则执行循环体,否则退出循环体,即“当条件成立时执行循环”。“当型”循环的循环体有可能一次也不执行。
while循环函数的格式如下:
while{x,
y1,y2,
...,
break(),
...,
continue(),
...,
yn
};
其中x为逻辑语句;y1,y2,...,break(),...,continue(), ...yn为循环体语句。当x的值为真(或者对象x值非0)时,依次执行循环体语句,直到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
};
16.5 until循环函数
until循环是“直到型”循环,其特点是:先执行循环体,再判断条件是否成立,当条件成立时,退出循环体,否则继续执行循环体,即“执行循环直到条件成立”。“直到型”循环的循环体至少执行一次。
until循环函数的格式如下:
until{x1,x2,
...,
break(),
...,
continue(),
...,
y
};
until为先执行后判断的循环函数。即先执行循环体语句x1,x2,...,break(),...,continue(),...,然后计算逻辑语句y的值,直到y的值为真(或者对象y值非0)时退出循环。当执行到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)Lu支持多线程,在多线程的程序中,如果不慎进入了无限循环,可以通过另一个线程退出。
在Lu中使用Unicode宽字符串,一个宽字符有2个字节,用两个双引号定义一个字符串,即"..."表示一个字符串。在Lu字符串中,还可以用反斜杠转义字符输入一些特殊字符,见表17-1。
表17-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,这种字符串格式可转换为整数、实数或复数数组。例如: 任意表达式中,"\[256]" 是一个256个字符长的空格符字符串。"\&\[256]" 是一个256×4个字符长的空格符字符串。 |
除了表中定义的之外,Lu没有定义其它的转义字符(“\”和其他字符的组合都是非法的)。可以看出,反斜杠“\”和双引号“"”只能通过转义字符输入。
另外,若字符串前有符号@(例如:@"...\r\n..."),将忽略转义字符。
例如:
o("hello!\r\n字符串!!!"); //用o函数输出字符串;
o(@"hello!\r\n字符串!!!");//用o函数输出字符串;
可以用字符串标识任意类型的数据值,但核心库中没有这方面的例子。
可以用字符串存储任意类型的数据值,如下面将要讲到的静态数组。
Lu字符串有二种类型:Lu静态字符串和Lu动态字符串。Lu静态字符串即在Lu表达式中定义的字符串,长度是固定的,不能随意进行销毁(但Lu静态字符串也并非一成不变);Lu动态字符串由函数new生成。
Lu数组有静态和动态两种。
Lu动态数组是多维的,参考函数new的说明。
Lu静态数组是一维的,通过一组函数操作静态字符串来实现。使用数组之前,须先用转义字符"\[n]"定义足够的静态空间。
Lu内置了Unicode字符数组、整数数组、实数数组、复数数组和三维向量数组的操作函数,参考函数A、A2和A3的说明。也可以通过数组句柄直接访问数组元素,如下例:
例子1:静态Unicode字符数组
(:i,k,str)=
{
str="**\[26]**",//定义字符串str,共30个字符,这就是一个Unicode字符数组
k=["a"], k=k[0],//用获得字符"a"的Unicode值并赋值给k
i=0, while{i<26, str[2+i]=k++, i++}, //将星号之间是字符空间设置为26个英文字母
o(str)//输出字符数组str
};
例子2:静态实数数组,字符串必须定义成"\&... ..."格式。
(:i,k,a,s)=
{
a=realss("\&\[10]"), //定义4×10个字符长的字符串,并转换为实数数组
k=len(a), //获得数组长度
i=0, while{i<k, a(i)=1.0, i++},//将数组的每一个元素都设为1.0
i=0, s=0.0, while{i<k, s=s+a(i++)},//将数组的所有元素都加起来
s //输出相加后的值
};
例子3:动态实数数组
(:i,j,m,n,a,s)=
{
a=new(reals,3,10), //定义3×10实数数组(矩阵)
len(a,0,&m,&n), //获得数组维数大小
s=0.0, i=0, while{i<m,
j=0, while{j<n, a(i,j)=1.0, s=s+a(i,j), j++},//将数组的每一个元素都设为1.0,并将数组的所有元素都加起来
i++
},
s //输出相加后的值
};
Lu动态数据存放在一个线性表中,简称lu表,lu表中可以存放任意类型的数据,包括nil和lu表。
有两种方法可以生成lu表。一种是使用lu函数,例如:lu[2,3.3,"me"];或者使用函数new[lu,5:a,b,c,...]创建lu表,此时可指定lu表的长度并赋初值。
lu表是Lu的基本动态数据类型。函数以lu表为参数时,可实现变参数传递。在垃圾收集时,若一个lu表是有效的,则该lu表中的所有对象都是有效的,不会被垃圾收集器所收集。
例子1:
(:a,b)= a=lu[2,3.5,"me",lu[5.5,true]], b=a[3], o[a(0)," ",a(1)," ",a(2)," ",b(0)," ",b(1)," "];
例子2:
(:a,b)= a=new[lu,4: 2,3.5,"me",lu[5.5,true]], b=a[3], o[a(0)," ",a(1)," ",a(2)," ",b(0)," ",b(1)," "];
例子3:
f(x:i,k,s)= k=len(x), s=x[0], i=1, while{i<k, s=s+x[i++]}, s; //函数f接受一个一维对象,并对对象的所有元素相加
f[lu(1,2,3,4,5)]; //5个整数相加
f[lu(1.0,2.0,3.0)]; //3个实数相加
f[lu("abc","123","xyz")];//3个字符串相加
指针是动态数据对象的内存地址,对象的成员函数通过指针可以操作对象。Lu核心库中的动态对象有Lu动态数据、动态字符串string、动态整数数组ints及动态实数数组reals等。扩展库中将存在更多的动态对象,请参考各扩展库的说明。
通常,动态对象是用函数new申请的,但也不尽然,例如函数lu[...]就返回一个动态对象。
所有新生成的动态对象都是局部对象,其生命期即所在表达式运行时的生命期,即当一个表达式运行结束时,所有在该表达式中的局部动态对象都将被销毁。但函数global(p)可以将局部动态对象转换为全局动态对象,则表达式运行生命期结束时该对象不会被销毁。也可以使用函数local(p)将一个全局动态对象转换为局部动态对象,则表达式运行生命期结束时该对象会被销毁。
Lu表达式使用栈管理局部动态对象,故如果在循环中不断生成新的动态对象,若不对新动态对象使用global函数,则只有最后一次循环中生成的新动态对象被保留,其余的都将被销毁。
例子:
gc[]; //垃圾收集,必要时重新初始化程序,使当前动态对象数为0
lu[1], lu[2], global[lu(3)], global[lu(4)], lu[5], lu[6], ObjNum[0]; //生成6个对象,返回对象总数:6
ObjNum[0]; //返回当前对象总数:6
ClearBuf[], ObjNum[0]; //清空缓冲区,返回当前对象总数:2
gc[], ObjNum[0]; //垃圾收集,返回当前对象总数:0
lu[1], lu[2], global[true], lu(3), lu(4), global[false], lu[5], lu[6], ObjNum[0]; //生成6个对象,返回对象总数:6
ObjNum[0]; //返回当前对象总数:6
ClearBuf[], ObjNum[0]; //清空缓冲区,返回当前对象总数:2
gc[], ObjNum[0]; //垃圾收集,返回当前对象总数:0
(:a,b,c,d:e,f)= a=lu[1], b=lu[2], global[true], e=lu(3), f=lu(4), global[false], c=lu[5], d=lu[6], ObjNum[0]; //生成6个对象,返回对象总数:6
ObjNum[0]; //返回当前对象总数:6
ClearBuf[], ObjNum[0]; //清空缓冲区,返回当前对象总数:2
gc[], ObjNum[0]; //垃圾收集,返回当前对象总数:2
(::e,f)= delete[e,f], ObjNum[0]; //彻底销毁对象,返回当前对象总数:0
21 对象成员运算符(函数参数运算符)及对象赋值运算符 [返回页首] [返回目录]
若标识符后面有句点“.”,表示将该标识符联系到一个函数,例如:a.sin();或者产生一个函数调用,例如:a.b 相当于 a(b)。若变量是显示说明的,则通过句点将产生隐含的oset函数或oget函数调用,称变量函数调用,例如:a.b 相当于 oget[a,b];a.b=c 相当于 oset[a,b:c]。
在Lu中,存在很多动态对象,动态对象用指针标识,所以Lu编译器把任何一个函数或表达式都看作动态对象或指针的成员函数。为了存取动态对象时,在形式上看起来更美观一些,Lu32.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)。
"字符串也可以的".o[]; //执行o["字符串也可以的"];
o."字符串也可以的"; //执行o["字符串也可以的"];
(: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);
对象成员运算符不但可以表示对象的成员关系,合理地使用该运算符也可使程序更易读。例如:对象ObjA有一个成员函数ObjA_Add(a,b),执行对象a和b相加,则对象a、b、c、d连续相加的表达式为:
ObjA_Add{ObjA_Add[ObjA_Add(a,b),c],d}
用运算符“.”可表示为:
a.ObjA_Add(b).ObjA_Add(c).ObjA_Add(d)
尽管运算符“.”对程序的运行速度没有影响,但会影响编译速度,因而建议仅在需要的时候使用它。
如果一个函数直接或者间接地调用了自己,称作函数的递归调用。Lu支持函数的递归调用。
为了在Lu中使用递归,需要在表达式中用SetStackMax(n)设置好堆栈。注意n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出Lu的堆栈,但会使系统堆栈溢出,这样会使程序运行中断,丢失数据。并不需要每次运行程序都进行堆栈的设置,如果堆栈设置的合适,可以只设置一次堆栈。
下面就是递归的最简单的例子:
SetStackMax(10000); //设置堆栈为10000;
a()=a();//函数a递归地调用自己,属于无穷递归调用。
直接运行上面的表达式,将会返回一个堆栈溢出的运行错误。虽然溢出Lu的堆栈不会中断程序的运行,但对于上面的程序,无论设置多大的堆栈都会溢出,因为函数a的递归定义是错误的。递归函数应当包含一条控制该函数是继续调用其本身还是返回的语句。如果没有这样的语句,递归函数将用完分配给堆栈的所有内存空间,导致堆栈溢出错误。
下面举一个能正常递归调用返回的例子。
SetStackMax(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!;
以下是一个交叉递归的例子。
SetStackMax(1000); //设置堆栈为1000;
a(x:k)=o["a..."],if(x<1,return[x]),k=HFor("b"),k(x-1); //a(...)函数中调用了b(...)函数;
b(x:k)=o["b..."],if(x<1,return[x]),k=HFor("a"),k(x-1); //b(...)函数中调用了a(...)函数;
a[10]; //启动递归程序;
Lu支持表达式的模块化编译。
在用Lu编译表达式时,要给该表达式指定模块号,模块号用一个整数进行标识。
在Lu中,一个模块由一个或多个表达式组成。模块用一个整数标识,整数可正可负,只要绝对值相等,就属于同一个模块。一般用正整数表示该模块名。模块共有两类,即主模块(0#模块)和普通模块(其他标号的模块)。
同一模块中,模块号为负的表达式称私有表达式,只能被本模块的表达式所访问(即调用),在其他模块中是不可见的;模块号为正的表达式称公有表达式或全局表达式,能被任何一个表达式所访问。主模块(0#模块)中的表达式都是私有表达式。任何一个表达式,既可以访问本模块中的表达式,也可以访问其他模块中的全局表达式,如果本模块中的一个私有表达式与其他模块的一个全局表达式重名,将优先调用本模块中的私有表达式。
由以上规定可以看出,主模块可以访问本模块中的表达式,也可以访问其他模块中的全局表达式。因此,主模块常常用在主程序中。
通常(核心库不提供这些支持),可以用编译符#MODULE#及#END#定义一个模块,用编译符:::输出模块中的全局表达式。另外,若表达式名称前有编译符!!!,在编译后将立即执行;若表达式名称前有编译符~~~,只编译,不执行。如下例:
#MODULE#//定义一个子模块
!!!a()=o("字符串!");//模块私有表达式,编译后立即执行
f(x)= x+1; //模块私有表达式
~~~g()= 100;//模块私有表达式,只编译,不执行
:::h(x)= f(x)+g()+2;//全局表达式
#END#//子模块定义结束
f(x)= x+5; //主模块中的私有表达式,可以使用与其他模块中的表达式相同的名字
f[3]; //调用主模块中的函数f
h[3]; //调用子模块中的全局表达式
使用命名空间可以有效地避免函数重名问题。Lu中可以用函数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,true);//在命名空间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,结果为:55
在下面的说明中,LuErr表示相应函数的运行错误代码。
28.1 数组元素存取 A(p,...):
该函数效率很高,但仅对如下数组元素进行存取(存取时仅对数据的基本类型做检查,忽略扩展类型):
(1)静态字符串(一维静态字符数组)
例子:
(:p)= p=" ", A[p,1]=66, A[p,1];
(2)静态(一维)整数数组
例子:
(:p)= p=intss("\&\[5]"), A[p,1]=66, A[p,1];
(3)静态(一维)实数数组
例子:
(:p)= p=realss("\&\[5]"), A[p,1]=66.77, A[p,1];
(4)静态(一维)lu表
例子:
(:p)= p=lu[1,1,1], A[p,1]=66.77, A[p,1];
(5)动态字符串(一维动态字符数组)
例子:
(:p)= p=new[string,5], A[p,1]=66, A[p,1];
(6)动态(多维)整数数组
例子:
(:p)= p=new[ints,3,5], A[p,1,3]=66, A[p,1,3];
(7)动态(多维)实数数组
例子:
(:p)= p=new[reals,3,5], A[p,1,3]=66.77, A[p,1,3];
若LuErr=1:对象类型不可识别;LuErr=2:参数不匹配;LuErr=3:对象不存在;LuErr=4:元素地址错误;LuErr=5:赋值参数不符合要求;LuErr=6:数组维数非法。
28.2 复数数组元素存取 A2(p,...):
该函数仅对如下数组元素进行存取(存取时仅对数据的基本类型做检查,忽略扩展类型):
(1)静态字符串(看作一维静态复数数组,一个数组元素占16个字节,相当于8个Unicode字符长度)
例子:
(:p)= p="\[32]", A2[p,1]=66+77i, A2[p,1];
(2)静态(一维)实数数组(看作一维静态复数数组,一个数组元素占16个字节,相当于2个实数长度)
例子:
(:p)= p=realss("\&\[6]"), A2[p,1]=66+77i, A2[p,1];
(3)动态多维实数数组(最后一维必须为2,看作动态复数数组,即实数数组a[i,j,2]相当于复数数组a[i,j])
例子:
(:p)= p=new[reals,3,5,2], A2[p,1,3]=66-77i, A2[p,1,3];
若LuErr=1:对象类型不可识别;LuErr=2:参数不匹配;LuErr=3:对象不存在;LuErr=4:元素地址错误;LuErr=5:赋值参数不符合要求;LuErr=6:数组维数非法。
28.3 三维向量数组元素存取 A3(p,...):
该函数仅对如下数组元素进行存取(存取时仅对数据的基本类型做检查,忽略扩展类型):
(1)静态字符串(看作一维静态三维向量数组,一个数组元素占24个字节,相当于12个Unicode字符长度)
例子:
(:p)= p="\[36]", A3[p,1]=(3$5$7), A3[p,1];
(2)静态(一维)实数数组(看作一维静态三维向量数组,一个数组元素占24个字节,相当于3个实数长度)
例子:
(:p)= p=realss("\&\[6]"), A3[p,1]=(3$5$7), A3[p,1];
(3)动态多维实数数组(最后一维必须为3,看作动态三维向量数组,即实数数组a[i,j,3]相当于三维向量数组a[i,j])
例子:
(:p)= p=new[reals,3,5,3], A3[p,1,3]=(3$5$7), A3[p,1,3];
若LuErr=1:对象类型不可识别;LuErr=2:参数不匹配;LuErr=3:对象不存在;LuErr=4:元素地址错误;LuErr=5:赋值参数不符合要求;LuErr=6:数组维数非法。
28.4 获取流逝过去的时钟脉冲数 clock();
28.5 求和函数 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);
LuErr=1:参数太少;LuErr=2:非法表达式句柄;LuErr=3:常量表达式无法求和;LuErr=4:参数个数不匹配;LuErr=5:内存分配失败;LuErr=6:自变量参数非法;LuErr=7:求和函数应返回一个实数。
28.6 求积函数 pro(@F,y1min,y1max,y1dy,y2min,y2max,y2dy... ...):
用法请参见sum(),用于求积。
LuErr=1:参数太少;LuErr=2:非法表达式句柄;LuErr=3:常量表达式无法求积;LuErr=4:参数个数不匹配;LuErr=5:内存分配失败;LuErr=6:自变量参数非法;LuErr=7:求积函数应返回一个实数。
28.7 设置整数堆栈最大数目 SetStackMax(n):
在使用递归函数之前,需要先设置一定数目的堆栈。通常n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出Lu的堆栈,但会使系统堆栈溢出,这样会使程序运行中断。该函数返回实际设置的堆栈数目。
若LuErr=1:内存分配失败;LuErr=2:在函数递归时使用该函数;LuErr=2:需要整数参数。
28.8 设置常量 const("ConstStr",ConstValue)或const("ConstStr",ConstValue,bMode):
ConstStr:常量名,要符合Lu标识符的命名规定,否则编译器找不到该常量。可使用常量命名空间的方式命名,例如:"ConstName::MyConst"。
ConstValue:常量的值。当bMode为逻辑假时,不使用该值。
bMode:逻辑值,指出工作方式。若缺省该参数(函数const只有两个参数),创建一个永久性常量,无法删除一个永久性常量,除非Lu重新初始化。若bMode为true,创建一个暂时性常量;若bMode为false,删除一个暂时性常量。暂时性常量保持常量的基本意义,在编译表达式时不可改变其值,但可被const函数删除。
该函数返回值的意义如下:
0:设置成功。
1:已存在该常量。
2:内存分配失败。
3:不能用空字符串作为常量名。
4:参数不符合要求。
5:需用字符串指出常量名。
6:不能删除该常量。
28.9 创建模块命名空间 Module("NameSpace":"name1","name2",... ...):
该函数为调用该函数的模块创建模块命名空间。NameSpace为空间名称,继承自"name1","name2",... ...。模块命名空间可以循环继承。模块命名空间是一棵树或者是一个连通图。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:创建成功。
1:至少需要一个参数,不能缺省模块命名空间名称。
2:不能重复创建模块命名空间,模块命名空间只能创建一次。
3:须用字符串指出模块命名空间名称。
4:内存错误。
5:该命名空间已被其他模块使用。
6:要创建的命名空间不能是空字符串。
28.10 输出模块命名空间中的表达式 OutFun("fun1","fun2",... ...):
该函数为模块命名空间输出表达式。模块命名空间即调用函数OutFun的模块的命名空间。须用字符串参数指出表达式的名称。
请参考模块命名空间了解该函数的使用。
该函数返回值的意义如下:
0:操作成功。
i:其他正整数:前i-1个参数操作成功,第i个参数操作失败,其他参数未进行操作。
28.11 获得表达式的句柄 HFor("ForName",bType,&nPara,&hModule,&bParaModify):
ForName:表达式的名称。可以是简单名称,也可以是包含模块命名空间访问符::(该访问符可简化为一个或多个:)的复杂名称。
bType:名称的类型。如果bType为true,只能取简单名称。如果bType为false,名称可包含模块命名空间访问符::。若缺省该参数,默认为true。
nPara:返回表达式的参数个数。该参数可以缺省。
hModule:返回表达式所在的模块。该参数可以缺省。
bParaModify:返回一个逻辑值,表示表达式的自定义参数在执行过程中是否可能被修改,逻辑真表示可能被修改。该参数可以缺省。
28.12 访问命名空间 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函数而互不影响。
28.13 标记需自动销毁的对象 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销毁,这取决于主程序和Lu扩展库的设计。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。
注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。
28.14 获得对象信息 o(p1,p2,p3,... ...):
获得对象p1,p2,p3,... ...的信息。函数返回输出的信息串的长度。
不是所有的对象都支持o函数,具体请查看该对象的说明。
28.15 对象赋值 oset(p,... ...):
p是一个对象指针,函数oset(p,... ...)可对对象p赋值。oset函数的调用是与等号相关联的。
若LuErr=1:参数不匹配;LuErr=2:没有为对象重载该函数;LuErr=3:非法的对象指针;LuErr=4:参数不符合要求。
不是所有的对象都支持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]
28.16 获得对象的值 oget(p,... ...):
p是一个对象指针,函数oget(p,... ...)可获得对象p的值;p也可以是一个自定义函数的指针,此时将产生一个函数调用。oget函数的调用不与等号相关联。
该函数的返回值请参考对象的有关说明。若p是函数指针,则该函数返回函数调用的值。
若LuErr=1:参数不匹配;LuErr=2:没有为对象重载该函数;LuErr=3:非法的对象指针;LuErr=4:参数不符合要求;LuErr=5:调用函数时参数不匹配;LuErr=6:非法的函数句柄。
不是所有的对象都支持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)
28.17 垃圾收集 gc():
立即扫描并销毁垃圾对象,释放所占空间。函数返回0表示运行成功,否则表示不允许运行垃圾收集器。
在Lu中,一个对象用一个指针标识,该指针存放在一个Lu数据中,此时,我们称这个数为指向该对象的指针。若Lu系统中的静态变量、模块变量、全局变量以及正在运行的表达式的数据区(自变量、动态变量及数据堆栈)中存在一个数对指向一个全局象时,该对象是有效的,否则视为垃圾对象,会被垃圾收集器所回收。
28.18 获得或设置动态对象数目 ObjNum(n):
当n>=100时设置允许的动态对象的最大数目,否则返回当前动态对象的数目。
通常,Lu在检测到动态对象数目超过了允许的最大值时,将启动垃圾收集器进行清理。
28.19 清空垃圾对象缓冲区 ClearBuf():
动态对象生命期结束时,或者用函数del(...)删除对象时,对象会暂时存放在垃圾对象缓冲区中,该函数用于清空垃圾对象缓冲区。
28.20 生成Lu动态数据 lu(a,b,c,...):
Lu动态数据存放在一个线性表中,简称lu表,lu表中可以存放任意类型的数据,包括nil和lu表。
还可以用函数new[lu,5:a,b,c,...]创建lu表,此时可指定lu表的长度并赋初值。
lu表是Lu的基本动态数据类型。函数以lu表为参数时,可实现变参数传递。在垃圾收集时,若一个lu表是有效的,则该lu表中的所有对象都是有效的,不会被垃圾收集器所收集。
28.21 将静态字符串转换为静态一维整数数组 intss("...",x1,x2,x3,...):
参数x1,x2,x3,...将给数组赋初值(只能是整数或实数,若是实数将截断取整)。
若LuErr=1:非法的字符串对象;LuErr=2:非法的初值参数。
28.22 将静态字符串转换为静态一维实数数组 realss("...",x1,x2,x3,...):
参数x1,x2,x3,...将给数组赋初值(只能是实数或整数,若是整数将自动进行类型转换)。
若LuErr=1:非法的字符串对象;LuErr=2:非法的初值参数。
28.23 强制类型转换 cast(Data,MyType):
强制将数据Data的扩展类型转换为MyType类型。MyType是一个整数,标识某种类型的数据。若MyType为self,则将数据Data的扩展类型转换为其基本类型。
28.24 将动态对象放到垃圾对象缓冲区 del(x1,x2,x3,...):
垃圾对象缓冲区中的对象将由Lu在合适的时候进行销毁。
28.25 立即销毁对象 delete(x1,x2,x3,...):
28.26 生成新的对象 new(type,x1,x2,x3,...):
type是一个整数,标识某种动态数据类型;x1,x2,x3,...是初始化参数。
Lu核心库中定义的动态类型有lu表(lu)、动态字符串(string)、动态整数数组(ints)和动态实数数组(reals),见下表:
动态类型 | 标识符 | 例 子 | 说 明 |
lu表 | lu | new[lu,5] | 生成长度为5的lu表。 |
new[lu,5 : 2, "aa", 3.3] | 生成长度为5的lu表并赋初值,若有多余的初值参数,将被忽略。 | ||
字符串 | string | new[string,5] | 生成长度为5的动态字符串。 |
new[string,5,"abc"] | 生成长度为5的动态字符串并赋初值,初值是一个字符串参数,若字符串过长,将被截断。 | ||
new[string,"abc"] | 生成与所给字符串长度相同的动态字符串,并赋初值。 | ||
整数数组 | ints | new[ints,3,5] | 生成二维数组。 |
new[ints,3,5,data: 11,22,33] | 生成二维数组并赋初值,多余的参数被忽略。 | ||
new[ints,data: 11,22,33] | 生成一维数组并赋初值,数组长度即初值参数个数。 | ||
实数数组 | reals | new[reals,3,5] | 生成二维数组(矩阵)。 |
new[reals,3,5,data: 1.1,2.2,3.3] | 生成二维数组(矩阵)并赋初值,多余的参数被忽略。 | ||
new[reals,data: 1.1,2.2,3.3] | 生成一维数组并赋初值,数组长度即初值参数个数。 |
28.27 返回对象长度 len(p,&x1,&x2,&x3,...):
该函数返回对象p的长度,并通过参数x1,x2,x3等返回更多信息。如果是动态整数数组(ints)或动态实数数组(reals),则x1返回数组维数,x2,x3,...返回每一维的大小。
28.28 创建新的扩展数据类型 newtype():
该函数返回一个新的数据扩展类型(是一个整数),当该类型的数据成为Lu运算符或重载函数的操作数时,将调用与该扩展类型匹配的运算符重载函数。调用函数newtype的表达式即是与该扩展类型匹配的运算符重载函数,参考运算符及函数重载。
在一个表达式中多次调用newtype()时,仅返回一种数据扩展类型,或者nil。
Lu运算符有单目运算符和双目运算符两种,最多有两个操作数。Lu程序在运行时,如果遇到运算符,先查看第一操作数的扩展类型,找到其重载函数调用之,若第一操作数没有重载函数,再查看第二操作数的扩展类型,找到其重载函数调用之,若仍没有找到重载函数,将报告一个运行错误。
Lu部分内置函数允许重载,对于一元函数或参数不确定函数,都是由第一操作数的扩展类型决定重载函数的调用,或者报告一个找不到重载函数的运行错误;对于二元函数,先查看第一操作数的扩展类型,找到其重载函数调用之,若第一操作数没有重载函数,再查看第二操作数的扩展类型,找到其重载函数调用之,若仍没有找到重载函数,将报告一个运行错误。
在Lu中可重载的运算符及函数见下表:
theOperator | 运算符 | 功 能 | 参数个数m+1 | 引起重载函数被调用的操作数 | 说 明 |
0 | + | 加 | 2 | 第一操作数优先,其次第二操作数 | |
1 | - | 减 | 2 | 第一操作数优先,其次第二操作数 | |
2 | * | 乘 | 2 | 第一操作数优先,其次第二操作数 | |
3 | % | 求模 | 2 | 第一操作数优先,其次第二操作数 | |
4 | / | 左除 | 2 | 第一操作数优先,其次第二操作数 | |
5 | \ | 右除 | 2 | 第一操作数优先,其次第二操作数 | |
6 | ^ | 乘方 | 2 | 第一操作数优先,其次第二操作数 | |
7 | - | 负 | 1 | 第一操作数 | |
8 | ' | 转置 | 1 | 第一操作数 | |
9 | ++o | 前置自增 | 1 | 第一操作数 | 通常先对当前对象自增,再返回当前对象。 |
10 | --o | 前置自减 | 1 | 第一操作数 | 通常先对当前对象自减,再返回当前对象。 |
11 | o++ | 后置自增 | 1 | 第一操作数 | 通常返回新对象。 |
12 | o-- | 后置自减 | 1 | 第一操作数 | 通常返回新对象。 |
13 | << | 左移位 | 2 | 第一操作数优先,其次第二操作数 | |
14 | >> | 右移位 | 2 | 第一操作数优先,其次第二操作数 | |
15 | > | 大于 | 2 | 第一操作数优先,其次第二操作数 | |
16 | >= | 大于等于 | 2 | 第一操作数优先,其次第二操作数 | |
17 | < | 小于 | 2 | 第一操作数优先,其次第二操作数 | |
18 | <= | 小于等于 | 2 | 第一操作数优先,其次第二操作数 | |
19 | == | 等于 | 2 | 第一操作数优先,其次第二操作数 | |
20 | != | 不等于 | 2 | 第一操作数优先,其次第二操作数 | |
21 | & | 与 | 2 | 第一操作数优先,其次第二操作数 | |
22 | | | 或 | 2 | 第一操作数优先,其次第二操作数 | |
23 | ~ | 异或 | 2 | 第一操作数优先,其次第二操作数 | |
24 | ! | 非 | 1 | 第一操作数 | |
25 | .* | 点乘 | 2 | 第一操作数优先,其次第二操作数 | |
26 | ./ | 点左除 | 2 | 第一操作数优先,其次第二操作数 | |
27 | .\ | 点右除 | 2 | 第一操作数优先,其次第二操作数 | |
28 | .^ | 点乘方 | 2 | 第一操作数优先,其次第二操作数 | |
29 | .' | 点转置 | 1 | 第一操作数 | |
30 | && | 按位与 | 2 | 第一操作数优先,其次第二操作数 | |
31 | || | 按位或 | 2 | 第一操作数优先,其次第二操作数 | |
32 | ~~ | 按位异或 | 2 | 第一操作数优先,其次第二操作数 | |
33 | !! | 按位非 | 1 | 第一操作数 | |
34 | $ | 并 | 2 | 第一操作数优先,其次第二操作数 | |
35~43 | 未定义 | ||||
44 | len | 重载函数 | 不确定 | 第一操作数 | 返回对象长度。 |
45 | copy | 重载函数 | 不确定 | 第一操作数 | 复制对象。 |
46 | new | 重载函数 | 不确定 | 第一操作数 | 返回一个新对象。 |
47 | oset | 重载函数 | 不确定 | 第一操作数 | 对象赋值。 |
48 | oget | 重载函数 | 不确定 | 第一操作数 | 获得对象的值。 |
49 | o | 重载函数 | 1 | 第一操作数 | 获得对象信息。函数要返回信息串的长度。 |
50 | sqrt | 重载函数 | 1 | 第一操作数 | |
51 | exp | 重载函数 | 1 | 第一操作数 | |
52 | ln | 重载函数 | 1 | 第一操作数 | |
53 | lg | 重载函数 | 1 | 第一操作数 | |
54 | sin | 重载函数 | 1 | 第一操作数 | |
55 | cos | 重载函数 | 1 | 第一操作数 | |
56 | tan | 重载函数 | 1 | 第一操作数 | |
57 | asin | 重载函数 | 1 | 第一操作数 | |
58 | acos | 重载函数 | 1 | 第一操作数 | |
59 | atan | 重载函数 | 1 | 第一操作数 | |
60 | sinh | 重载函数 | 1 | 第一操作数 | |
61 | cosh | 重载函数 | 1 | 第一操作数 | |
62 | tanh | 重载函数 | 1 | 第一操作数 | |
63 | abs | 重载函数 | 1 | 第一操作数 | |
64 | floor | 重载函数 | 1 | 第一操作数 | |
65 | ceil | 重载函数 | 1 | 第一操作数 | |
66 | itor | 重载函数 | 1 | 第一操作数 | |
67 | rtoi | 重载函数 | 1 | 第一操作数 | |
68 | con | 重载函数 | 1 | 第一操作数 | |
69 | atan2 | 重载函数 | 2 | 第一操作数优先,其次第二操作数 | |
70 | fmod | 重载函数 | 2 | 第一操作数优先,其次第二操作数 |
重载函数的格式如下:
mytype(x,y,...,num,op)=which //x,y,...中,x是第一操作数,y是第二操作数,以此类推;num是操作数个数;op标识所重载的运算符或函数。
{
op<0 : return[newtype()], //返回重载函数相关的扩展数据类型
op==0 : { ... }, //重载运算符+
op==1 : { ... }, //重载运算符-
op==2 : { ... }, //重载运算符*
... , //重载其他运算符
{ nil } //该数据类型不支持该运算符的重载 ,返回nil;若定义了该运算符或函数的操作,但操作失败时,应返回nil1。
};
重载函数的参数个数至少为4个。重载函数被调用时,前面的参数将传入实际的操作数,并将所有操作数的扩展类型重置为与其基本类型相同,多于的参数被置为nil;倒数第二个参数num传入实际的操作数个数;而最后一个参数op决定对哪个运算符或函数进行重载。当op<0时,由函数newtype()获取并返回一个数据的扩展类型,正是该扩展类型引起该重载函数的调用。
由运算符重载引起的运行错误,其出错函数名为operator::operator,错误代码:1:找不到该扩展类型的重载函数;2:重载函数的参数个数太少,而实际要传入的参数个数太多。
例子:
thetype(x,y,num,op)=which
{
op<0 : return[newtype()],
op==0 : x-y, //重载运算符+
op==1 : x+y, //重载运算符-
op==2 : x/y, //重载运算符*
nil //该数据类型不支持该运算符的重载,返回nil
};
test(:type,a,b)=
type=thetype(0,0,0,-1), //获取新数据类型
a=cast[3,type], b=cast[5,type], //强制转换为新数据类型
o[" a=",3," b=",5], //输出a和b
o[" a+b=",a+b], //计算并输出a+b,变成了a-b
o[" a-b=",a-b], //计算并输出a-b,变成了a+b
o[" a$b=",a$b]; //没有重载运算符$,故输出nil
C程序员要自己管理内存的分配和回收,而Python具有垃圾自动回收的机制,Lu的动态内存管理兼有二者的特点:既可以手动回收垃圾,也可以完全依赖于Lu的垃圾自动回收机制。通常,Lu中用类似new的函数生成动态对象,而用类似delete的函数销毁动态对象,这一点类似于C,用户可以高效地管理内存;所有用户没有销毁的对象,会由Lu的垃圾收集器管理,并最终会被Lu安全地回收,这一点类似于Python。
除Lu核心库中提供的动态对象外,支持模块可以使用C/C++、Delphi等语言创建任意的动态对象并加入Lu系统。
在Lu中,一个对象用一个指针标识,该指针保存在Lu数据中,此时称这个数为指向该对象的指针。若Lu系统中的静态变量、模块变量、全局变量以及正在运行的表达式的数据区(自变量、动态变量及数据堆栈)中存在一个数指向一个对象时,该对象是有效的,否则视为垃圾对象,会被垃圾收集器所回收。
由于只要有一个指针(直接或间接)指向某对象,该对象就不会被垃圾收集器回收,故若要确保立即销毁某对象,应使用delete之类的专用函数,而不要依赖垃圾收集器。
Lu的内存管理特点:(1)与C/C++类似,用户可立即销毁一个对象;(2)被用户忽略的垃圾将被自动回收;(3)任何时候,可手动立即启动垃圾收集器;(4)垃圾回收是有运行开销的,但如果用户有效地管理了内存,垃圾回收器可以一次也不启动。
启动Lu垃圾收集器:
(1)用户调用函数gc()将立即启动垃圾收集器。
(2)当Lu检测到对象太多时将自动启动垃圾收集器。Lu用一个计数器记住生成的动态对象数目,当达到规定值时,就启动垃圾收集器;允许用户修改对象计数器允许的最大值。
(3)主程序可能会按一定的算法在处理器空闲时调用Lu垃圾收集器,这取决于主程序的设计。
注意:与其他语言的垃圾收集器不同,Lu垃圾收集器总是立即启动的。
31 表达式的初始化及销毁 [返回页首] [返回目录]
31.1 通过静态变量初始化和销毁数据
通过静态变量,Lu表达式可以进行初始化,也可以借助专用静态变量free进行销毁表达式前的释放工作(Lu在销毁表达式前将自动设置free=1,然后自动执行表达式)。如下例:
f(x:y,static,d,e,free)= //表达式用o函数输出字符串
{
if{d==0,o("初始化!"),d=1},//注意静态变量d使初始化仅执行一次。
if{free,o("销毁!"),return(0)}, //Lu在销毁表达式前将自动设置free=1,然后自动执行表达式,使销毁仅执行一次。
x+1 //每次调用使自变量增1。
};
f(0);
f(1);
f(2);
为了充分发挥静态变量free的作用,在表达式中不要对其有任何的赋值运算,以免引起混乱。另外,如果没有将free定义为静态变量,Lu在销毁表达式前将不会将其自动设置为1,也不会自动执行表达式。
如果一个表达式中直接调用的其他表达式或二级函数被删除,该表达式将不能运行,这样即便在该表达式中定义了静态变量free,在Lu销毁该表达式前也无法执行该表达式。如下例:
//函数f中调用了函数a,如果函数a先于函数f被销毁,则函数f将无法进行正常的销毁工作。
a(x)=x+1;
f(x:y,static,d,e,free)=
{
if{d==0,o("初始化!"),d=1},//注意静态变量d使初始化仅执行一次。
if{free,o("销毁!"),return(0)}, //如果函数a先于函数f被销毁,则函数f将无法进行正常的销毁工作。
a(x)+1 //调用函数a。
};
f(0);
f(1);
f(2);
若要上面的程序正常工作,主程序在销毁表达式时必须遵循“后进先出”的原则,即:后编译的表达式先删除。
动态调用表达式或二级函数,能使上面的程序正常工作。如下例:
//函数f动态调用了函数a,即便函数a先于函数f被销毁,也不影响函数f执行正常的销毁工作。
a(x)=x+1;
f(x:y,aa,static,d,e,free)=
{
if{d==0,o("初始化!"),d=1},//注意静态变量d使初始化仅执行一次。
if{free,o("销毁!"),return(0)}, //Lu在销毁表达式前将自动设置free=1,然后自动执行表达式,使销毁仅执行一次。
aa=HFor("a"), //动态获取函数a的句柄。
aa(x)+1 //通过函数句柄aa动态调用函数a,使自动销毁正常进行。
};
f(0);
f(1);
f(2);
31.2 通过函数free(p)销毁数据
函数free(p)可将对象p自动记录下来,在销毁表达式时销毁对象p。用法举例如下:
i: ConstObj(:static,obj)=
{
if{obj==0, obj=new[...].free()},//第一次调用函数ConstObj()时,用函数new申请一个对象obj,并用函数free()标记在销毁表达式时销毁它
obj //返回对象obj
};
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
i: ConstObj(); //每次调用函数ConstObj(),都会得到同一个对象
注意:函数free(p)并不立即销毁对象p,而是在销毁表达式时销毁该对象。并不是所有的对象都可由函数free销毁,这取决于主程序和Lu扩展库的设计。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。
注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。
最后指出,能否进行销毁表达式前的释放工作,取决于主程序的设计,与主程序如何使用Lu的输出函数有关。即便销毁表达式前没有进行释放工作,Lu最后也将释放所有资源,因而无需担心太多。
简单地,使用Lu的垃圾收集器,可不必关心垃圾对象的销毁问题。
错误有编译错误和运行错误两种。通常,Lu程序总是准确定位编译期错误的。若程序编译通过,在运行时仍会发生错误,通常,Lu程序总是记住并给出第一个运行错误的信息,错误信息包括运行出错的表达式的类型和名称(可见,给每一个表达式都起一个名字是非常重要的)、表达式所在的模块、运行出错的函数名、错误代码等等,用户可根据这些信息查找并修改错误。
Lu运行时使用错误(异常)处理的恢复模型。模块化编译运行库MLu中提供了一组函数以支持该功能:
1、检测输出并清除Lu运行错误:err();
2、获得Lu运行错误:geterr(&ErrType,FunName,&FunCode,&ForHandle);
ErrType:返回运行错误的类型。
FunName:Lu字符串,返回出错函数名。
FunCode:返回函数错误代码。
ForHandle:返回出错表达式的句柄,该句柄即编译表达式时获得的句柄。
3、设置Lu运行错误:seterr(ErrType,FunName,FunCode);
ErrType:设置运行错误的类型。
FunName:设置出错的函数名,要求传递一个Lu字符串(长度小于80)。
FunCode:设置函数错误代码。
4、清除Lu运行错误:clearerr();
以下例子用函数seterr设置了一个Lu运行错误,并用函数geterr捕获了该错误:
f(:a,b,c,d,e)= seterr[66,"aaa",23], geterr[&a,b="\[10]",&c,&d], o[b];
Lu运行错误可由Lu系统、二级函数,或者由Lu脚本用户进行设置,可在任意位置设置运行错误,同时可在任意位置捕获该运行错误。运行错误一经设置,将始终存在,直到遇到函数clearerr()或err()为止。在执行MLu模块的最后,将自动调用函数err()。
捕获Lu运行错误并进行处理后,可调用函数clearerr()清除运行错误,重启程序代码,通常需借助循环完成该处理过程。
在一般的编程语言中,关键字是事先定义的有特别意义的字,关键字是保留字,不能用来做标识符(如变量名),使用关键字来做变量名是一种语法错误,不能通过编译。按此定义,则Lu中没有关键字。Lu中只有常量、变量和函数。但有些符号常量、变量名或函数名使用很频繁,可当作“关键字”来使用,不过不符合上述关键字的定义,例如:
f(return) = return(return+1); //return是自变量,同时也是一个二级函数。
f(2);
Lu允许符号常量、变量和函数用同一个标识符表示,参考标识符解释规则。但尽量避免这种用法。
Lu核心库中的“关键字”见下表:
关键字 | 类型 | 功 能 |
static | 静态变量 | 定义静态变量。 |
free | 静态变量、二级函数 | 专用静态变量或函数,进行销毁表达式前的释放工作。 |
common | 全局变量 | 定义全局变量。 |
const | 二级函数 | 定义永久性符号常量或暂时性符号常量。 |
return | 二级函数 | 结束计算并立即返回表达式的值。 |
if | 二级函数 | 条件满足时执行计算多个语句。 |
which | 二级函数 | 自定义分段函数,选择计算函数。 |
while | 二级函数 | “当型”循环函数。 |
until | 二级函数 | “直到型”循环函数。 |
continue | 二级函数 | 返回while或until循环的开始。 |
break | 二级函数 | 跳出while或until循环。 |
Module | 二级函数 | 创建模块命名空间。 |
OutFun | 二级函数 | 输出模块命名空间中的表达式。 |
HFor | 二级函数 | 获得表达式的句柄。 |
using | 二级函数 | 访问命名空间。 |
new | 二级函数 | 申请新的动态对象 |
del | 二级函数 | 将动态对象放到垃圾对象缓冲区中,由系统在合适的时候销毁它 |
delete | 二级函数 | 立即销毁动态对象 |
global | 一级函数 | 将局部动态对象转换为全局动态对象 |
local | 一级函数 | 将全局动态对象转换为局部动态对象 |
o | 二级函数 | 获得对象信息 |
oset | 二级函数 | 对象赋值 |
oget | 二级函数 | 获得对象的值 |
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)模块私有表达式与一个公有表达式重名时,优先调用本模块中的私有表达式。
Lu中有以下提高效率的方法。
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]"开辟大的静态空间,将影响编译速度。
8、尽量少用动态全局对象,即少用global函数。
1、表达式最大长度:约2G。
2、自变量个数:不限。
3、动态变量、静态变量、模块变量、全局变量个数:不限。
4、最多可用的表达式:不限。
5、表达式中最多可用的字符串数:不限。
6、自定义外部函数个数:不限。
7、while循环、until循环最大循环次数:不限。
8、表达式递归调用最多层数:受系统堆栈和自定义堆栈大小的限制,自定义堆栈最大为2G。
9、最多可存储的用户自定义数据类型:约2G。
10、执行速度:一级函数速度约为FORTRAN(或C/C++)执行速度的50%左右;其他情况,速度稍有下降;
11、Lu32.DLL文件大小约220K~300K。
12、内存需求:视Lu运行情况而定。
版权所有© Lu程序设计 2011-2011,保留所有权利
E-mail: forcal@sina.com QQ:630715621
最近更新: 2011年10月18日