欢迎访问 Lu程序设计

Lu脚本教程

目  录  [页首]

1 概述
2 安装OpenLu
3 Lu基础知识
3.1 第一个程序
3.2 简单的例子
3.3 标识符

3.4 表达式及函数简介
3.5 源代码格式简介
3.6 o函数
3.7 赋值语句
3.8 常用算术运算符
3.9 逗号(冒号、分号)运算符和括号运算符
4 Lu常量
5 Lu变量
5.1 自变量
5.2 动态变量
5.3 静态变量
5.4 模块变量
5.5 全局变量
6 Lu数据类型
6.1 整数
6.2 实数
6.3 复数
6.4 三维向量
6.5 混合运算隐式转换规则
6.6 逻辑值
6.7 nil
6.8 函数(表达式)句柄
6.9 静态字符串
6.10 静态数组
6.11 动态数据类型
6.12 数组操作函数
6.13 由编译符#生成的整数

7 Lu运算符及优先级
8 Lu流程控制
9 Lu函数
10 Lu错误处理
11 Lu递归调用
12 Lu协程
13 Lu模块化编译及源程序
13.1 源程序的一般格式
13.2 程序的执行
13.3 源程序中的模块
13.4 编译指令的位置和次序
13.5 OpenLu中的模块管理
14 Lu命名空间
14.1 模块命名空间
14.2 二级函数命名空间
14.3 常量命名空间
14.4 访问命名空间
15 Lu动态内存管理
15.1 动态对象与指针
15.2 对象成员运算符(函数参数运算符)及对象赋值运算符

15.3 对象赋值及获取
15.4 动态内存管理
15.5 表达式的初始化及销毁
16 Lu标识符解释规则
17 Lu运算符及函数重载
18 Lu高级数据类型
18.1 luu表
18.2 字符串String
18.3 字典dict
18.4 结构struct
18.5 类class
19 while in循环
20 数学库中的代码矢量化及其他
21 在脚本中编译脚本
22 Lu关键字
23 OpenLu及Lu扩展库

正文

1 概述  [返回页首]

    Lu是一个可对字符串表达式进行动态编译和运行的动态链接库(dll),是一种易于扩展的轻量级嵌入式脚本,支持协程,提供自动内存管理,也可以手动管理内存,可准确定位运行错误,是一种强类型的脚本。Lu可用于连接各种语言编写的模块。Lu的优势在于简单易用和可扩展性强。

    Lu语法由核心库(Lu64.dll)、扩展库及主程序提供。本教程介绍开放式计算程序OpenLu中的语法。OpenLu由核心库Lu64.dll和模块化编译运行库MLu64.dll提供支持。MLu是程序和Lu核心库之间的一个接口库,MLu会自动加载Lu核心库和动态加载多个Lu扩展库,简化了Lu系统的使用;MLu可对Lu源程序进行模块化编译,能够编译运行具有固定格式的源程序(字符串表达式),源程序中可以使用C++风格的注释。

    请下载试用OpenLu以演示本文的例子。OpenLu是免安装的绿色软件。

    除核心库(Lu64.dll)及个别扩展库外,OpenLu、Mlu及大多数扩展库都是开源的,你可以下载OpenLu、MLu及扩展库源代码并加以修改,加入到自己的工程中。

2 安装OpenLu  [返回页首]

    OpenLu是64位Windows平台上免安装的绿色软件,将压缩包解压到合适位置即可。

    OpenLu启动时界面上有2个窗口,上面是代码窗口,下面是运算结果输出窗口。

    OpenLu工作模式有三种,可通过命令菜单进行切换:

    (1)普通编译模式:在代码窗口写好代码后,通过菜单、工具栏或快捷键F8进行编译计算。

    (2)即时编译模式:在代码窗口写代码时,即时给出代码中的错误。

    (3)即时编译计算模式:在代码窗口写代码时,即时给出代码中的错误,若没有错误,将进行计算给出结果。

    为了更好地使用OpenLu时,建议进行以下操作:

    (1)用管理员身份运行OpenLu。用OpenLu打开文件夹“Ini”中的文件“OpenLu.ini”(通常会提示该文件已经打开),或者其他自定义的工作区文件。

        a、执行菜单命令:设置 -> 设置当前文件为工作区。

        b、执行菜单命令:设置 -> 设置当前文件为缺省工作区。

    (2)给OpenLu创建一个快捷方式,然后把该快捷方式放在桌面上或“开始”菜单中。

3 Lu基础知识

3.1 第一个程序  [返回页首]

    对于大多数程序语言,第一个入门编程代码便是"Hello World!",以下是Lu脚本中输出"Hello World!"的代码。

      o("Hello, World!");

    o是一个函数(取对象object或者输出out的首字母),用于输出对象的信息,"Hello World!"是一个字符串对象,字符串要放在两个双引号之间。输出结果如下:

      Hello, World!13

    输出结果的末尾13是o函数的返回值,o函数返回输出的信息串的长度。如果不希望出现o函数的返回值, 需要在o函数后使用两个连续的分号,如下列:

      o("你好,世界!");;

    输出结果如下:

      你好,世界!

3.2 简单的例子  [返回页首]

      F(x,y)=x+y;       //函数定义,恐怕是所有语言中最简单的了
      2+F[2,3]+5;      
 //简单的计算式

    在这个例子中,分号表示一个表达式的结束,两个反斜杠//后的内容表示注释,MLu中采用这种表示和注释方法。
    在第一个表达式中定义了一个函数 F ,小括号( )内的x,y为函数的自变量,等号后为函数体,即可执行的部分,该函数计算并返回两个参数的和。在第二个表达式中调用了上面定义的函数 F 。
    需要注意,在一般的Lu程序中,只计算无自变量的表达式。因此,对第一个表达式只编译,不计算。

3.3 标识符  [返回页首]

    在Lu中,一个标识符可以用来表示一个表达式的名字,或者是一个函数的名字,或者是一个变量,或者是一个符号常量。标识符可由一个或多个字符组成,可以任意一个英文字母、中文字符或者下划线开头,后面可以接英文字母、中文字符、数字或者下划线(下划线的使用可以增加标识符的可读性,如first_name)。注意:英文字母的大写和小写是不同的,例如:count和COUNT是两个不同的名字。下面给出一些合法的标识符:

      first    last    Addr1    top  _of_file    name23    _temp    a23e3    MyVar    您好    数1    My数y数

3.4 表达式及函数简介  [返回页首]

    表达式是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;   //有二个自变量的表达式(函数)

    以下代码的可读性强:

      new[reals, 3, 2, data : 1.1, 2.2; 3.3, 4.4; 5.5, 6.6];    //new函数生成3×2二维数组(矩阵)并赋初值, 使用三个等价的逗号、冒号及分号分隔数据

      f(x)=x^3*ln{abs[(x^2-1)*(x^2-2)]};   //定义函数f,使用三对等价的括号

3.5 源代码格式简介  [返回页首]

    MLu源代码文件由若干函数(或表达式)组成,函数(或表达式)由分号分隔,源代码中可使用C++风格的注释。如下例:

//每行中两个反斜杠后的内容为注释

/*

这是多行注释。

这是多行注释。

*/

f(x,y)=x+y;  //该函数只编译,不计算

2.5+sin[1.2-cos(0.8)]+f[2,3];  //该表达式计算时调用了前面定义的函数f

sin[2.3-5i];  //i表示一个虚数 。2.3-5i等价于{2.3$5},二者都是复数的表示形式

new[reals,3,2, data : 1.1,2.2; 3.3,4.4; 5.5,6.6].math::outa[];; //new函数生成二维数组(矩阵)并赋初值,然后用数学库math的函数outa输出了该矩阵,两个连续的冒号::是命名空间成员访问符

    如何区分表达式中的分号和分隔表达式的分号呢?很简单,通常在括号中的分号是表达式中的分号,否则是分隔表达式的分号。

    注意:MLu只顺序执行不带参数的表达式并输出计算结果。如果该表达式以两个连续的分号“;;”结尾,则该表达式只执行,不输出计算结果。

    以上代码执行结果如下:

      7.982313067962473
      {55.33874541030041$49.43981990995805}

      1.1   2.2
      3.3   4.4
      5.5   6.6

    Lu代码格式自由,但除了即时运行无需保存的代码外,代码书写要便于阅读和维护,需要在代码中合理地使用注释、缩进、 换行、添加空格等。

3.6 o函数  [返回页首]

    格式:o{p1, p2, p3, ... ...}

    获得对象p1,p2,p3,... ...的信息。函数返回输出的信息串的长度。

    可直接通过o函数输出的内置对象有:整数、实数、复数、三维向量、nil、表达式句柄、逻辑值、字符串。

    o函数是被重载的,故该函数执行时,对象p1,p2,p3,... ...的重载o函数将被依次调用。

    不是所有的对象都支持o函数,具体需要查看该对象的说明。

    例子:

    o["x=", 2, ", y=", 3, ", x+y=", 2+3, "\r\n"];

    结果:

    x=2, y=3, x+y=5
    17

    注意:为什么信息串的长度是17呢?因为还包含最后的两个字符"\r\n"。“\r”是转义字符,表示回车,只有一个字符;“\n”也是转义字符,表示换行,也只有一个字符。

3.7 赋值语句  [返回页首]

    Lu脚本中,变量可赋予不同的值,存储不同的数据类型,变量的赋值用等号“=”进行,等号左边是一个变量名,等号右边是存储在变量中的值。 例如:

      //该表达式中没有自变量,a和b为动态变量(冒号前为自变量,冒号后为动态变量),动态变量只在表达式执行时起作用,一旦表达式执行完毕,动态变量也随之失效

      f( : a, b) = {a=1, b=2, a+b}; //a和b为动态变量,分别赋值1和2,然后计算a+b

    结果:

      3

    可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量(这种变量本身类型不固定的语言称之为动态语言),例如:

      f( : a ) =  //a是动态变量
      {
          a = 1,
 //a是整数

          o["a=", a, "\r\n"],

          a = "abc", //a是字符串

          o["a=", a, "\r\n"]
      };;

    结果:

      a=1
      a=abc

    注意不要把赋值语句的等号等同于数学的等号,例如:

      f( : a ) =  //a是动态变量
      
{
          a = 1,

          a = a + 2,

          o["a=", a, "\r\n"]
      };;

    结果:

      a=3

    在数学上理解 a = a + 2 是不成立的,在程序中,赋值语句先计算右侧的表达式 a + 2,得到结果 3 ,然后再赋值给 a 。

    给一个变量 a 赋值,可以理解成将 a 指向某数据,把一个变量 a 赋值给另一个变量 b ,实际上是把变量 b 指向变量 a 所指向的数据,例如:

      f( : a, b ) =
      {
          a = "abc",
 //a指向数据"abc"

          b = a,    //b指向a所指向的数据,也是"abc"

          a = "xyz", //a指向数据"xyz"

          o["b=", b, "\r\n"]
      };;

    结果:

      b=abc

3.8 常用算术运算符  [返回页首]

    常用的算术运算符有:+(加或正)、-(减或负)、*(乘)、/(除)、%(求模)、^(乘方)、++(自增)、--(自减)等等。

    运算符*、/、%和 ^ 是双目运算符。注意数字与变量相乘时,乘号不可省略;在进行乘方运算时,底数应为非负数。

    运算符+、-作加、减运算时,是二元运算符,当作正、负运算时,是单目运算符。

    运算符++、--是单目运算符,仅能对变量使用。

    ++使变量的值加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; //编译出错

    算术运算符的优先级如下表所示。在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。

常用算术运算符及优先级

运 算 符

说  明

++、-- 后置单目运算符,自增减运算符
+、-、++、-- 前置单目运算符,“++、--”为自增减运算符
^ 乘方
*、/、% 乘、除、求模
+、- 加、减

3.9 逗号(冒号、分号)运算符和括号运算符  [返回页首]

    表达式中如果有多个语句,可以用逗号、冒号或分号进行分隔,Lu将按从左到右的顺序计算各个语句,并返回最后一个语句的值。也可以将多个用逗号、冒号 或分号分隔的语句放在一个括号内,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

    虽然逗号、冒号或分号运算符是通用的,但逗号的用法很常见。冒号和分号运算符仅用在一些特殊场合,例如分隔一些不同类别的变量。

    注意:一般表达式之间用分号进行分隔,故分号运算符必须放到括号内才有效。如下例:

      (: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常量  [返回页首]

    常量是指那些不需要程序计算的固定值。有数值常量和符号常量两种。如100就是一个数值常量。在Lu中,也可以用标识符表示一个符号常量。用函数const可以定义一个永久性符号常量或暂时性符号常量:

    设置常量:const("ConstStr",ConstValue)const("ConstStr",ConstValue,bMode)

    ConstStr:常量名,要符合Lu标识符的命名规定,否则编译器找不到该常量。可使用常量命名空间的方式命名,例如:"ConstName::MyConst",在这里先忽略这种命名方式。
    ConstValue:常量的值,只能是Lu基本数据类型所能保存的数据,例如整数、实数、复数、三维向量、长精度实数和逻辑值等;如果是字符串等动态对象,只保存该对象的指针,如果该对象被销毁,该常量的值无意义。当bMode为逻辑假时,该值被忽略。
    bMode:逻辑值,指出工作方式。若缺省该参数(函数const只有两个参数),创建一个永久性常量,无法删除一个永久性常量,除非Lu重新初始化。若bMode为true,创建一个暂时性常量;若bMode为false,删除一个暂时性常量。暂时性常量保持常量的基本意义,在编译表达式时不可改变其值,但可被const函数删除。

    该函数返回值的意义如下:

      0:设置成功。
      1:已存在该常量。
      2:内存分配失败。
      3:不能用空字符串作为常量名。
      4:参数不符合要求。
      5:需用字符串指出常量名。
      6:不能删除该常量。

    只有const函数被执行后,定义的常量才会起作用。如下例:

      !!!const("pi",3.1415926);  //定义永久性常量,不可删除 ;行首的!!!表示该表达式编译后立即执行(且以后不再执行),否则要等到全部源代码编译完后执行
      !!!const("aa",222,true);  
//定义暂时性常量,可以删除

      pi;           //pi=3.1415926
      aa;           
//aa=222
      const("pi",0,false);  
//返回值为6。试图删除永久性常量,但失败了
      const("aa",0,false);  //返回值为0。删除暂时性常量

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

5.1 自变量  [返回页首]

    自变量是函数(表达式)参数中声明的变量,用于向表达式传递参数,称为形式参数 ,自变量只能在函数内部使用。例如:

      f(x,y)=x+y; //该函数 中x和y即自变量

      2+f[2,3];   //向函数f传递参数2和3

    结果:

      7

5.2 动态变量  [返回页首]

    动态变量 只能在函数内部使用,只在函数执行时起作用,一旦函数执行完毕,动态变量也随之失效。例如:

      f(x,y : a, b) = {a=1, b=2, x+y+a+b}; //该函数 中x和y为自变量,a和b为动态变量

      2+f[2,3];   //向函数f传递参数2和3

    结果:

      10

5.3 静态变量  [返回页首]

    静态变量存在于函数 (表达式)的整个生命周期,每次函数执行完毕,静态变量的值被保留。静态变量初始化为0。例如:

      f(x,y : static, a) =  //该函数中x和y为自变量,a为静态变量
      {
          o["a=", a, "\r\n"], 
//用o函数输出a的值,"\r\n"表示输出一个换行符
          a++,      
//a的值增1,++是自增运算符
          x+y+a
      };

      f[2,3];
      f[2,3];
      f[2,3];

    结果:

      a=0
      6
      a=1
      7
      a=2
      8

5.4 模块变量  [返回页首]

    模块变量可被同一模块的所有表达式所访问,其他模块的表达式无法访问。模块变量在第一次生成时初始化为nil。

    Mlu编译时,将源程序中的函数(表达式)编译为一个或多个模块。编译时首先设置起始模块,也称主模块,以后每当遇到#MODULE#,开始编译为一个新的模块,称为子模块,而每当遇到#END#,回到主模块的编译。即#MODULE##END#之间的表达式定义为一个子模块,子模块之间不能嵌套定义。注意#MODULE##END#必须位于表达式的开头。

    在模块中,以:::开头的表达式被编译为公有表达式(全局表达式或全局函数),能被其他模块访问到,其余的表达式均被编译为私有表达式(私有函数),其他模块无法访问。另外,若表达式名称前有编译符!!!,在编译后将立即执行;若表达式名称前有编译符~~~,只编译,不执行。

    (1)模块源文件的格式

      !!!const("pi",3.1415926);  //主模块中的表达式,定义永久性常量,该表达式是在编译时执行的

      #MODULE#              //定义一个子模块

        a(x)=x+1;            //私有函数,只能被该模块的表达式所访问

        :::b(x)=a(x)+2;        //全局函数,任意模块包括本模块均可访问

      #END#                  //子模块定义结束,可缺省

      #MODULE#              //定义一个子模块

        a(x)=x-1;           //私有函数,只能被该模块的表达式所访问

        :::c(x)=a(x)+2;       //全局函数,任意模块包括本模块均可访问

      #END#                  //子模块定义结束,不可缺省

      a(0);                  //主模块中的表达式 ,编译时将提示“不可识别函数名”

      b(0);                  //主模块中的表达式

      c(0);                 //主模块中的表达式

    结果(去掉主模块中的a函数调用):

      3
      1

    (2)模块变量例子

      #MODULE#              //定义一个子模块

        !!!a( :: s) = s=1;        //私有函数, 编译时立即执行,使得模块变量s=1

        :::b(x :: s) = s+x;        //全局函数,任意模块包括本模块均可访问

      #MODULE#              //定义一个子模块

        !!!a( :: s) = s=-1;        //私有函数, 编译时立即执行,使得模块变量s=-1

        :::c(x :: s) = s+x;        //全局函数,任意模块包括本模块均可访问

      #END#                  //子模块定义结束,不可缺省

      b(1);                  //主模块中的表达式

      c(1);                 //主模块中的表达式

    结果:

      2
      0

    (3)使用未定义的模块变量

    在MLu中,可使用编译符mvar:通知编译器使用未定义的模块变量 (这并不是一个好方法,因为会由于一些相似的变量的误用而导致难以查找的错误,但这个方法使代码看起来简单,故本文某些地方也使用该编译符;在大的工程中,建议将变量定义在函数中迫使编译器对变量进行检查),使用编译符unmvar:通知编译器取消这种设置,格式如下:

      mvar:        //通知编译器使用未定义的模块变量

      s, t, u=5, v=6; //如果不存在同名的常量,就解释为模块变量

      s=1, 2+s+u;  //可以正确编译,返回值=8

      unmvar:      //通知编译器取消使用未定义的模块变量

      2+s+u;       //编译出错,变量不可识别

    根据标识符解释规则,确定变量或常量的顺行是:变量、常量、常量命名空间(在这里先忽略)。若要确保编译器将未定义的标识符解释为模块变量,则应在前面编译的表达式中,将该标识符明确地定义为模块变量并至少使用一次,如下例:

      !!!const["v",8,true]; //创建一个常量v=8

      v;            //v为常量,返回值=8

      mvar:          //通知编译器使用未定义的模块变量

      v;           //v仍为常量,返回值=8

      (::v)= v=6;   //将v定义为模块变量并至少使用一次 ,返回值=6

      v;          //v将解释为模块变量,返回值=6

      unmvar:        //通知编译器取消使用未定义的模块变量

      v;          //v将解释为常量,返回值=8

    (4)将源文件保存为磁盘模块文件来使用

    模块源文件如下:

      //模块名:myModule

      #MODULE#              //定义一个子模块

        !!!a( :: s) = s=1;        //私有函数, 编译时立即执行,使得模块变量s=1

        :::b(x :: s) = s+x;      //全局函数,任意模块包括本模块均可访问

      #END#               //子模块定义结束,不可缺省

      :::c(x) = b(x)+1;         //主模块中的全局函数,只有定义为全局函数,才能被外部使用

    将以上源文件命名为“myModule.txt”(文件命名没有特殊要求),保存到目录“OpenLu64\module”(即程序OpenLu64所在的文件夹module)中。

    在源程序的任何地方,可用指令#USE#调用另一个模块。

      #USE# module\myModule.txt;  //关键字USE必须为大写 ,后面给出模块文件名(必要时包含完整路径)。

      b(0);      //调用myModule模块中定义的全局函数

      c(0);      //调用myModule模块中定义的全局函数

    结果:

      1
      2

    也可以将模块文件加到OpenLu工作区中,使用更方便,不过需要设置一下。

    打开文件夹ini中的“OpenLu.ini”文件(OpenLu工作区设置文件),找到关键字“#MODULE”(用于设置模块),在前面添加如下代码:

      #MODULE
      {
        //定义模块“myModule”,模块文件为“Module\myModule.txt”。

        "myModule*Module\myModule.txt"

        ... ...
      }

    执行菜单“设置->重载模块文件”,使新加入的模块可用。

    如此,可用指令#USE#调用新加入的模块myModule。

      #USE# myModule;  //关键字USE必须为大写 。myModule是模块名称,myModule必须在OpenLu工作区设置文件中用关键字“#MODULE”说明

      b(0);      //调用myModule模块中定义的全局函数

      c(0);      //调用myModule模块中定义的全局函数

    结果:

      1
      2

5.5 全局变量  [返回页首]

    全局变量可被所有的函数(表达式)所访问。全局变量的使用要避免命名重名,尽量少用全局变量。

      #MODULE#       //定义一个子模块

        a( :: common, s) = s=1;;  //私有函数,全局变量s=1

      #END#       //子模块定义结束,不可缺省

      a();         //主模块中的表达式,编译时将提示“不可识别函数名”

      ( :: common, s) = s;  //主模块中的无参表达式,可以获取全局变量的值

    结果(去掉主模块中的a函数调用):

      1

Lu数据类型  [返回页首]

    Lu表达式中会存在许多类型的数据,如整数、实数、字符串、逻辑值、函数句柄等等,但在其内部实现上,所有的数据类型都使用同一种结构形式,即:

    typedef __int64 luIFOR;    //Lu整数类型定义
    typedef __int32 luKEY;     
//Lu键值定义

struct LuData{    //Lu基本数据结构
    luIFOR x;     
//存放数据。对于动态数据类型,对象指针约定保存在x中
    luIFOR y;     
//存放数据
    luIFOR z;     
//存放数据
    luKEY  VType; 
//扩展数据类型,决定重载函数
    luKEY  BType; 
//基本数据类型,决定数据结构
};

    任何Lu数据在赋值时,必须提供基本数据类型BType和扩展数据类型VTypeBType决定了实际的数据结构;VType指出要调用哪一个重载函数。

    根据实际需要,Lu基本数据类型也可以定义为以下形式或者其他形式(任何数据长度不超过24字节的数据都可以直接保存):

struct LuDataF{   //Lu基本数据结构
    double x;
    double y;
    double z;
    luKEY  VType; 
//扩展数据类型,决定重载函数
    luKEY  BType; 
//基本数据类型,决定数据结构
};

    要看懂以上内容,需要一些C/C++的基础知识。看不懂也不要紧,不影响学习以下内容。

    Lu基本数据类型共32个字节长度,类型说明占8个字节,剩余24个字节用于保存数据。整数、实数、复数、三维向量、nil、逻辑值等均可直接用基本数据表示;另外,Lu脚本在函数(表达式)中可开辟一定空间用于保存静态字符串及其衍生的静态数组类型;其他Lu数据需要动态申请后使用,称动态数据类型,动态数据类型通常用函数new或者其他专用函数生成,用完后用del或者delete函数释放,或者由Lu的垃圾回收系统回收。

    Lu基本数据类型及其静态数据类型的运行速度高于动态数据类型。

    Lu常量和变量的存储空间与Lu基本数据类型相同,故可直接保存Lu基本数据类型;其他Lu数据在常量和变量中只保存一个指向该数据的指针,该指针是否有效在Lu系统的内部是要做检验的,故在脚本中我们无需考虑,如果指针无效,脚本运行时将返回一个运行错误。

    由以上可以知道,变量赋值时,如果数据大小不超过Lu基本数据类型,就直接保存该数据(视同指向该数据),否则保存一个指针指向该数据。

6.1 整数  [返回页首]

    整数是没有小数部分的数字,整数运算永远是精确的。Lu整数是64位的(范围从-9223372036854775808~9223372036854775807,如果整数太大或者太小会发生溢出,须做检测),既可以是10进制数,也可以是16进制数,但数字中不能包含小数点,也不能用科学记数法表示数字。16进制整数以0x开头,并用A~F表示10~16。

    例子:

      12

      0x1A

      0x123D

    在整数运算中,除法运算需要特别注意:(1)除数不能为0;(2)两个整数相除仍然是一个整数。

    例如:

      10/2;  //结果为5

      10/3;  //结果为3,余数被舍弃

    小学中我们学习过四则运算法则:当一级运算(加减)和二级运算(乘除)同时出现在一个式子中时,它们的运算顺序是先乘除,后加减,如果有括号就先算括号内后算括号外,同一级运算顺序是从左到右 :

      1+2*3;  //结果为7

      1+2*(3+4);  //结果为15

    Lu表达式中的运算法则与此类似,但由于运算符多,情况更加复杂,建议用括号区分计算顺序,因为括号里面的总是要先算的。

    Lu核心库中整数运算的运算符及优先级(同一类型的运算符优先级相同,不同类型运算符的优先级从上往下依次降低)如下表(初值 a=10, b=3, c=2, d=0):

运算符类型

运算符 名称 例子 结果

说  明

后置单目运算符 ++ 后置自增 a++ 10 执行 e = a + b++ 后,e=13,b=4
-- 后置自减 a-- 10 执行 e = a + b-- 后,e=13,b=2
前置单目运算符 ! !a false

对整数求非时,返回逻辑值,且规定 !0 = true,其他情况均返回false

!d true
+ +a 10
- -a -10
++ 前置自增 ++a 11 执行 e = ++a + b 后,e=14,a=11
-- 前置自减 --a 9 执行 e = --a + b 后,e=12,a=9
!! 按位非 !!a -11 整型数据在内存中以补码表示,请查阅相关资料以理解此运算
乘方算术运算符 ^ 乘方 a^b 1000. 结果为实数
乘除算术运算符 * a*b 30

/ 左除 a/b 3 两个整数相除仍然是一个整数,余数被舍弃

整数/0将返回一个运行错误,运行错误说明:Integer division by zero,错误代码LuErr=1

a/c 5
c/a 0
% 求模 a%b 1 表示 a 除以 b 的余数
加减算术运算符 + a+b 13
- a-b 7
移位运算符 << 左移位 a<<1 20 整型数据在内存中以补码表示,请查阅相关资料以理解此运算

如何对非整型数据做移位运算呢?可先用cast函数对数据做强制类型转换就可以了,例如:

(:a)= a=1.2, a=cast(a,2), a>>1;  //cast(a,2)将数据a转换为整数类型,2表示整数

a<<5 320
a<<(-2) -9223372036854775808
>> 右移位 a>>1 5
a>>5 0
a>>(-2) 0
关系运算符 > 大于 a>b true 关系运算返回逻辑值
>= 大于等于 a>=b true
< 小于 a<b false
<= 小于等于 a<=b false
== 等于 a==b false
!= 不等于 a!=b true
按位与 && 按位与 a&&b 2 整型数据在内存中以补码表示,请查阅相关资料以理解此运算
按位异或 ~~ 按位异或 a~~b 9
按位或 || 按位或 a||b 11
$ a$b 10+3i或者{10.$3.} 并运算的结果为复数或三维向量
a$b$c {10.$3.$2.}

6.2 实数  [返回页首]

    实数也称浮点数或者小数,是含有小数点的数字,可以用科学记数法表示;浮点数运算则可能会有四舍五入的误差。Lu使用64位双精度实数(范围大致从±1.7E-308~±1.7E+308)。

    例子:

      0.0

      0.

      .0

      1.2

      -1.2E-3

    Lu核心库中实数运算的运算符及优先级(同一类型的运算符优先级相同,不同类型运算符的优先级从上往下依次降低)如下表(初值 a=10.0, b=3.1, c=2.0, d=0.0):

运算符类型

运算符 名称 例子 结果

说  明

后置单目运算符 ++ 后置自增 a++ 10. 执行 e = a + b++ 后,e=13.1,b=4.1
b++ 3.1
-- 后置自减 a-- 10. 执行 e = a + b-- 后,e=13.1,b=2.1
b-- 3.1
前置单目运算符 ! !a false 对实数求非时,返回逻辑值,且规定 !0.0 = true,其他情况均返回false
!d true
+ +a 10.
- -a -10.
++ 前置自增 ++a 11. 执行 e = ++a + b 后,e=14.1,a=11.
++b 4.1
-- 前置自减 --a 9. 执行 e = --a + b 后,e=12.1,a=9.
--b 2.1
乘方算术运算符 ^ 乘方 a^b 1258.925411794168
乘除算术运算符 * a*b 31.
/ 左除 a/b 3.225806451612903 实数/0.0将返回1.#INF,表示无穷大
a/c 5.
c/a 0.2
a/d 1.#INF
加减算术运算符 + a+b 13.1
- a-b 6.9
关系运算符 > 大于 a>b true 关系运算返回逻辑值
>= 大于等于 a>=b true
< 小于 a<b false
<= 小于等于 a<=b false
== 等于 a==b false
!= 不等于 a!=b true
$ a$b 10.0+3.1i或者{10.$3.1} 并运算的结果为复数或三维向量
a$b$c {10.$3.1$2.}

6.3 复数  [返回页首]

    Lu复数的实部和虚部都是64位双精度实数,以i结尾的数字表示复数的虚部,复数可由运算符$产生。

    例子:

      2i

      1-2i

      2$3

      -1.2E-3+2i

    Lu核心库中复数运算的运算符及优先级(同一类型的运算符优先级相同,不同类型运算符的优先级从上往下依次降低)如下表(初值 a=10+2i, b=1-2i, c=2.0, d=0.0):

运算符类型

运算符 名称 例子 结果

说  明

前置单目运算符
+ +a 10+2i或者{10.$2.}
- -a -10-2i或者{(-10.)$(-2.)}
乘方算术运算符 ^ 乘方 a^b {(-3.969653469091033)$14.60477211288343}
a^c {95.99999999999999$39.99999999999999}
乘除算术运算符 * a*b {14.$(-18.)}
/ 左除 a/b {1.2$4.4} 复数/0.0将返回{1.#INF$1.#INF},表示无穷大
a/c {5.$1.}
c/a {0.2$0.1}
a/d {1.#INF$1.#INF}
加减算术运算符 + a+b {11.$0.}
- a-b {9.$4.}
$ a$c {10.$2.$2.} 并运算的结果为三维向量

6.4 三维向量  [返回页首]

    Lu三维向量的每一维都是64位双精度实数,三维向量可由运算符$产生。

    例子:

      (1$2$3)

      (1-2i$3)

    Lu核心库中三维向量运算的运算符及优先级(同一类型的运算符优先级相同,不同类型运算符的优先级从上往下依次降低)如下表(初值 a=(1$2$3), b=(5$6$7)):

运算符类型

运算符 名称 例子 结果
前置单目运算符 + +a {1.$2.$3.}
- -a {(-1.)$(-2.)$(-3.)}
乘算术运算符 * a*b {(-4.)$8.$(-4.)}
加减算术运算符 + a+b {6.$8.$10.}
- a-b {(-4.)$(-4.)$(-4.)}

6.5 混合运算隐式转换规则  [返回页首]

    当不同类型的数据进行操作时,首先将其转换成相同的数据类型,然后进行操作,转换规则是由低级向高级转换(所谓高低,是指表示的范围大小。通常和内存存储空间大小及 数据类型有关)。

    例如:

      1+2.0;  // 先将整数1转换为实数1.0,然后与2.0相加,结果为3.0

      10/3/2.0;  // 10/3为整数运算,结果为3,然后3/2.0,结果为1.5

    Lu表达式中各种数值数据进行混合运算时,其隐式转换规则如下表:

数据类型

整数 实数 复数 三维向量
整数 整数 实数 复数  
实数 实数 实数 复数  
复数 复数 复数 复数  
三维向量       三维向量

6.6 逻辑值  [返回页首]

    在Lu中关系运算符和逻辑运算符将返回逻辑值。逻辑值只有两个:逻辑真true和逻辑假false。在Lu中用0表示逻辑假,其他任何非0值表示逻辑真。

    关系运算是对两个值的大小进行比较,返回一个逻辑值。例如:

      3>2;       //返回true
      2>3;       //返回false

    关系运算符共有6个:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于)。

    逻辑值之间的运算称逻辑运算,逻辑运算的结果仍然是一个逻辑值。有四个逻辑运算符:&(逻辑与)、|(逻辑或)、!(逻辑非)、~(逻辑异或)。通过下面给出的真值表可以掌握这几种运算。表中用1代表逻辑真,0代表逻辑假。

真值表

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

    关系运算符和逻辑运算符的优先级如下表所示。在表中,同一行中运算符优先级相同,不同行中运算符的优先级从上往下依次降低。

关系运算符和逻辑运算符及优先级

运 算 符

说  明

! 逻辑非
>、>=、<、<=、==、!= 关系运算符
& 逻辑与
~ 逻辑异或
| 逻辑或

6.7 nil  [返回页首]

    在Lu中 nil 表示未定义的Lu数据或某个运算失败的返回值。 系统扩展库sys的函数isnil可以判断是否是一个nil,该函数返回一个逻辑值。例如:

      sys::isnil( nil );      //返回true

      sys::isnil( 0 );       //返回false

      sys::isnil( 1 );       //返回false

6.8 函数(表达式)句柄  [返回页首]

    在函数调用时,有时候需要用表达式(即自定义函数)作为函数的参数。可以用编译符 @ 或者函数 HFor("ForName") 获得表达式句柄(也称为表达式指针、函数指针 、函数句柄),注意只能获得全局函数的句柄

    (1)用编译符@获得函数句柄:编译时获得

    例子:

      a(x)=x+8;        //定义一元函数a

      b(f,x)= f(x)+2;  //定义函数b,接受一个一元函数句柄f

      b[@a,2];        //调用函数b

    结果:

      12

    (2)用函数HFor("ForName")获得函数句柄:运行时获得

    例子:

      a(x)=x+8;        //定义一元函数a

      b(f,x)= f(x)+2;  //定义函数b,接受一个一元函数句柄f

      b[HFor("a"),2];  //调用函数b

    结果:

      12

6.9 静态字符串  [返回页首]

    在Lu中使用Unicode宽字符串,一个宽字符有2个字节,用两个双引号定义一个字符串,即"..."表示一个字符串。在Lu字符串中,还可以用反斜杠转义字符输入一些特殊字符,见下表。

反斜杠转义字符

\\ 反斜杠“\”
\" 双引号“"”
\% 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;若字符串最前面有转义字符\&, 则每个单元取系统字长(64位系统为8个字节),此时k=4,这种字符串格式可转换为整数、实数或复数数组。例如: 任意表达式中,"\[256]" 是一个256个字符长的空格符字符串。"\&\[256]" 是一个256×4个字符长 (占用256×8个字节)的空格符字符串。

    除了表中定义的之外,Lu没有定义其它的转义字符(“\”和其他字符的组合都是非法的)。 可以看出,反斜杠“\”和双引号“"”只能通过转义字符输入。

    另外,若字符串前有符号@(例如:@"...\r\n..."),将忽略转义字符。

    例如:

      o("hello!\r\n字符串!!!\r\n");;   //用o函数输出字符串

      o(@"hello!\r\n字符串!!!\r\n");;  //用o函数输出字符串

    结果:

      hello!
      字符串!!!
      hello!\r\n字符串!!!\r\n

    可以用字符串存储任意类型的数据值,如下面将要讲到的静态数组。

    Lu字符串有二种类型:Lu静态字符串和Lu动态字符串。Lu静态字符串即在Lu表达式中定义的字符串(生命周期与该表达式相同),长度是固定的,不能随意进行销毁(但Lu静态字符串也并非一成不变);Lu动态字符串由函数 new 生成。

    以下是静态字符串发生改变的例子:

      ( : p ) =    //定义自动变量p

          p = "abcd",  //p指向字符串,可以看成是指向字符串的指针

          p[0] = "A",  //p的首字母赋值为"A",任何字符串首字母序号为0;Lu脚本中任何有序数据的首序号均为0

          p[1] = "B",  //p的第二个字母赋值为"B"

          p;      //表达式计算结果为p,默认输出该结果

    结果:

      ABcd

6.10 静态数组  [返回页首]

    Lu数组有静态和动态两种。

    Lu静态数组是一维的,通过一组函数操作静态字符串来实现。使用数组之前,须先用转义字符"\[n]"定义足够的静态空间。 可以通过数组句柄(指针)直接访问数组元素,如下例:

    例子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个英文字母。while是“当型”循环函数:先判断条件是否成立,当条件成立时,则执行循环体,否则退出循环体

    o(str)                //输出字符数组str
};;

    结果:

      **abcdefghijklmnopqrstuvwxyz**

    例子2:静态整数数组,字符串必须定义成"\&... ..."格式。

( : i, k, a, s) =
{
    a=intss("
\&\[10]"),       //定义4×10个字符长的字符串(占用8×10个字节),并用intss函数转换为长度为10的一维实数数组

    k=len(a),             //用函数len获得数组长度

    i=0, while{i<k, a(i)=1, i++},  //将数组的每一个元素都设为1

    i=0, s=0, while{i<k, s=s+a(i++)}, //将数组的所有元素都加起来

    s                //输出相加后的值
};

    结果:

      10

    例子3:静态实数数组,字符串必须定义成"\&... ..."格式。

( : i, k, a, s) =
{
    a=realss("
\&\[10]"),       //定义4×10个字符长的字符串(占用8×10个字节),并用realss函数转换为长度为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                //输出相加后的值
};

    结果:

      10.

    以下是函数intss和realss的用法:

    (1)将静态字符串转换为静态一维整数数组 intss("...",x1,x2,x3,...):参数x1,x2,x3,...将给数组赋初值(只能是整数或实数,若是实数将截断取整)。

    (2)将静态字符串转换为静态一维实数数组 realss("...",x1,x2,x3,...):参数x1,x2,x3,...将给数组赋初值(只能是实数或整数,若是整数将自动进行类型转换)。

6.11 动态数据类型  [返回页首]

    Lu脚本中使用new函数(也可能是其他专用函数)生成新的动态对象:

      new(type,x1,x2,x3,...):type是一个整数,标识某种动态数据类型;x1,x2,x3,...是初始化参数。

    Lu核心库中定义的动态类型有lu表(lu)、动态字符串(string)、动态整数数组(ints)和动态实数数组(reals),见下表:

动态类型 标识符 例 子 说 明 推 荐
lu表 lu new[lu,5] 生成长度为5的lu表。 使用系统扩展库sys的new[sys::luu,... ...]函数申请luu表对象。该对象的基本类型为lu,扩展类型为luu。
new[lu,5 : 2, "aa", 3.3] 生成长度为5的lu表并赋初值,若有多余的初值参数,将被忽略。
字符串 string new[string,5] 生成长度为5的动态字符串。 使用系统扩展库sys的new[sys::String,... ...]函数申请String对象。该对象的基本类型为string,扩展类型为String。
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] 生成二维数组(矩阵)。 使用数学库math的new[math::real_s,... ...]函数申请数组或矩阵。该对象的基本类型为reals,扩展类型为real_s。
new[reals,3,5,data: 1.1,2.2,3.3] 生成二维数组(矩阵)并赋初值,多余的参数被忽略。
new[reals,data: 1.1,2.2,3.3] 生成一维数组并赋初值,数组长度即初值参数个数。

    6.11.1 lu表

    Lu动态数据存放在一个线性表中,简称lu表,lu表中可以存放任意类型的数据,包括nil和lu表。lu表还可以使用lu函数生成:lu(a,b,c,...),但用new函数创建lu表时可指定lu表的长度 。

    lu表是Lu的基本动态数据类型。函数以lu表为参数时,可实现变参数传递。在垃圾收集时,若一个lu表是有效的,则该lu表中的所有对象都是有效的,不会被垃圾收集器所收集。

    例子1:

(:a,b) =

  a=lu[2, 3.5, "me", lu[5.5, true]],  //嵌套lu表

  b=a[3],  //b指向子lu表

  o[a(0),"  ",a(1),"  ",a(2),"  ",b(0),"  ",b(1),"  "];;

    结果:

      2 3.5 me 5.5 true

    例子2:

(:a,b)=

  a=new[lu, 4: 2, 3.5, "me", lu[5.5, true]],  //new函数生成的嵌套lu表

  b=a[3],

  o[a(0),"  ",a(1),"  ",a(2),"  ",b(0),"  ",b(1),"  "];;

    结果:

      2 3.5 me 5.5 true

    例子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个字符串相加

    结果:

      15

      6.

      abc123xyz

    6.11.2 动态字符串

o{ new[string, "动态字符串:"] + "hello!"};;  //动态字符串可以使用 + 连接两个字符串

    结果:

      动态字符串:hello!

    6.11.3 动态整数数组

main( : i, j, m, n, p, z, t)=
{

    p=new(ints, 2000, 5000),  //生成二维整数数组

    p.len(&z, &m, &n),  //获取数组维数 z 及每一维的大小 m 和 n

    z=0,

    t=clock(),  //获取时间

    i=-1, while{++i<m,

        j=-1, while{++j<n, p[i,j]=1, z=z+p[i,j]}  //数组元素赋值及使用

    },

    o{"z=",z,",存取次数=",m*n,",耗时约",[clock()-t]/1000.0,"秒。\r\n"}
};;

    结果:

      z=10000000,存取次数=10000000,耗时约2.032秒 。

    6.11.4 动态实数数组

main( : i, j, m, n, p, z, t)=
{

    p=new(reals, 2000, 5000),  //生成二维实数数组

    p.len(&z, &m, &n),  //获取数组维数 z 及每一维的大小 m 和 n,取地址运算符 & 使得函数len可以修改z、m及n的值

    z=0.0,

    t=clock(),  //获取时间

    i=-1, while{++i<m,

        j=-1, while{++j<n, p[i,j]=1.0, z=z+p[i,j]}  //数组元素赋值及使用

    },

    o{"z=",z,",存取次数=",m*n,",耗时约",[clock()-t]/1000.0,"秒。\r\n"}
};;

    结果:

      z=10000000.,存取次数=10000000,耗时约2.266秒。

6.12 数组操作函数  [返回页首]

    Lu内置了Unicode字符数组、整数数组、实数数组、复数数组和三维向量数组的专用操作函数。

    6.12.1 数组元素存取 A(p,...):

    该函数效率较高,但仅对如下数组元素进行存取(存取时仅对数据的基本类型做检查,忽略扩展类型)。

    (1)静态字符串(一维静态字符数组)

(:p)= p="abc", A[p,1]=66, A[p,1];  //返回 66

    (2)静态(一维)整数数组

(:p)= p=intss("\&\[5]"), A[p,1]=66, A[p,1];  //返回 66

    (3)静态(一维)实数数组

(:p)= p=realss("\&\[5]"), A[p,1]=66.77, A[p,1];  //返回 66.77

    (4)静态(一维)lu表

(:p)= p=lu[1,1,1], A[p,1]=66.77, A[p,1];  //返回 66.77

    (5)动态字符串(一维动态字符数组)

(:p)= p=new[string,5], A[p,1]=66, A[p,1];  //返回 66

    (6)动态(多维)整数数组

(:p)= p=new[ints,3,5], A[p,1,3]=66, A[p,1,3];  //返回 66

    (7)动态(多维)实数数组

(:p)= p=new[reals,3,5], A[p,1,3]=66.77, A[p,1,3];  //返回 66.77

    6.12.2 复数数组元素存取 A2(p,...):

    该函数仅对如下数组元素进行存取(存取时仅对数据的基本类型做检查,忽略扩展类型)。

    (1)静态字符串(看作一维静态复数数组,一个数组元素占16个字节,相当于8个Unicode字符长度)

(:p)= p="\[32]", A2[p,1]=66+77i, A2[p,1];  //返回 {66.$77.}

    (2)静态(一维)实数数组(看作一维静态复数数组,一个数组元素占16个字节,相当于2个实数长度)

(:p)= p=realss("\&\[6]"), A2[p,1]=66+77i, A2[p,1];  //返回 {66.$77.}

    (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];  //返回 {66.$77.}

    6.12.3 三维向量数组元素存取 A3(p,...):

    该函数仅对如下数组元素进行存取(存取时仅对数据的基本类型做检查,忽略扩展类型)。

    (1)静态字符串(看作一维静态三维向量数组,一个数组元素占24个字节,相当于12个Unicode字符长度)

(:p)= p="\[36]", A3[p,1]=(3$5$7), A3[p,1];  //返回 {3.$5.$7.}

    (2)静态(一维)实数数组(看作一维静态三维向量数组,一个数组元素占24个字节,相当于3个实数长度)

(:p)= p=realss("\&\[6]"), A3[p,1]=(3$5$7), A3[p,1];  //返回 {3.$5.$7.}

    (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];  //返回 {3.$5.$7.}

6.13 由编译符#生成的整数  [返回页首]

    由编译符 # 生成,放在标识符的前面,生成与标识符对应的唯一整数,只要Lu系统没有重新初始化,该整数就唯一,常用于系统扩展设计。例如:

      sys::printf["%d\r\n", #abc];;  //用系统库sys的库函数printf输出了该整数,格式符"%d"表示输出一个整数

    由编译符 # 生成的整数支持关系运算符 == 和 != ,例如:

      #abc == #abc;  //返回 true

      #abc == #ab;   //返回 false

      #abc != #ab;   //返回 true

7 Lu运算符及优先级  [返回页首]

    Lu中的运算符及优先级如下表所示,在表中,同一类型的运算符优先级相同,不同类型运算符的优先级从上往下依次降低。

Lu运算符及优先级

运算符类型

运算符 名称 是否允许重载 核心库支持的运算

说  明

双括号连接运算符 :=     双括号连接 冒号前和等号后都必须是括号
单括号连接运算符 ( )=     单括号连接 等号后是一个表达式
[ ]=     单括号连接
{ }=     单括号连接
括号运算符 ( ) 小括号   括号运算 返回最后一个语句的值
[ ] 中括号   括号运算
{ } 大括号   括号运算
命名空间成员访问符 :: 双冒号   访问命名空间成员 访问命名空间成员
对象成员运算符 .   访问对象成员或传递函数参数 也称为函数参数运算符,或者变量函数调用运算符
后置单目运算符 ++ 后置自增 整数、实数 后置单目运算符(自增、自减、转置、点转置)
-- 后置自减 整数、实数
' 转置 未定义
.' 点转置 未定义
前置单目运算符 ! 逻辑值、整数、实数 前置单目运算符(非、正、负、自增、自减、按位非)

对整数或实数求非时,返回逻辑值,且规定!0=true,!0.0=true,其他情况均返回false

+   被忽略
- 整数、实数、复数、三维向量
++ 前置自增 整数、实数
-- 前置自减 整数、实数
!! 按位非 整数
乘方算术运算符 ^ 乘方 整数、实数、复数 算术运算符(乘方、点乘方)
.^ 点乘方 未定义
乘除算术运算符 * 整数、实数、复数、三维向量 算术运算符(乘、左除、右除、求模、点乘、点左除、点右除)

整数/0将返回一个运行错误,运行错误说明:Integer division by zero,错误代码LuErr=1

/ 左除 整数、实数、复数
\ 右除 未定义
% 求模 整数
.* 点乘 未定义
./ 点左除 未定义
.\ 点右除 未定义
加减算术运算符 + 整数、实数、复数、三维向量、字符串 算术运算符(加、减)
- 整数、实数、复数、三维向量
移位运算符 << 左移位 整数 左移位、右移位
>> 右移位 整数
关系运算符 > 大于 整数、实数 关系运算符(大于、大于等于、小于、小于等于、等于、不等于)
>= 大于等于 整数、实数
< 小于 整数、实数
<= 小于等于 整数、实数
== 等于 整数、实数、编译符#生成的整数
!= 不等于 整数、实数、编译符#生成的整数
按位与 && 按位与 整数 按位与
按位异或 ~~ 按位异或 整数 按位异或
按位或 || 按位或 整数 按位或
逻辑与 & 逻辑与 逻辑值 逻辑与
逻辑异或 ~ 逻辑异或 逻辑值 逻辑异或
逻辑或 | 逻辑或 逻辑值 逻辑或
$ 整数、实数、复数 核心库并运算的结果为复数或三维向量
赋值运算符 = 赋值   赋值 赋值运算符
对象赋值运算符 .= 对象赋值   对象赋值 变量函数调用运算符与赋值运算符的结合,一般用于对象赋值
语句分隔符 , 逗号   分隔语句 逗号、冒号、分号运算符
: 冒号   分隔语句
; 分号   分隔语句

    括号连接运算符应用举例:

      (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运算符在表没有列出:&(取变量的地址)。取地址运算符&只能用于(被逗号或冒号隔开的)单独的变量,与其他运算符不发生任何关系,所以这个运算符在表中没有列出,其用法我们将在下面详细介绍。

    对象成员运算符(函数参数运算符)“.”、对象赋值运算符“.=”及命名空间成员访问符“::”也将在下面详细介绍。

8 Lu流程控制  [返回页首]

    在Lu中,表达式的各个语句一般是顺序执行的。但是某些函数可以改变语句执行的顺序,称为流程控制函数。

    8.1 立即返回函数 return(x)

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

    8.2 挂起协程并返回函数 yield(x)

    挂起协程并立即返回表达式的值为x。下次执行表达式时将从yield(x)的下一条语句开始。(先忽略此内容)

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

    当逻辑语句x的值为真(或者对象x值非0)时,依次执行语句y1,y2,... ...,yn,否则,不执行语句y1,y2,... ...,yn。

    该函数至少要有2个自变量参数,其中第一个参数为逻辑语句。

    该函数的返回值无意义。

    8.4 分段函数

      which{

        逻辑语句1 : 语句1,

        逻辑语句2 : 语句2,

        ... ...,

        逻辑语句n : 语句n,

        缺省语句
      };

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

    该函数至少要有2个自变量参数。

    例如下式定义了一个分段函数:

      f(x)=which{

            x>0 : 2*x-1,

            x*x-1

          };

    如果舍弃该函数的返回值,则该函数可以作为一个选择计算函数使用。

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

    也可以将循环增量写在前面(使用括号运算符):

      (:i,k)=
      {

        i=-1, k=0,

        while{(i++, i<=1000000), k=k+i}, //当i<=1000000时,计算k=k+i,然后i++

        k
      };

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

    可以使用until函数实现语句的跳转,如下列:

      until{

        x1,

        x2,

        ...,

        break(),   //跳出循环体,即跳到until函数之后

        ...,

        continue(), //跳到until开头,即 x1 语句处

        ...,

        1 //设为1,使本循环体代码只执行一次
      
};

    注意:
    (1)break()和continue()是两个无参函数,只能用在while和until两个循环函数中。如果循环进行了多重嵌套,break()只跳出最近一层循环,continue()只跳转到最近一层循环的起始处。
    (2)Lu支持多线程,在多线程的程序中,如果不慎进入了无限循环,可以通过另一个线程退出。

9 Lu函数  [返回页首]

    Lu表达式有无名表达式和有名表达式两种,有名表达式即函数。 关于函数,前面多有介绍,本部分内容主要是查漏补缺。

    函数是Lu最为重要的特性。一个函数通过传递给它的参数完成一个特定的功能。函数都有一个名字,可通过调用函数名并传递参数来使用函数。所有的函数都将返回一个函数值。典型的函数调用方法如下:

      函数名(参数1, 参数2, ... ...)

    函数可以有零个或多个参数,但即便有零个参数,函数名后的括号也不能缺省。函数调用时,参数一定要匹配。

    在Lu中可以使用的函数有三种:一级函数、二级函数和自定义函数(表达式)。其中一级函数为系统内置的函数,运算速度快;二级函数部分为Lu核心库内置的函数,部分为Lu扩展库中的函数,功能非常丰富;自定义函数实际上就是一个有名表达式,由用户定义。

    实际上,Lu还有一类为数不多的函数,称为流程控制函数,不过对于它们,函数的意义并不明显,更多的是流程控制的意义,因此我们不把它们包括在上面的分类中。

    Lu中的一级函数见下表。

一级函数

函 数 参数类型 是否允许重载

说 明

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的余数

    Lu中的二级函数种类众多,本教程按需介绍,这里就不展开了。

    这里主要介绍自定义函数,在不引起误解的情况下,仍称之为函数。

    9.1 函数(表达式)定义

    在这里,我们总结性地给出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")获得表函数句柄是在运行期进行。

    获得表达式的句柄 HFor("ForName",bType,&nPara,&hModule,&bParaModify):

    ForName:表达式的名称。可以是简单名称,也可以是包含模块命名空间访问符::(该访问符可简化为一个或多个:)的复杂名称。
    
bType:名称的类型。如果bTypetrue,只能取简单名称。如果bTypefalse,名称可包含模块命名空间访问符::。 若缺省该参数,默认为true
    
nPara:返回表达式的参数个数。该参数可以缺省。
    hModule:返回表达式所在的模块。该参数可以缺省。
    bParaModify:返回一个逻辑值,表示表达式的自定义参数在执行过程中是否可能被修改,逻辑真表示可能被修改。该参数可以缺省。

    9.2 函数中的变量

    Lu有五种变量,即:自变量、动态变量、静态变量、模块变量和全局变量。参见:Lu变量

    9.3 函数的传值调用和传址调用

    传值调用是把变量值拷贝到被调函数的形式参数中,函数在执行时,参数的改变不会影响到调用函数时所用的变量。

    传址调用(也叫引用调用)是把变量的地址拷贝到被调函数的形式参数中,函数在执行时,参数的改变会影响到调用函数时所用的变量。

    通常,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

    9.4 用表达式作为函数的参数

    参考:函数(表达式)句柄

    9.5 全局函数和私有函数

    Mlu编译时,将源程序中的函数(表达式)编译为一个或多个模块。编译时首先设置起始模块,也称主模块,以后每当遇到#MODULE#,开始编译为一个新的模块,称为子模块,而每当遇到#END#,回到主模块的编译。即#MODULE##END#之间的表达式定义为一个子模块,子模块之间不能嵌套定义。注意#MODULE##END#必须位于表达式的开头。

    在模块中,以:::开头的表达式被编译为公有表达式(全局表达式或全局函数),能被其他模块访问到,其余的表达式均被编译为私有表达式(私有函数),其他模块无法访问。在大程序中,私有函数有效地避免了函数重名带来的麻烦。

    模块源文件的格式:

      #MODULE#              //定义一个子模块

        a(x)=x+1;            //私有函数,只能被该模块的表达式所访问

        :::b(x)=a(x)+2;        //全局函数,任意模块包括本模块均可访问

      #END#                  //子模块定义结束,可缺省

      #MODULE#              //定义一个子模块

        a(x)=x-1;           //私有函数,只能被该模块的表达式所访问

        :::c(x)=a(x)+2;       //全局函数,任意模块包括本模块均可访问

      #END#                  //子模块定义结束,不可缺省

      a(0);                  //主模块中的表达式 ,编译时将提示“不可识别函数名”

      b(0);                  //主模块中的表达式

      c(0);                 //主模块中的表达式

    结果(去掉主模块中的a函数调用):

      3
      1

10 Lu错误处理  [返回页首]

    错误有编译错误和运行错误两种。通常,Lu程序总是准确定位编译期错误的。

    若程序编译通过,在运行时仍会发生错误,通常,Lu程序总是记住并给出第一个运行错误的信息,错误信息包括运行出错的表达式的类型和名称(可见,给每一个表达式都起一个名字是非常重要的)、表达式所在的模块、运行出错的函数名、错误代码等等,用户可根据这些信息查找并修改错误。

    通常,在OpenLu中会直接定位运行错误的位置:代码窗口的光标位置即运行出错的位置。

    下面演示一个较复杂的运行错误定位的例子。

    第一个模块源文件如下:

      //模块名:errModule

      #MODULE#              //定义一个子模块

        a(x) = x + nil;        //私有函数,执行该函数将出错,因为没有定义变量与nil的相加运算

        :::b(x) = a(x) + 1;      //全局函数

      #END#               //子模块定义结束

      :::c(x) = b(x)+1;         //主模块中的全局函数

    将以上源文件命名为“errModule.txt”,保存到目录“OpenLu64\module”(即程序OpenLu64所在的文件夹module)中。

    第二个模块源文件如下:

      //模块名:myModule

      #USE# errModule.txt;  //用关键字USE调用errModule模块,须给出模块文件名(必要时包含完整路径)

      #MODULE#              //定义一个子模块

        a(x) = c(x) + 1;         //私有函数,调用了模块 errModule 中的全局函数 c

        :::d(x) = a(x) + 1;      //全局函数

      #END#               //子模块定义结束

      :::e(x) = d(x)+1;         //主模块中的全局函数

    将以上源文件命名为“myModule.txt”,保存到目录“OpenLu64\module”(即程序OpenLu64所在的文件夹module)中。

    在OpenLu中输入以下代码(直接复制粘贴即可):

      #USE# module\myModule.txt;  //用关键字USE调用myModule模块,须给出模块文件名(必要时包含完整路径)

      e(0);      //调用myModule模块中定义的全局函数

    在OpenLu中编译运行,出现运行错误,按提示打开文件“errModule.txt”,再次运行,光标即停留在出错位置。

    另外,Lu运行时使用错误处理恢复模型。模块化编译运行库MLu中提供了一组函数以支持该功能:

    (1)检测输出并清除Lu运行错误:err();

    (2)获得Lu运行错误:geterr(FunName,&FunCode,&ForHandle,&hModule,&err1,&err2);

    FunName:Lu字符串。返回出错函数名;若字符串长度太小,仅返回部分函数名;若不是一个字符串,什么也不做。当返回值等于2或3时,返回空字符串。
    FunCode:当返回值等于1时,返回函数错误代码。当返回值等于2或3时,返回0。
    ForHandle:返回出错表达式的句柄,该句柄即编译表达式时获得的句柄。若ForHandle=0,则运行出错的表达式已被销毁。
    hModule:返回出现运行错误的模块句柄,该句柄为使用Mlu的输出函数ComModule或二级函数ComModule编译源程序时得到,与源程序相关联。
    err1:返回出现运行错误时源程序中的代码当前位置,即运行至此位置时出现运行错误,故错误是当前位置的前一个运算引起的。当前位置通常有一个或多个字符,其末位置由参数err2返回。
    err2:返回出现运行错误时源程序中的代码末位置。

    返回值:运行错误的类型。返回值=0:没有运行错误; 返回值<0:运行警告;返回值=1:表达式运行错误;返回值=2:父表达式被删除,父表达式即该表达式中所调用的表达式,也称基表达式;返回值=3:该表达式中所调用的二级函数被删除;返回值=其它值:其它类型运行错误。

    (3)设置Lu运行错误:seterr(ErrType, FunName, FunCode);

    ErrType:设置运行错误的类型。ErrType=0:没有错误;ErrType=1: 表达式运行错误;不要设置ErrType=2或ErrType=3,这两个值由系统自动设置;ErrType=其它值:其它类型运行错误。
    FunName:设置出错的函数名,要求传递一个Lu字符串(长度小于255)。
    FunCode:设置函数错误代码。

    返回值:false:非法的Lu字符串地址、ErrType=2或ErrType=3;true:成功返回。

    注意:由于Lu只保存出现的第一个运行错误或警告,故Lu有可能忽略本次设置。若先设置了警告,后设置错误,则警告信息将被丢弃。

    (4)清除Lu运行错误:clearerr();

    以下例子用函数seterr设置了一个Lu运行错误,并用函数geterr捕获了该错误:

f(: a, b, c, d, e, f, g) = seterr[66, "aaa", 23], a = geterr[b="\[10]", &c, &d, &e, &f, &g], o[b];;

    Lu运行错误可由Lu系统、二级函数,或者由Lu脚本用户进行设置,可在任意位置设置运行错误,同时可在任意位置捕获该运行错误。运行错误一经设置,将始终存在,直到遇到函数clearerr()err()为止。在执行MLu模块的最后,将自动调用函数err()

    捕获Lu运行错误并进行处理后,可调用函数clearerr()清除运行错误,重启程序代码。

11 Lu递归调用  [返回页首]

    如果一个函数直接或者间接地调用了自己,称作函数的递归调用。Lu支持函数的递归调用。

    为了在Lu中使用递归,需要在表达式中用SetStackMax(n)设置好堆栈。注意n的值不能取得太大,当n取得很大时,函数递归调用虽不会溢出Lu的堆栈,但会使系统堆栈溢出,这样会使程序运行中断,丢失数据。并不需要每次运行程序都进行堆栈的设置,如果堆栈设置的合适,可以只设置一次堆栈。

    下面就是递归的最简单的例子:

      SetStackMax(1000); //设置堆栈为1000

      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!

    以下是一个交叉递归的例子。

      SetStackMax(1000);; //设置堆栈为1000

      a(x : k) = o[x, "a..."], if(x<1, return[x]), k=HFor("b"), k(x-1);    //a(...)函数中调用了b(...)函数

      b(x : k) = o[x, "b..."], if(x<1, return[x]), k=HFor("a"), k(x-1);    //b(...)函数中调用了a(...)函数

      a[10];             //启动递归程序

    结果:

      10a...9b...8a...7b...6a...5b...4a...3b...2a...1b...0a...0

12 协程  [返回页首]

    如果表达式(函数)中使用了函数yield,该表达式称为一个协程(coroutine)。协程不能递归运行,只有这一个限制,否则将产生一个运行错误。Lu协程有4种状态:正常、运行、挂起、终止。正常态是可以运行但还没有运行的状态;终止态协程是不能运行的,如果运行将返回nil。与协程相关函数有5个:

    yield(x):挂起协程并立即返回表达式(函数)的值。

    下次执行表达式时将从yield(x)的下一条语句开始。

    status(p):查询表达式(包含协程)的状态。

    p是一个表达式句柄。正常返回一个整数表示表达式状态:0:普通表达式;1:普通表达式正在运行;2:普通表达式递归运行;5:协程正常;6:协程正在运行;7:协程挂起;8:协程终止。p非法时返回nil。

    abort(p):中止挂起的协程,此时协程处于正常态。

    p是一个协程句柄,正处于挂起状态。协程中止后,可以再次从头开始执行。操作成功返回true,否则返回false。

    terminate(p):终止协程,协程处于终止态,不能运行。

    p是一个协程句柄,处于正常等待运行状态或者挂起状态。协程终止后将不能执行,执行时总返回nil。操作成功返回true,否则返回false。

    resume(p):重启一个终止的协程,协程转为正常态。

    p是一个协程句柄,正处于终止状态,重启后可以运行。操作成功返回true,否则返回false。

    例子1:

~~~f(: i)= i=0, while{(i++, i<=5), yield(i)}, 888; //编译符~~~指出该函数只编译,不自动执行

f[]; f[]; f[]; f[]; f[]; f[]; f[]; f[];

    结果(返回888时协程转为了正常态):

1
2
3
4
5
888
1
2

    例子2:协程状态转换

~~~f(: i)= i=0, while{(i++, i<=5), yield(i)}, 888;

f[];  f[];  status[@f];  f[];  abort[@f];  f[];  terminate[@f];  status[@f];  f[];  resume[@f];  f[];  f[];  f[];

    结果(对照status、abort、terminate、resume等函数的返回值很容易理解以下结果):

1       // f[ ]
2      
// f[ ]
7      
// status[@f]
3      
// f[ ]
true  
// abort[@f]
1      
// f[ ]
true  
// terminate[@f]
8      
// status[@f]
nil    
// f[ ]
true  
// resume[@f]
1      
// f[ ]
2      
// f[ ]
3      
// f[ ]

    例子3:协程效率测试

f1(x : a : c1)= a=0.0, c1=0, while{true, c1++, a = a + x, yield(a)};  //自动变量a对自变量x的值进行累加,模块变量c1记录f1的执行次数
f2(x : a : c2)= a=0.0, c2=0, while{true, c2++, a = a + x, yield(a)}; 
//自动变量a对自变量x的值进行累加,模块变量c2记录f2的执行次数
f3(x : a : c3)= a=0.0, c3=0, while{true, c3++, a = a + x, yield(a)}; 
//自动变量a对自变量x的值进行累加,模块变量c3记录f3的执行次数

main(:i, t : c1, c2, c3) =
{

    t=clock(), //获取时间

    i=-1, while{++i<10000000, which{i%5 : f1[i]; i%3 : f2[i]; f3[i]}},

    o{"协程f1的终值=",f1[0],",协程f2的终值=",f2[0],",协程f3的终值=",f3[0],"\r\n"},

    o{"协程总执行次数c1+c2+c3=",c1+c2+c3,",c1=",c1,",c2=",c2,",c3=",c3,",耗时约",[clock()-t]/1000.0,"秒。\r\n"}
};;

    结果:

协程f1的终值=40000000000000.,协程f2的终值=6666663333335.,协程f3的终值=3333331666665.

协程总执行次数c1+c2+c3=10000003,c1=8000001,c2=1333334,c3=666668,耗时约1.327秒。

    例子4:筛选数据,轮询方式工作。

out_lt_5(x) =

  while{true,

    which{x>=0 & x<5 :  {o["out_lt_5:0~4的数:", x, "\r\n"], yield(true)}; //筛选成功,挂起协程,返回true

              {o["out_lt_5:不是0~4的数。\r\n"], yield(false)}       //筛选失败,挂起协程,返回false

    }

  };

out_gt_5(x) =

  while{true,

    which{x>5 & x<=9 :  {o["out_gt_5:5~9的数:", x, "\r\n"], yield(true)}, //筛选成功,挂起协程,返回true

              {o["out_gt_5:不是5~9的数。\r\n"], yield(false)}       //筛选失败,挂起协程,返回false

    }

  };

out_other(x) = while{true, o["out_other:其他的数据:", x, "\r\n"], yield(true)};   //筛选总是成功,挂起协程,返回true

main(:x, k, i) =

  x=lu[8, 3, 22, 2, 9, 55, -33, 1],

  k=len(x), i=-1,

  while{(i++, i<k),

      o["\r\nmain:筛选数据:", x(i), "\r\n"],

      which{out_lt_5[x(i)], 0; out_gt_5[x(i)], 0; out_other[x(i)]}

  };;

    结果:

main:筛选数据:8
out_lt_5:不是0~4的数。
out_gt_5:5~9的数:8

main:筛选数据:3
out_lt_5:0~4的数:3

main:筛选数据:22
out_lt_5:不是0~4的数。
out_gt_5:不是5~9的数。
out_other:其他的数据:22

main:筛选数据:2
out_lt_5:0~4的数:2

main:筛选数据:9
out_lt_5:不是0~4的数。
out_gt_5:5~9的数:9

main:筛选数据:55
out_lt_5:不是0~4的数。
out_gt_5:不是5~9的数。
out_other:其他的数据:55

main:筛选数据:-33
out_lt_5:不是0~4的数。
out_gt_5:不是5~9的数。
out_other:其他的数据:-33

main:筛选数据:1
out_lt_5:0~4的数:1

    例子5:筛选数据,自己不能处理就交给下一个协程。

out_other(x) = while{true, o["out_other:其他的数据:", x, "\r\n"], yield(true)};

out_gt_5(x) =

  while{true,

    which{x>5 & x<=9 :  {o["out_gt_5:5~9的数:", x, "\r\n"], yield(true)},   //筛选成功,挂起协程,返回true

              {o["out_gt_5:不是5~9的数。\r\n"], out_other(x), yield(true)} //筛选失败,交给out_other处理,然后挂起协程,返回true

    }

  };

out_lt_5(x) =

  while{true,

    which{x>=0 & x<5 :  {o["out_lt_5:0~4的数:", x, "\r\n"], yield(true)},   //筛选成功,挂起协程,返回true

              {o["out_lt_5:不是0~4的数。\r\n"], out_gt_5(x), yield(true)} //筛选失败,交给out_gt_5处理,然后挂起协程,返回true

    }

  };

main(:x, k, i) =

  x=lu[8, 3, 22, 2, 9, 55, -33, 1],

  k=len(x), i=-1,

  while{(i++, i<k), o["\r\nmain:筛选数据:", x(i), "\r\n"], out_lt_5[x(i)]};; //筛选时只有一个入口out_lt_5

    结果同例子3。

    例子6:生产者与消费者(使用了扩展库LuSystem,命名空间为sys,本例中使用的函数 isnil 来自该库)。

!!!using["sys"];      //使用命名空间sys
~~~生产者(: pp, k, i : p) =
    p=nil,        //p是模块变量,用于传递数据。生产者与消费者通过p进行通信
    pp=lu[2,"aaa",6], //pp存放生产者的数据
    k=len(pp), i=0,
    while{i<k,
        o["\r\n---生产者---\r\n"],   //打印所在位置
        if{isnil(p), p=pp[i++], o["\r\n---生产者发送数据  ", p, "\r\n"]},  //p=nil时发送一个数据
        yield(true)   //挂起协程
    };
~~~消费者(: i : p) =
    i=0,         //控制变量,i为偶数时消费者才消费
    while{true,
        o["\r\n---消费者---\r\n"], //打印所在位置
        if[++i%2, yield(true)],    //i为奇数时挂起协程
        if{!isnil(p), o["\r\n---消费者输出数据  ", p, "\r\n"], p=nil}, //p!=nil时输出一个数据
        if[status(@生产者)==8, break()],   //如果生产者终止,消费者就退出
        yield(true)   //挂起协程
    };
main()=
    while{true,
        o["\r\n---main---\r\n"],  //打印所在位置
        生产者[],
        if[status(@生产者)==5, terminate(@生产者)],  //如果生产者结束,就终止生产者
        消费者[],
        if[status(@消费者)==5, break()]  //如果消费者结束,就退出
    };;

    结果:

---main---

---生产者---

---生产者发送数据 2

---消费者---

---main---

---生产者---

---消费者输出数据 2

---main---

---生产者---

---生产者发送数据 aaa

---消费者---

---消费者输出数据 aaa

---main---

---生产者---

---生产者发送数据 6

---消费者---

---main---

---消费者输出数据 6

13 Lu模块化编译与源程序  [返回页首]

13.1 源程序的一般格式

    在MLu源文件中,可以有多个Lu表达式,表达式之间用分号“;”隔开。 两个连续的分号“;;”表示前面的表达式在执行时不会输出结果。

    若表达式以mvar:开头,表示自该表达式开始,后面的表达式允许使用未定义的模块变量;若表达式以unmvar:开头,表示取消这种设置。mvar:unmvar:仅在当前模块起作用。

    在源文件中可以进行注释,注释方法与C++语言相似,即:每行两个//后的内容为注释内容;在一行内或者多行之间/*...*/之间的内容为注释内容。注释不是源程序的有效部分,但可以使程序更易读。

    举例如下:

    //这是一个例子!灰色部分为注释,不会被执行

    2+3;            //整数表达式

    2.2+3.3;        //实数表达式

    sin(2.2+3.3i);  //复数表达式

    2+3;/* 从这里开始,连续几行注释:

    333+222;

    . . . . . . ;

    555-222;

    */ sin(2.5);

    exp(2.);

13.2 程序的执行

    由于表达式有些带有参数,有些不带参数,MLu在进行处理时,对于有参数的表达式只进行编译,不进行计算。

    MLu只顺序执行不带参数的表达式。如果该表达式以两个连续的分号“;;”结尾,则该表达式只执行,不输出计算结果。

    但是,如果表达式以~~~开头,则无论是有参表达式还是无参表达式,都是只编译,不执行,格式如下:


    ~~~ 2+3;        //整数无参表达式,只编译,不执行

    ~~~ f1()=2+3;   //整数无参表达式,只编译,不执行

    ~~~ 2.0+3;      //实数无参表达式,只编译,不执行

    ~~~ f2()=2+3.0;   //实数无参表达式,只编译,不执行

    ~~~ 2+3i;       //复数无参表达式,只编译,不执行

    ~~~ f3()=2+3i;   //复数无参表达式,只编译,不执行

    无参表达式f1、f2和f3可以在其他可执行的表达式中被调用。

    另外,如果无参表达式以感叹号!!!开头,则编译后立即执行,且以后执行模块时不再自动执行。格式如下:

    !!! 2+3;        //整数无参表达式,编译成功,立即执行,以后不再自动执行

    !!! f1()=2+3;    //整数无参表达式,编译成功,立即执行,以后不再自动执行

    !!! 2.0+3;      //实数无参表达式,编译成功,立即执行,以后不再自动执行

    !!! f2()=2+3.0;   //实数无参表达式,编译成功,立即执行,以后不再自动执行

    !!! 2+3i;       //复数无参表达式,编译成功,立即执行,以后不再自动执行

    !!! f3()=2+3i;   //复数无参表达式,编译成功,立即执行,以后不再自动执行

    无参表达式f1、f2和f3可以在其他可执行的表达式中被调用。

13.3 源程序中的模块

    编译时,将源程序中的表达式编译为一个或多个模块。编译时首先设置起始模块,也称主模块,以后每当遇到#MODULE#,开始编译为一个新的模块,称为子模块,而每当遇到#END#,回到主模块的编译。即#MODULE##END#之间的表达式定义为一个子模块,子模块之间不能嵌套定义。注意#MODULE##END#必须位于表达式的开头。

    在模块中,以:::开头的表达式被编译为公有表达式(全局表达式或全局函数),能被其他模块访问到,其余的表达式均被编译为私有表达式(私有函数),其他模块无法访问。在源程序的任何地方,可用指令#USE#调用另一个模块。

    第一个模块源文件如下:

      //单行注释:模块名:Module1
      /*
          多行注释:在同一模块源文件中的所有表达式属于同一个模块
          多行注释:以:::开头的表达式可被其他模块的表达式所访问
          多行注释:不以:::开头的表达式只能被该模块的表达式所访问
      */

      #MODULE#              //定义一个子模块

        !!!me()= o["Module1中的私有函数me,在编译时执行!\r\n"]; //私有函数,只能被该模块的表达式所访问,该表达式是在编译时执行的

        a(x) = x + 1;          //私有函数,只能被该模块的表达式所访问

        :::aa(x) = a(x) + 1;     //全局函数,任意模块包括本模块均可访问

      #END#                     //子模块定义结束,可缺省

      #MODULE#              //定义一个子模块

        a(x)= x - 1;            //私有函数,只能被该模块的表达式所访问

        :::bb(x) = a(x) - 1;     //全局函数,任意模块包括本模块均可访问

      #END#                     //子模块定义结束,不可缺省

      :::cc(x) = aa(x) + 1;      //主模块中的全局函数

    将以上源文件命名为“Module1.txt”,保存到目录“OpenLu64\module”(即程序OpenLu64所在的文件夹module)中。

    第二个模块源文件如下:

      //模块名:Module2

      #MODULE#             //定义一个子模块

        a(x) = 10 + x;        //私有函数,只能被该模块的表达式所访问

        #USE# Module1.txt;     //用关键字USE调用Module1,须给出模块文件名(必要时包含完整路径)

        :::dd(x) = a(x) + bb(x) + cc(x);  //全局函数,任意模块包括本模块均可访问 。函数bb()和cc()在模块Module1中定义

      #END#                  //子模块定义结束,可缺省

      #MODULE#            //定义一个子模块

        a(x)=10-x;         //私有函数,只能被该模块的表达式所访问

        :::ee(x) = a(x);    //全局函数,任意模块包括本模块均可访问

      #END#               //子模块定义结束

    将以上源文件命名为“Module2.txt”,保存到目录“OpenLu64\module”(即程序OpenLu64所在的文件夹module)中。

    在OpenLu中使用以上模块中的格式如下:

      #USE# module\Module2.txt;  //用关键字USE调用Module2模块,须给出模块文件名(必要时包含完整路径)

      dd(1)+ee(1);          //调用Module2模块中定义的函数

13.4 编译指令的位置和次序

    在MLu中使用的 #MODULE##END##USE#mvar:unmvar::::!!!~~~ 等称为编译指令,用以确定一个表达式所在模块、是否私有函数等属性。这些编译指令必须位于表达式的开头,有些指令能同时使用,有些指令不能同时使用,并且在使用时有一定的次序,按先后顺序依次为:

    1)编译指令#MODULE##END##USE#mvar:unmvar:之间没有先后顺序,可混合使用,但这些指令必须在表达式的最前面,一般单独成行。 注意mvar:unmvar:只在本模块内有效。

    2)编译指令!!!~~~不能混合使用,只能使用其中的一个。~~~表示该表达式只编译,不执行;!!!表示该表达式编译后立即执行,但以后执行模块时不再自动执行。

    3):::表示该表达式是一个全局表达式,否则是私有表达式。

    如果表达式前没有使用任何一个编译指令,则按缺省表达式的类型编译为私有表达式,若该表达式是无参表达式,则执行模块时将自动执行。

13.5 OpenLu中的模块管理

    打开模块管理窗口,可以运行一个模块,释放或者删除一个模块,释放全部模块等。

    在OpenLu中,一个源程序对应着一个模块。模块分为四种。

    标准模块:在工作区文件中设置的模块,或者通过 #USE# 命令加载的模块。只能释放模块所占空间,不能删除模块说明部分。
    命令模块:在工作区文件中设置的命令。只能释放模块所占空间,不能删除模块说明部分。
    普通模块:在主窗口中编译的普通模块。可全部删除。
    临时模块:在主窗口中编译的临时模块。可全部删除。

    无论哪种模块,编译后编译结果将被保留,以提高运行效率。释放或者删除模块可以清除编译结果。

14 Lu命名空间  [返回页首]

    使用命名空间可以有效地避免函数重名问题。

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

    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

14.2 二级函数命名空间  [返回页首]

    为了避免二级函数(部分为Lu核心库内置的函数,部分为Lu扩展库中的函数)重名,二级函数可以采用模块命名空间中的命名方式,如:Fun2::Set(...),称二级函数命名空间。是否采用二级函数命名方式,取决于提供二级函数的模块。

    一般,二级函数名称中不应有空格,所以可用如下方式判断一个函数是一个二级函数,或者是一个模块命名空间中输出的表达式。

      name ::Set(...);         //注意::前有一空格,若编译通过,一般是模块命名空间中的表达式,若编译未通过,说明是个二级函数

14.3 常量命名空间  [返回页首]

    为了避免常量重名,常量可以采用函数命名空间中的命名方式,如:ConstName::MyConst,称常量命名空间。常量命名空间可由程序或库提供,也可用函数const创建。

    一般,常量名称中不应有空格,否则该常量将无法正常访问。

    例子:

      !!!const("aa::c1",111);    //在命名空间aa中定义永久性常量

      !!!const("aa::c2",222,true);//在命名空间aa中定义暂时性常量

      aa::c1;                    //直接访问命名空间中的常量

      aa::c2;                    //直接访问命名空间中的常量

      !!!using("aa");            //访问命名空间aa

      c1;                        //访问命名空间中的常量

      c2;                        //访问命名空间中的常量

14.4 访问命名空间  [返回页首]

    命名空间(包括模块命名空间和二级函数命名空间)比较长时,输入比较麻烦,使用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

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

15.1 动态对象与指针  [返回页首]

    指针是动态数据对象的内存地址,对象的成员函数通过指针可以操作对象。Lu核心库中的动态对象有Lu动态数据、动态字符串string、动态整数数组ints及动态实数数组reals等。扩展库中将存在更多的动态对象,请参考各扩展库的说明。

    通常,动态对象是用函数new申请的,但也不尽然,例如函数lu[...]就返回一个动态对象。

    所有新生成的动态对象都是局部对象,其生命期为所在表达式运行(含挂起)时的生命期,即当一个表达式运行结束时,所有在该表达式中的局部动态对象都将被销毁。但函数global(p)可以将局部动态对象转换为全局动态对象,则表达式运行生命期结束时该对象不会被销毁。也可以使用函数local(p)将一个全局动态对象转换为局部动态对象,则表达式运行生命期结束时该对象会被销毁。

    以下例子中所有局部对象会被自动销毁:

main(: a, b, c, d, e, f, g) =
{
    a=new[string,"aa"],

    b=new[string,"bb"],

    c=new[string,"cc"],

    d = a + b + c;

    if{ true,

        e = a + b + c + d,

        f = a + b + c + d + e
    },

    g = a + b + c + d + e + f,

    o[a, "\r\n", b, "\r\n", c, "\r\n", d, "\r\n", e, "\r\n", f, "\r\n", g, "\r\n"]
};;

    全局动态对象可通过函数gc销毁。

    (1) 垃圾收集 gc():

    立即扫描并销毁垃圾对象,释放所占空间。函数返回0表示运行成功,否则表示不允许运行垃圾收集器。

    在Lu中,一个对象用一个指针标识,该指针存放在一个Lu数据中,此时,我们称这个数为指向该对象的指针。若Lu系统中的静态变量、模块变量、全局变量以及正在运行的表达式的数据区(自变量、动态变量及数据堆栈)中存在一个数对指向一个全局象时,该对象是有效的,否则视为垃圾对象,会被垃圾收集器所回收。

    (2) 获得或设置动态对象数目 ObjNum(n):

    当n>=100时设置允许的动态对象的最大数目,否则返回当前动态对象的数目。

    通常,Lu在检测到动态对象数目超过了允许的最大值时,将启动垃圾收集器进行清理。

    (3) 清空垃圾对象缓冲区 ClearBuf():

    动态对象生命期结束时,或者用函数del(...)删除对象时,对象会暂时存放在垃圾对象缓冲区中,该函数用于清空垃圾对象缓冲区。

    (4) 将动态对象放到垃圾对象缓冲区 del(x1,x2,x3,...):

    垃圾对象缓冲区中的对象将由Lu在合适的时候进行销毁。

    (5) 立即销毁对象 delete(x1,x2,x3,...):

    以下例子演示了gc、global、ObjNum、delete等函数的使用:

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

15.2 对象成员运算符(函数参数运算符)及对象赋值运算符  [返回页首]

    若标识符后面有句点“.”,表示将该标识符联系到一个函数,例如:a.sin();或者产生一个函数调用,例如:a.b 相当于 a(b)。若变量是显示说明的,则通过句点将产生隐含的oset函数或oget函数调用,称变量函数调用,例如:a.b 相当于 oget[a,b];a.b=c 相当于 oset[a,b:c]。

    在Lu中,存在很多动态对象,动态对象用指针标识,所以Lu编译器把任何一个函数或表达式都看作动态对象或指针的成员函数。为了存取动态对象时,在形式上看起来更美观一些,Lu核心库中提供了对象成员运算符“.”操作对象的成员函数或指针数据。如下例:

      (: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和3max[]的参数,而max[2,3]和5min[]的参数,即: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)

    尽管运算符“.”对程序的运行速度没有影响,但会影响编译速度,因而建议仅在需要的时候使用它。

15.3 对象赋值及获取  [返回页首]

    (1) 对象赋值 oset(p,... ...)

    p是一个对象指针,函数oset(p,... ...)可对对象p赋值。oset函数的调用是与等号相关联的。

    不是所有的对象都支持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]

    (2)获得对象的值 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)

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

    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用一个计数器记住生成的动态对象数目,当达到规定值时,就启动垃圾收集器;允许用户修改对象计数器允许的最大值 (默认值是10000)。

    (3)主程序可能会按一定的算法在处理器空闲时调用Lu垃圾收集器,这取决于主程序的设计。

    注意:与其他语言的垃圾收集器不同,Lu垃圾收集器总是立即启动的。

15.5 表达式的初始化及销毁  [返回页首]

    (1)通过协程初始化和销毁数据:强烈建议的方法

    协程在挂起时,里面产生的所有数据(包含局部动态对象)被保存,故可以设计一个永远挂起的协程产生全局对象(实际上,对该协程来说,这些对象是局部的,但可以使该协程始终处于挂起状态,直至协程被显示地终止),例如:

global_obj(x, y, z : i, j, k) = //通过函数global_obj获得的三个对象将始终有效

    i=lu[2, 3],

    j=lu[2.3],

    k=lu["全局字符串"],

    while{true, x=i, y=j, z=k, yield(0)};

    也可以将对象保存在模块变量中:

global_obj(:: i, j, k) = //模块变量i,j,k中的三个对象将始终有效

    i=lu[2,3],

    j=lu[2.3],

    k=lu["全局字符串"],

    while{true, yield(0)};

    (2)通过静态变量初始化和销毁数据

    通过静态变量,Lu表达式可以进行初始化,也可以借助专用静态变量free进行销毁表达式前的释放工作(Lu在销毁表达式前将自动设置free=1,然后自动执行表达式)。如下例:

      f(x : y, static, d, e, free) =       //表达式用o函数输出字符串
      {
        if{d==0, o("初始化!\r\n"), d=1},    
//注意静态变量d使初始化仅执行一次

        if{free, o("销毁!\r\n"), return(0)}, //Lu在销毁表达式前将自动设置free=1,然后自动执行表达式,使销毁仅执行一次

        ++x                       //每次调用使自变量增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("初始化!\r\n"), d=1},     
//注意静态变量d使初始化仅执行一次

        if{free, o("销毁!\r\n"), 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("初始化!\r\n"), d=1},      
//注意静态变量d使初始化仅执行一次

        if{free, o("销毁!\r\n"), return(0)},   //Lu在销毁表达式前将自动设置free=1,然后自动执行表达式,使销毁仅执行一次

        aa=HFor("a"),                  //动态获取函数a的句柄

        aa(x)+1                        //通过函数句柄aa动态调用函数a,使自动销毁正常进行
      
};

      f(0);  f(1);  f(2);

    (3)通过函数free(p)销毁数据

    函数free(p)可将对象p自动记录下来,在销毁表达式时销毁对象p。用法举例如下:

      ConstObj( : static, obj) =
      {
        if{obj==0, obj=new[...].free()},
 //第一次调用函数ConstObj()时,用函数new申请一个对象obj,并用函数free()标记在销毁表达式 时销毁它

        obj                              //返回对象obj

      };

      ConstObj();                        //每次调用函数ConstObj(),都会得到同一个对象

      ConstObj();                        //每次调用函数ConstObj(),都会得到同一个对象

    注意:函数free(p)并不立即销毁对象p,而是在销毁表达式时销毁该对象。并不是所有的对象都可由函数free销毁,这取决于主程序和Lu扩展库的设计。函数free(p)只适合销毁少量的数据,故不要将其放在循环中使用。

    注意:静态变量free和函数free(p)不必同时使用,同时使用将降低效率。

    最后指出,能否进行销毁表达式前的释放工作,取决于主程序的设计,与主程序如何使用Lu的输出函数有关。即便销毁表达式前没有进行释放工作,Lu最后也将释放所有资源,因而无需担心太多。

    简单地,使用Lu的垃圾收集器,可不必关心垃圾对象的销毁问题。

16 Lu标识符解释规则  [返回页首]

    (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)模块私有表达式与一个公有表达式重名时,优先调用本模块中的私有表达式。

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

    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~38 未定义        
39 sum 重载函数 不确定 第一操作数 求和。
40 pro 重载函数 不确定 第一操作数 求积。
41 deepcopy 重载函数 不确定 第一操作数 深复制对象。
42 get 重载函数 不确定 第一操作数 获得对象(成员)。
43 set 重载函数 不确定 第一操作数 设置对象(成员)。
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()获取并返回一个数据的扩展类型,正是该扩展类型引起该重载函数的调用。

    (1)创建新的扩展数据类型 newtype():

    该函数返回一个新的数据扩展类型(是一个整数),当该类型的数据成为Lu运算符或重载函数的操作数时,将调用与该扩展类型匹配的运算符重载函数。调用函数newtype的表达式即是与该扩展类型匹配的运算符重载函数。

    在一个表达式中多次调用newtype()时,仅返回一种数据扩展类型,或者nil。

    (2)强制类型转换 cast(Data,MyType):

    强制将数据Data的扩展类型转换为MyType类型。MyType是一个整数,标识某种类型的数据。若MyType为self,则将数据Data的扩展类型转换为其基本类型。

    例子:

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

    结果:

a=3 b=5 a+b=-2 a-b=8 a$b=nil

18 Lu高级数据类型  [返回页首]

    此部分介绍的Lu高级数据类型来源于Lu系统扩展库sys的一部分,事实上,其他Lu扩展库中的数据类型都可以看成是Lu高级数据类型,限于篇幅,不再进行介绍(本教程的重点是介绍Lu脚本的语法)。

18.1 luu表  [返回页首]

    luu是系统内置类型lu表的扩展类型。luu表是一个可直接存取的线性表,可以存放若干任意的Lu数据。 目前,luu表的功能仍较弱,但功能容易扩充。

    (1)新建luu表

!!!using("sys");    //使用命名空间sys

new[luu,5 : 1, 1.2, "abc"].o["\r\n"];;    //用函数new生成luu表,长度为5,有三个初始化数据。函数o用于输出luu表

    用函数luu生成luu表更为方便:

!!!using("sys");

luu[1+2i, 1.2, "abc"].o["\r\n"];;      //用函数luu生成luu表,长度为3,有三个初始化数据。函数o用于输出luu表

    (2)元素存取

!!!using("sys");

main(: a) = a=luu[1+2i, 1.2, "abc"], a[1] = a[2] + "luuuu", o[a];;

    (3)luu表连接:可用加号+连接两个luu表,或者将一个简单数加到luu表的前面或后面

!!!using("sys");

main(: a, b) = a=luu[1, 1, 1], b=luu[2, 2], o[a+b, 3+a, a+3];;

    (4)以lu表的元素为自变量进行函数调用

        lufun(hFor : x1, x2, ..., xn : xx)

    hFor为函数句柄;x1,x2,...,xn : xx为等长的Lu表;xx用于存放结果,可缺省。若缺省xx,返回一个Lu表存放结果,否则该函数返回xx。

    该函数以x1,x2,...,xn的第i个元素为自变量,用函数hFor进行计算,结果存入xx的第i个单元。

    运行错误:1:至少需要2个参数;2:指定的表达式不存在;3:Lu表个数与自变量个数不相等;4:不是Lu表;5:Lu表长度不匹配;6:内存错误。

    例子:

!!!using("sys");

f(x, y) = x + y;

lufun[@f, luu(1.,"aa",5), luu(2.,"bb",5)].o["\r\n"];;

    结果:

luu{3. , aabb , 10}

18.2 字符串String  [返回页首]

    字符串String是一种Unicode字符串对象,是系统内置类型字符串string的扩展类型。

Unicode字符串

双字节字符串。Lu系统默认字符串。
sys::String 整数常量,用在函数new[sys::String,... ...]中,申请String对象。该对象的基本类型为string,扩展类型为String。
sys::String["abc"] 生成String对象并用一个字符串初始化 ,实际上是生成了该字符串的一个副本。该对象的基本类型为string,扩展类型为String。
运行错误:1:不可识别的初始化参数。
new 该函数是重载函数,但直接调用了string的new函数。用于申请String对象,该对象的基本类型为string,扩展类型为String。
+ 重载运算符+,但直接调用了string的+函数。连接两个String。
len 该函数是重载函数,但直接调用了string的len函数。返回String的缓冲区长度。strlen函数返回一个字符串的长度。
o 该函数是重载函数。输出String的内容。运行错误:1:不可识别对象。
oset 该函数是重载函数,但直接调用了string的oset函数。设a是一个String,则执行形如a[i]=2的赋值语句时将调用该函数。
oget 该函数是重载函数,但直接调用了string的oget函数。设a是一个String,则执行形如a[i]的语句时将调用该函数。
sys::strlen[str] 返回字符串str的长度。len(str)返回字符串str的缓冲区大小。运行错误:1:不可识别对象。
sys::strcpy[str1,str2] 复制字符串str2到str1。当str1是动态字符串时,如果缓冲区不够用,会自动增加缓冲区。该函数不生成新的字符串 ,仍返回str1。
运行错误:1:参数非法;2:内存错误;3:静态缓冲区不够用。
sys::strcat[str1,str2] 将字符串str2连接到str1的后面。当str1是动态字符串时,如果缓冲区不够用,会自动增加缓冲区。该函数不生成新的字符串 ,仍返回str1。
运行错误:1:参数非法;2:内存错误;3:静态缓冲区不够用。
sys::substr[str,begin,end] 返回字符串str的子字符串,begin和end指定了子字符串的位置。如果begin或end为-1,或者大于字符串长度,取字符串的最后一个字符位置;此时,若begin>end,取end和begin之间字符串的反序字符串。运行错误:1:参数非法;2:内存错误。
例子1:substr["abcdefg",-1,3],结果:gfed
例子2:substr["abcdefg",3,-1],结果:defg
sys::strchr[str,c] 在字符串str中定位首次出现的字符c(整数),返回c在str中的下标(相对偏移量),失败时返回-1。运行错误:1:参数非法。
sys::strrchr[str,c] 在字符串str中定位最后出现的字符c(整数),返回c在str中的下标(相对偏移量),失败时返回-1。运行错误:1:参数非法。
sys::strcmp[str1,str2] 按字母顺序,对字符串进行比较。若str1<str2,返回值<0;若str1=str2,返回值=0;若str1>str2,返回值>0。运行错误:1:参数非法。
sys::strpbrk[str1,str2] 在字符串str1中定位字符集str2中首次出现的某个字符,返回这个字符在str1中的下标(相对偏移量),失败时返回-1。运行错误:1:参数非法。
sys::strspn[str1,str2] 在字符串str1中定位首次出现的不包含在字符集str2中的某个字符,返回这个字符在str1中的下标(相对偏移量),失败时返回-1。运行错误:1:参数非法。
sys::strstr[str1,str2,begin,end] 在字符串str1中查找子串str2,返回子串在str1中的位置(相对偏移量),失败时返回-1。begin和end指定了查找位置;若缺省end,则从begin位置开始查找;若begin和end都缺省,则查找整个字符串。运行错误:1: 参数个数非法;2:参数非法。
sys::strtok[str1,str2] 在字符串str1(包含分隔符和记号)中提取记号,str2是包含分隔符的字符串,返回一个luu对象,luu对象包含了所有提取的记号,失败时返回nil。
运行错误:1:参数非法。
例子:sys::strtok["one,two three , four*five", ", *"].o["\r\n"];
sys::strlwr[str] 将字符串str转换为小写,仍返回该字符串。运行错误:1:参数非法。
sys::strupr[str] 将字符串str转换为大写,仍返回该字符串。运行错误:1:参数非法。
sys::strset[str,ch,begin,end] 将字符串str中从begin~end的内容设置为字符ch。若缺省begin和end,则设置整个内容;若仅缺省end,则设置从begin开始的内容。仍返回该字符串。运行错误:1:参数个数非法;2:参数非法。
sys::strrev[str] 反转字符串str中的字符顺序,仍返回该字符串。运行错误:1:参数非法。
sys::strrep[str,oldstr,newsyt,start,ntimes] 将字符串str中的子串oldstr替换为newstr,返回替换后的字符串。start为查找替换的开始位置,缺省为从头开始替换。ntimes指定进行替换的次数,缺省表示替换所有匹配。运行错误:1:参数个数非法;2:参数非法 ;3:内存错误。
sys::strdate[] 把日期拷贝到一个字符串中并返回该字符串,失败时返回nil。
sys::strtime[] 把时间拷贝到一个字符串中并返回该字符串,失败时返回nil。

    例子:

(1)生成String

!!!using("sys");

new[String,5 : "abc"].o["\r\n"];;    //用函数new生成String,长度为5,并用一个字符串进行初始化。函数o用于输出String,并返回输出的字符数目

用函数String生成String更为方便:实际上该函数生成了原字符串的一个副本。

!!!using("sys");

String["abc"].o["\r\n"];;            //用函数String生成String,必须用一个字符串作为初始化数据

注意:函数strcpy将原字符串复制到目标字符串,但不会返回一个新串。

!!!using("sys");

main(:a)= a=String[""], strcpy[a,"abc"], o[a];;

(2)元素存取:可直接对字符串做更改。

!!!using("sys");

main1(:a)= a=String["abc"], a[1]=a[2]+1, o[a];;  //动态字符串

main2(:a)= a="abc",  a[1]=a[2]+1, o[a];;  //静态字符串

(3)String缓冲区大小和长度

!!!using("sys");

main(:a)= a=new[String, 5 : "abc"], o{"字符串缓冲区=", len(a), "  字符串长度=", strlen(a), "\r\n"};;

(4)String连接:用加号+连接两个字符串,返回一个新串。

!!!using("sys");

o{String["abc"]+String["def"]};

o{String["abc"]+"def"};

(5)String连接:用函数strcat连接两个字符串,不返回新串。

!!!using("sys");

main(:me)=

  me=String["abc"], printf["%s\r\n",me],  //输出me

  strcat(me,"def"), printf["%s\r\n",me];; //输出me

(6)大小写互换:用自定义函数实现

!!!using("sys");

Swapcase(str : i, k, ch, a, z, A, Z) =

  k="azAZ", a=k[0], z=k[1], A=k[2], Z=k[3],  //获得字符azAZ的ASCII值

  k=strlen[str], i=0,

  while{i<k, ch=str(i),

    which{ch>=a & ch<=z : str(i)=ch-a+A;

          ch>=A & ch<=Z : str(i)=ch-A+a;

          ch},

    i++
  },

  str;

Swapcase["abc 88 啊啊 DEF"].o["\r\n"];;

(7)格式化字符串,用sprintf函数实现

!!!using("sys");

main(: a, b, k, i, s) =

  a=String[""], sprintf[a, "%20s", "abc"],  o[a, "\r\n"],  //固定长度,右对齐,左边不够用空格补齐

  a=String[""], sprintf[a, "%-20s", "abc"], o[a, "\r\n"],  //固定长度,左对齐,右边不够用空格补齐

  a=String[""], b="abc", k=[20-strlen(b)]/2, s=new[String, k+1], i=0, while{i<k, s[i++]=32}, s[i]=0,

  sprintf[a,"%s%s%s",s,b,s], o[a,"\r\n"];  //固定长度,中间对齐,两边不够用空格补齐

(8)去字符串两边空格

!!!using("sys");

main(: a, i, j, k) =

    a=["   asd  啊啊  def   "],

    i=strspn(a, " \r\n\t"),

    strrev(a),

    j = strlen(a) - strspn(a," \r\n\t")-1,

    strrev(a),

    o{substr[a, i, j]};

(9)是否以start开头

!!!using("sys");

main(:a) =

    a=["   start 啊啊  def   "],

    strspn(a, " \r\n\t") == strstr(a,"start");

(10)是否全小写

!!!using("sys");

main(:a) =

    a=["   start wsx  def   "],

    strspn(a, "abcdefghijklmnopqrstuvwxyz \r\n\t") < 0;

18.3 字典dict  [返回页首]

    字典(dict)是一个可直接存取的双向链表,可以存放若干任意的Lu数据。字典元素由“键-值”对组成,键只能是字符串,但值可以是任何Lu数据类型。

sys::dict 整数常量,用在函数new[sys::dict,... ...]中,申请字典对象。
sys::dict["a":2, "b":8, ...] 生成字典对象并初始化。运行错误:1:字典元素不匹配;2:字典元素的键应为字符串;3:内存错误。
new 该函数是重载函数,用于申请字典对象。一般用法:new[sys::dict, "a":2, "b":8, ... ...]
o 该函数是重载函数。输出字典的内容。运行错误:1:不可识别对象。
oset 该函数是重载函数。设a是一个字典,则执行形如a."b"=2的赋值语句时将调用该函数。运行错误:1:参数太多或者太少;2:不可识别对象;3:非法的字符串;4:内存错误。
oget 该函数是重载函数。设a是一个字典,则执行形如a."b"的语句时将调用该函数,获得字典元素的值。运行错误:1:参数太多或者太少;2:不可识别对象;3:非法的字符串。
len 该函数是重载函数。设a是一个字典,len(a)求a的所有元素数目。
sys::dict_oset[p:"a",2] 设置字典p的元素"a"的值为2。运行错误:1:参数太多或者太少;2:不可识别对象;3:非法的字符串;4:内存错误。
sys::dict_oget[p:"a"] 获得字典p的元素"a"的值。运行错误:1:参数太多或者太少;2:不可识别对象;3:非法的字符串。
sys::dict_del[p:"a","bc",...] 删除字典p的元素"a","bc",...。运行错误:1:参数太少;2:不可识别对象;3:非法的字符串。
sys::dict_clear[p] 清空字典p的所有元素。运行错误:1:不可识别对象。
sys::dict_reverse[p] 字典反序。

    例子:

    (1)新建字典

!!!using("sys");

new[dict, "aa":1.2, "abc":"luuu"].o["\r\n"];;    //用函数new生成字典,有2组初始化数据。函数o用于输出字典

    用函数dict生成字典更为方便:

!!!using("sys");

dict["aa":1.2, "abc":"luuu"].o["\r\n"];;       //用函数dict生成字典,有2组初始化数据。函数o用于输出字典

    (2)得到字典元素

!!!using("sys");

main(:a)=

    a=dict["aa":1.2, "abc":"luu"],

    o[a."aa", "\r\n", a."abc", "\r\n"];

    (3)增加字典元素

!!!using("sys");

main(:a)=

    a=dict["aa":1.2, "abc":"luu"],

    a."cc"=a."abc"+"88", o[a, "\r\n"];;

    说明:当往字典中增加元素时,若已存在该键,则进行更新。

    (4)删除字典

    删除指定键-值对:a.dict_del["aa","abc"];

    清空字典:a.dict_clear();

    删除字典对象,但暂存在缓冲区:del[a];

    立即彻底删除字典对象:delete[a];

    (5)字典反序

!!!using("sys");

main(:a)= a=dict["aa":1.2, "abc":"luu"], o[a], o[a.dict_reverse(), "\r\n"];;

    结果:

dict{aa : 1.2 , abc : luu}dict{abc : luu , aa : 1.2}

    (6)字典存取效率测试 :在字典a中添加若干元素,元素的键通过变换字符串str的值得到,sum用于求所有元素的值的和。

!!!using("sys");

main(:a, i, j, str, t0, sum) =

  t0=clock(), a=dict[], str="aaaa", sum=0,

  i=0, while{++i<=100, str[1]=i,

    j=1000, while{j<2000, str[2]=j, sum=sum+i+j, a.str=i+j, j++}

  },

  o{"\r\n创建字典耗时", [clock()-t0]/1000., "秒。共", len(a), "个元素。sum=", sum},

  t0=clock(), sum=0,

  i=0, while{++i<=100, str[1]=i,

    j=1000, while{j<2000, str[2]=j, sum=sum+a.str, j++}

  },

  o{"\r\n查询字典耗时", [clock()-t0]/1000., "秒。共", len(a), "个元素。sum=", sum, "\r\n"};;

    结果:

创建字典耗时0.89000000000000001秒。共100000个元素。sum=155000000
查询字典耗时9.4e-002秒。共100000个元素。sum=155000000

18.4 结构struct  [返回页首]

    结构(struct)是一个线性表,可以存放若干任意的Lu数据。结构成员必须以#开头。结构成员的存储顺序与其定义顺序不一定相同。

sys::struct 整数常量,用在函数new[sys::struct,... ...]中,申请结构对象。
sys::struct[#a, #b:8, ...] 生成结构对象并初始化 ,结构成员必须以#开头。运行错误:1:非法的结构成员;2:结构成员重名。
new 该函数是重载函数,用于申请结构对象。一般用法:new[sys::struct, #a, #b:8, ...]
o 该函数是重载函数。输出结构的内容。运行错误:1:不可识别对象。
oset 该函数是重载函数。设a是一个结构,则执行形如a.#b=2的赋值语句时将调用该函数。运行错误:1:参数太少;2:不可识别对象或成员;3:没有找到成员。
oget 该函数是重载函数。设a是一个结构,则执行形如a.#b的语句时将调用该函数,获得结构成员的值。运行错误:1:参数太少;2:不可识别对象或成员;3:没有找到成员。
len 该函数是重载函数。设a是一个结构,len(a)求a的所有成员数目。
copy 该函数是重载函数。设a和b是结构,copy(a)求a的副本,copy(a,b)复制b的内容到a(执行a.=b时将自动调用该函数),copy(a: #ab,2, #b:8, ...)求a的副本并进行初始化,初始化参数要成对。运行错误:1:不可识别对象 ;2:对象结构不相同;3:非法的成员标识;4:没有找到成员;5:参数不匹配。
set 该函数是重载函数,设置结构的成员。设a是一个结构,a.set(#ab,2, #b:8, ...)设置a的成员,注意参数要成对。运行错误:1:参数太少;2:不可识别对象 ;3:非法的成员标识;4:没有找到成员;5:参数不匹配。
sys::struct_oset[p,#b : 2] 设置结构p的成员#b的值为2。运行错误:1:参数太少;2:不可识别对象或成员;3:没有找到成员。
sys::struct_oget[p,#b] 获得结构p的成员#b的值。运行错误:1:参数太少;2:不可识别对象或成员;3:没有找到成员。

    例子:

    (1)新建结构:相当于结构定义

!!!using("sys");

new[struct, #num, #姓名 : "luuu", #年龄].o["\r\n"];    //用函数new生成结构,有3个成员,其中“姓名”成员进行了初始化。函数o用于输出结构

    用函数struct生成结构更为方便:

!!!using("sys");

struct[#num, #姓名 : "luuu", #年龄].o["\r\n"];        //用函数new生成结构,有3个成员,其中“姓名”成员进行了初始化。函数o用于输出结构

    (2)复制结构:相当于生成结构对象

!!!using("sys");

main(:a) =

    a=struct[#num, #姓名 : "luuu", #年龄],

    o[a,"\r\n",copy(a), "\r\n", copy(a, #num:22, #年龄:33), "\r\n"];

    结果:

struct{#num : nil , #姓名 : luuu , #年龄 : nil}
struct{#num : nil , #姓名 : luuu , #年龄 : nil}
struct{#num : 22 , #姓名 : luuu , #年龄 : 33}

    (3)复制结构:要求有相同的结构成员,依次复制结构成员的值

!!!using("sys");

main(:a, b) =

    a=struct[#num : 55, #姓名 : "luuu", #年龄 : 33],

    b=struct[#num, #姓名, #年龄],

    b.=a,

    o[a, "\r\n", b, "\r\n"];;

    结果:

struct{#num : 55 , #姓名 : luuu , #年龄 : 33}
struct{#num : 55 , #姓名 : luuu , #年龄 : 33}

    (4)设置结构:批量对结构成员赋值 ,注意次序是任意的,并且可设置部分成员或全部成员

!!!using("sys");

main(:a) =

    a=struct[#num, #姓名, #年龄],

    o[a,"\r\n"],

    a.set(#年龄:33, #num:22).o["\r\n"];;

    结果:

struct{#num : nil , #姓名 : nil , #年龄 : nil}
struct{#num : 22 , #姓名 : nil , #年龄 : 33}

    (5)结构成员单独赋值,获取结构成员的值

!!!using("sys");

main(:a) =

    a=struct[#num, #姓名, #年龄],

    a.#姓名="王强",

    a.#年龄=33,

    a.#num=22,

    o[a,"\r\n", a.#姓名, "\r\n", a.#年龄, "\r\n"];;

    结果:

struct{#num : 22 , #姓名 : 王强 , #年龄 : 33}
王强
33

    (6)结构嵌套

!!!using("sys");

main(:a) =

    a=struct[#num, #班级],

    a.#num=22,

    a.#班级=struct[#num, #姓名, #年龄],

    a.#班级.#num=88,

    a.#班级.#姓名="王强",

    a.#班级.#年龄=33,

    o[a, "\r\n", a.#num, "\r\n", a.#班级, "\r\n", a.#班级.#num, "\r\n", a.#班级.#姓名, "\r\n", a.#班级.#年龄, "\r\n"];;

    结果:

struct{#num : 22 , #班级 : struct[...]}
22
struct{#num : 88 , #姓名 : 王强 , #年龄 : 33}
88
王强
33

    (7)效率测试

!!!using("sys");

main(:a,i,t0,sum)=
  a=struct{
    #January,
    #February,
    #March,
    #April,
    #May,
    #June,
    #July,
    #August,
    #September,
    #October,
    #November,
    #December
  },

  t0=clock(), sum=0,

  i=0, while{++i<=100000,
    a.#January=1,
    a.#February=2,
    a.#March=3,
    a.#April=4,
    a.#May=5,
    a.#June=6,
    a.#July=7,
    a.#August=8,
    a.#September=9,
    a.#October=10,
    a.#November=11,
    a.#December=12,
    sum=sum+a.#January+a.#February+a.#March+a.#April+a.#May+a.#June+a.#July+a.#August+a.#September+a.#October+a.#November+a.#December
  },

  o{"\r\n耗时", [clock()-t0]/1000., "秒。sum=", sum, "\r\n"};

    结果:

耗时0.75秒。sum=7800000

    使用函数struct_oset和struct_oget存取结构成员会提供运行效率,如下例:

!!!using("sys");

main(:a,i,t0,sum)=
  a=struct{
    #January,
    #February,
    #March,
    #April,
    #May,
    #June,
    #July,
    #August,
    #September,
    #October,
    #November,
    #December
  },

  t0=clock(), sum=0,

  i=0, while{++i<=100000,
    a.struct_oset[#January : 1],
    a.struct_oset[#February : 2],
    a.struct_oset[#March : 3],
    a.struct_oset[#April : 4],
    a.struct_oset[#May : 5],
    a.struct_oset[#June : 6],
    a.struct_oset[#July : 7],
    a.struct_oset[#August : 8],
    a.struct_oset[#September : 9],
    a.struct_oset[#October : 10],
    a.struct_oset[#November : 11],
    a.struct_oset[#December : 12],

    sum=sum+a.struct_oget[#January]+a.struct_oget[#February]+a.struct_oget[#March]+a.struct_oget[#April]+a.struct_oget[#May]+a.struct_oget[#June]
        +a.struct_oget[#July]+a.struct_oget[#August]+a.struct_oget[#September]+a.struct_oget[#October]+a.struct_oget[#November]+a.struct_oget[#December]

  },

  o{"\r\n耗时", [clock()-t0]/1000., "秒。sum=", sum, "\r\n"};

    结果:

耗时0.56200000000000006秒。sum=7800000

    Matlab2009a代码:

a=struct('January',1,'Febrary',2,'March',3,'April',4,'May',5,'June',6,'July',7,'August',8,'September',9,'October',10,'November',11,'December',12);
tic;
sum=0;
for i=1:100000
    a.January=1;
    a.February=2;
    a.March=3;
    a.April=4;
    a.May=5;
    a.June=6;
    a.July=7;
    a.August=8;
    a.September=9;
    a.October=10;
    a.November=11;
    a.December=12;
    sum=sum+a.January+a.February+a.March+a.April+a.May+a.June+a.July+a.August+a.September+a.October+a.November+a.December;
end
toc,
sum

    结果参考(未测试):

Elapsed time is *** seconds.

sum =

7800000

18.5 类class  [返回页首]

    类(class)是一个具有数据成员和方法成员的自定义数据结构。类可以继承和派生,类的层次结构是一棵树。

    类对象(obj)是类的实例。

sys::class 整数常量,用在函数new[sys::class,... ...]中,申请类定义。
sys::obj 整数常量,用在函数new[sys::obj,pclass,... ...]中,申请类对象,其中pclass是类定义。
sys::class 创建类定义。
sys::obj 创建类对象。
sys::class_handle(#AA) 根据类名称得到类句柄,该句柄可用在函数obj中创建类对象。
new 该函数是重载函数,用于申请类定义类对象
o 该函数是重载函数。输出类定义及类对象的内容。运行错误:1:不可识别对象;2:找不到类定义。
oset 该函数是重载函数。设a是一个类对象,则执行形如a.#b=2的赋值语句时将调用该函数。运行错误:1:参数太少;2:不可识别成员标识;3:不可识别对象标识;4:没有找到类成员;5:不可识别的结构对象;6:没有找到结构成员;7:不可识别数据类型;8:成员函数参数不匹配;9:不可更改基类对象。
oget 该函数是重载函数。设a是一个类对象,则执行形如a.#b的语句时将调用该函数,获得类成员的值。运行错误:1:参数太少;2:不可识别成员标识;3: 不可识别对象标识;4:没有找到类成员;5:不可识别的结构对象;6:没有找到结构成员;7:不可识别数据类型;8:成员函数参数不匹配。
copy 该函数是重载函数。设a和b是对象,copy(a,b)复制b的内容到a,执行a.=b时将自动调用该函数。运行错误:1: 参数太多或者太少;2:不可识别对象;3:对象结构不相同;4:对象的成员函数#__copy__参数不是2个;5:没有找到成员函数#__copy__的句柄。
sys::obj_oset[p,#b : 2] 设置对象p的成员#b的值为2。运行错误:1:参数太少;2:不可识别成员标识;3:不可识别对象标识;4:没有找到类成员;5:不可识别的结构对象;6:没有找到结构成员;7:不可识别数据类型;8:成员函数参数不匹配;9:不可更改基类对象。
sys::obj_oget[p,#b] 获得对象p的成员#b的值。运行错误:1:参数太少;2:不可识别成员标识;3: 不可识别对象标识;4:没有找到类成员;5:不可识别的结构对象;6:没有找到结构成员;7:不可识别数据类型;8:成员函数参数不匹配。
sys::obj_method 调用类对象的方法。

    类的基本函数:

    (1)sys::class{... ...}:创建类定义

    类定义格式如下:

class{#cMyClass : #cBase1, #cBase2, ...  //定义类cMyClass,继承自cBase1和cBase2, ...

private:                  //私有数据成员

  #pvMem1, #pvMem2, ...

method:                   //私有方法(私有函数成员)

  #vmFun1 :@Fun1, #vmFun2 :@Fun2, ...

public:                   //公有数据成员

  #plMem1, #plMem2, ...

method:                   //公有方法(公有函数成员)

  #__init__ : @init, #__del__ : @delme, #lmFun1 :@fun1, #lmFun2 :@fun2, ...
};

    类定义中,类名称、基类名称、成员及方法都是以#开头的标识符,不可重复。类名称在最前面,其次是基类名称;privatepublic的次序是任意的,且可多次定义; 在private后定义的method为私有方法,在public后定义的method为公有方法,若method前面没有privatepublic,默认是公有方法;方法标识符后必须提供函数句柄。类成员的存储顺序与其定义顺序不一定相同。

    #__init__#__del__只可被定义为 公有方法,其中#__init__用于定义构造函数,#__del__用于定义析构函数。若这两个方法被缺省,其函数句柄被自动指定为0。构造函数和析构函数只有一个参数,即对象本身。

    若方法有不止一个参数,则方法被调用时,第一个参数总是传入对象本身,相当于C++的this指针。

    运行错误:1:非法的类名;2:__init__和__del__只能是公有方法;3:类名或基类名有重名;4:不可识别关键字;5:缺少函数句柄;6:不可识别关键字或成员;7:基类没有定义或循环定义。

    约定:类名称以字母c开头;私有数据成员以pv开头;公有数据成员以pl开头 ;私有方法成员以vm开头;公有方法成员以lm开头。

    (2)sys::obj{pClass}:创建类对象

    pClass是类定义的句柄。

    创建类对象时,将自动调用每一个基类的构造函数#__init__初始化基类对象。对象被销毁时,将自动调用每一个基类的析构函数#__del__销毁基类对象。

    语句 a.=b 将对象b的内容复制到对象a,要求a和b具有相同的类定义,若类定义中定义了方法#__copy__(有且只有2个参数),将自动调用该方法进行对象复制,否则仅依次复制类成员的值。

    运行错误:1:不可识别类定义。

    (3)sys::obj_method(#FunName : pObj,... ...):调用类对象的方法

    #FunName:以#开头的标识符指出函数名。

    pObj:类对象。

    运行错误:1:参数太少;2:不可识别的函数标识或对象;3:不可识别类 对象;4:参数不匹配;5:找不到指定的方法。

    例子:

    (1)类定义

!!!using("sys");

new[class, #人, public : #姓名, private : #性别, #年龄].o["\r\n"];;    //用函数new定义类,有1个公有成员,2个私有成员。函数o用于输出类定义

    结果:

class{#人 : 
private:
  #性别, #年龄, 
method:
  
public:
  #姓名, 
method:
  #__init__ : 0, #__del__ : 0
}

    用函数class定义类更为方便:

!!!using("sys");

class[#人, public : #姓名, private : #性别, #年龄].o["\r\n"];;    //用函数new定义类,有1个公有成员,2个私有成员。函数o用于输出类定义

    输出结果与上面相同。

    (2)单一继承

!!!using("sys");

class[#人, public : #姓名, private : #性别, #年龄],   //“#人”的类定义

class[#学生, #人, public : #学校, #班级].o["\r\n"];;  //“#学生”的类定义,继承自“#人”

    结果:

class{#学生 : #人,
private:
  
method:
  
public:
  #学校, #班级, 
method:
  #__init__ : 0, #__del__ : 0
}

    (3)类对象

!!!using("sys");

main(: a, b) =

    class{#人,   //“#人”的类定义

        public :

            #姓名,

            #性别,

            #年龄
    },

    a=class{#学生, #人,   //“#学生”的类定义,继承自“#人”

        public :

        #学校,

        #班级
    },

    b=obj[a],   //“#学生”的类对象

    b.#学校="实验中学",

    b.#班级="二年级",

    b.#姓名="王强",

    b.#性别="男",

    b.#年龄=12,

    o[b, b.#学校,"  ", b.#班级,"  ", b.#姓名,"  ", b.#性别, "  ", b.#年龄, "\r\n"];;

    结果:

class obj{#学生 : #人,
private:
public:
  #学校 : 实验中学,
  #班级 : 二年级
}
实验中学  二年级  王强  男  12

    说明:如果基类“#人”中#性别#年龄被定义为私有成员,将无法直接进行存取。

    (4)多重继承及构造函数和析构函数

    例子1:

!!!using("sys");

initA(p)= o["\r\nA的构造函数!"];
initB(p)= o["\r\nB的构造函数!"];
initC(p)= o["\r\nC的构造函数!"];
initD(p)= o["\r\nD的构造函数!"];


delA(p) = o["\r\nA的析构函数!"];
delB(p) = o["\r\nB的析构函数!"];
delC(p) = o["\r\nC的析构函数!"];
delD(p) = o["\r\nD的析构函数!"];

main()=
    class{#A,

        method : #__init__ : @initA;  #__del__ : @delA
    },

    class{#B, #A,

        method : #__init__ : @initB;  #__del__ : @delB
    },

    class{#C, #A,

        method : #__init__ : @initC;  #__del__ : @delC
    },

    class{#D, #B, #C,   //“#D”的类定义,继承自“#B”和“#C”

        method : #__init__ : @initD;  #__del__ : @delD

    }.obj[].delete[];;   //生成“#D”的类对象,然后立即销毁。注意运算符“.”的使用

    结果:

A的构造函数!
B的构造函数!
A的构造函数!
C的构造函数!
D的构造函数!
D的析构函数!
B的析构函数!
A的析构函数!
C的析构函数!
A的析构函数!

    例子2:

!!!using("sys");

initA(p)= p.#a=0;
initB(p)= p.#a=1;
initC(p)= p.#a=2;
initD(p)= p.#a=3;

main(:c) =

    class[#A,           public: #a, method : #__init__ : @initA],

    class[#B, #A,       public: #a, method : #__init__ : @initB],

    class[#C, #A,       public: #a, method : #__init__ : @initC],

    c=class[#D, #B, #C, public: #a, method : #__init__ : @initD].obj[],

    o[c.#a, "  ", c.#B.#a, "  ", c.#C.#a, "  ", c.#B.#A.#a, "  ", c.#C.#A.#a, "\r\n"],

    c.#B.#A.#a=5,

    c.#C.#A.#a=6,

    o[c.#B.#A.#a, "  ", c.#C.#A.#a, "\r\n"];;

    结果:

3 1 2 0 0
5 6

    例子3:

!!!using("sys");

initA(p)= p.#a=0;
initB(p)= p.#b=1;
initC(p)= p.#c=2;
initD(p)= p.#d=3;

main(:c) =

    class[#A,           public: #a, method : #__init__ : @initA],

    class[#B, #A,       public: #b, method : #__init__ : @initB],

    class[#C, #A,       public: #c, method : #__init__ : @initC],

    c=class[#D, #B, #C, public: #d, method : #__init__ : @initD].obj[],

    o[c.#a, "  ", c.#b, "  ", c.#c, "  ", c.#d, "\r\n"],

    c.#a=5,  c.#b=6,  c.#c=7,  c.#d=8,

    o[c.#a, "  ", c.#b, "  ", c.#c, "  ", c.#d, "\r\n"],

    c.#B.#A.#a=11,  c.#C.#A.#a=12,

    o[c.#a, " ", c.#B.#A.#a, "  ", c.#C.#A.#a, "\r\n"];;

    结果:

0 1 2 3
5 6 7 8
11 11 12

    (5)成员函数:第一个参数为对象指针

    例子1:

!!!using("sys");

setA(p,x)= p.#a=x;
setB(p,x)= p.#a=x;
setC(p,x)= p.#a=x;

getA(p)= p.#a;
getB(p)= p.#a;
getC(p)= p.#a;

main(:c) =

    class[#A,           public: #a, method : #mGetA : @getA, #mSetA : @setA],

    class[#B,           public: #a, method : #mGetB : @getB, #mSetB : @setB],

    c=class[#C, #A, #B, public: #a, method : #mGetC : @getC, #mSetC : @setC].obj[],

    c.#mSetA=5,  c.#mSetB=6,  c.#mSetC=7,

    o[c.#mGetA, "  ", c.#mGetB, "  ", c.#mGetC, "\r\n"];;

    结果:

5 6 7

    说明:类的方法成员和数据成员用法几乎相同,但方法可以接受多个参数,如下例。

    例子2:

!!!using("sys");

out(p, x, y, z)= o[x, y, z, "\r\n"];

main(:c)=

    c=class[#A, method : #mOut : @out].obj[],

    c.#mOut := [5,6,7],   //注意运算符“.”和“:=”的使用

    c.#mOut."***".(888)."***";;   //注意运算符“.”的使用

    结果:

567
***888***

    (6)类对象复制

!!!using("sys");

setA(p, x, y) = p.#a=x, p.#b=y;

sumA(p) = p.#a + p.#b;

copyAB(s, t) = s.#a=t.#a, s.#b=t.#b;

class[#A,     public : #a, public : #b, method : #__copy__ : @copyAB, #setA : @setA, #sumA : @sumA],

class[#B, #A, public : #a, public : #b, method : #__copy__ : @copyAB];;

main(: a, b) =

    a=obj[class_handle(#B)], b=obj[class_handle(#B)],

    a.#a=1, a.#b=2, a.#setA := [8, 9],

    b.=a,   //注意运算符“.=”的使用

    b.#a+b.#b+b.#sumA;

    结果:

20

    (7)多态性

!!!using("sys");

getA()= 1;
getB()= 2;
getC()= 3;

class[#A,     method : #mGet : @getA],
class[#B, #A, method : #mGet : @getB],
class[#C, #B, method : #mGet : @getC];

main(: a, b, c) =

    c=obj[class_handle(#C)],

    o[c.#mGet, "  ", c.#B.#mGet, "  ", c.#B.#A.#mGet, "\r\n"],

    b=c.#B,  a=b.#A,  //获得基类对象

    o[c.#mGet, "  ", b.#mGet, "  ", a.#mGet, "\r\n"];;

    结果:

3 2 1
3 2 1

    (8)效率测试

!!!using("sys");

main(:a,i,t0,sum)=
  a=class{#A,
    public:
    #January,
    #February,
    #March,
    #April,
    #May,
    #June,
    #July,
    #August,
    #September,
    #October,
    #November,
    #December
  }.obj[],

  t0=clock(), sum=0,

  i=0, while{++i<=100000,
    a.#January=1,
    a.#February=2,
    a.#March=3,
    a.#April=4,
    a.#May=5,
    a.#June=6,
    a.#July=7,
    a.#August=8,
    a.#September=9,
    a.#October=10,
    a.#November=11,
    a.#December=12,
    sum=sum+a.#January+a.#February+a.#March+a.#April+a.#May+a.#June+a.#July+a.#August+a.#September+a.#October+a.#November+a.#December
  },

  o{"\r\n耗时", [clock()-t0]/1000., "秒。sum=", sum, "\r\n"};

    结果:

耗时0.85999999999999999秒。sum=7800000

    比较:前面使用struct的耗时0.75秒,故class比struct稍慢。

    使用函数obj_oset和obj_oget存取类成员会提供运行效率,如下例:

!!!using("sys");

main(:a,i,t0,sum)=
  a=class{#A,
    public:
    #January,
    #February,
    #March,
    #April,
    #May,
    #June,
    #July,
    #August,
    #September,
    #October,
    #November,
    #December
  }.obj[],

  t0=clock(), sum=0,

  i=0, while{++i<=100000,
    a.obj_oset[#January : 1],
    a.obj_oset[#February : 2],
    a.obj_oset[#March : 3],
    a.obj_oset[#April : 4],
    a.obj_oset[#May : 5],
    a.obj_oset[#June : 6],
    a.obj_oset[#July : 7],
    a.obj_oset[#August : 8],
    a.obj_oset[#September : 9],
    a.obj_oset[#October : 10],
    a.obj_oset[#November : 11],
    a.obj_oset[#December : 12],

    sum=sum+a.obj_oget[#January]+a.obj_oget[#February]+a.obj_oget[#March]+a.obj_oget[#April]+a.obj_oget[#May]+a.obj_oget[#June]+a.obj_oget[#July]
        +a.obj_oget[#August]+a.obj_oget[#September]+a.obj_oget[#October]+a.obj_oget[#November]+a.obj_oget[#December]
  },

  o{"\r\n耗时", [clock()-t0]/1000., "秒。sum=", sum, "\r\n"};

    结果:

耗时0.625秒。sum=7800000

19 while in循环  [返回页首]

    19.1 简单序列化对象的in函数

    in函数是一个可重载的函数,用于迭代获取序列化的对象的值,字符串、lu表、数组 (多维数组当作一维数组进行操作)等都是序列化的对象。在Lu脚本中,序列化对象的下标是从0开始的。

        in(p : &next, inc : 2, range : 3, 16, &a, &b, &... ...):

    p:序列化对象的句柄。

    next:整数,标识对象下标,必须使用引用方式传递参数,函数返回时i指向下一个对象。next须初始化为有效值。 该参数不可缺省,且必须为第二个参数。

    inc,2:常量标识符inc指定迭代增量,迭代增量是一个整数,可以为负值,但不可为0。inc及迭代增量可以缺省,缺省时增量为1;不缺省时必须为第三个参数。假设迭代增量为2,则该函数获取下标为next,next+2,next+4,... ...的对象元素赋值给&a,&b,&c,... ...。

    range,3,16:常量标识符range指定下标范围,下标范围用2个整数标识。当范围值小于0时,取0;范围值大于对象长度时,取对象长度。该函数只从该下标范围内取值。该参数可以缺省,缺省时从整个对象长度范围内取值 ;不缺省时必须为第五个参数。

    &a, &b, &... ...:引用方式传递参数,返回指定迭代增量的若干对象元素。

    该函数返回值:返回非0值表示操作成功。通常返回实际的对象元素个数,返回值小于1时表示返回的对象元素个数为0。

    运行错误代码:1:至少需要2个参数;2: 不可识别对象;3:该对象不支持in函数;4:初始序号必须为整数;5:增量必须为非0整数 ;6:迭代范围必须为整数。

    该函数通常与while函数配合使用,例子:

    (1)返回下一个下标

(:p, i) = p=new[ints, data : 1, 2, 3], i=0, while{ in[p : &i] ,o[i, "\r\n"]};;  //返回i的下一个值

    结果:

1
2
3

    (2)获取下一个下标及当前元素的值

(:p, i, a) = p=new[ints, data : 11, 22, 33], i=0, while{ in[p : &i, &a] ,o[i, " ", a, "\r\n"]};;

    结果(注意最后一个下标指向nil):

1 11
2 22
nil 33

    (3)获取下标及元素的值

(:p, i, j, a) =

    p=new[ints, data : 11, 22, 33],

    i=0, j=i,  //注意j的使用,记住下一个下标

    while{ in[p : &i, &a],

        o[j, " ", a, "\r\n"],

        j = i  //记住下一个下标
    };;

    结果:

0 11
1 22
2 33

    (4)自定义增量及范围

(:p, i, k, a, b, c) =

    p=new[ints, data : 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],

    i=5,  //从下标5开始取值

    while{ k=in[p : &i, inc,2 , range: 3, 20, &a, &b, &c],  //增量为2;范围为3~20;每次取3个数

        o[k, " - 返回值个数, ", a, " ", b, " ", c, "\r\n"]

    };;

    结果(注意最后一行只有2个数有效,最后一个是无效值;实际应用中应计算好,让每一行的值都有效,以便于处理):

3 - 返回值个数, 6 8 10
3 - 返回值个数, 12 14 16
2 - 返回值个数, 18 20 16

    (5)自定义增量及范围,反向迭代

(:p, i, k, a, b, c) =

    p=new[ints, data : 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],

    i=19,  //从下标19开始取值

    while{ k=in[p : &i, inc,-2 , range: 20, 3, &a, &b, &c],  //增量为-2;范围为20~3;每次取3个数

        o[k, " - 返回值个数, ", a, " ", b, " ", c, "\r\n"]

    };;

    结果:

3 - 返回值个数, 20 18 16
3 - 返回值个数, 14 12 10
3 - 返回值个数, 8 6 4

    (6)自定义增量及范围,迭代位置、增量参数等都可以修改

(:p, i, k, a, b, c, n) =

    p=new[ints, data : 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],

    i=5, n=2,  //从下标5开始取值,初始增量为2

    while{ k=in[p : &i, inc,n , range: 3, 20, &a, &b, &c],  //增量为n;范围为3~20;每次取3个数

        if[a==12, i=15, n=1],  //修改迭代位置及增量

        o[k, " - 返回值个数, ", a, " ", b, " ", c, "\r\n"]

    };;

    结果:

3 - 返回值个数, 6 8 10
3 - 返回值个数, 12 14 16
3 - 返回值个数, 16 17 18
3 - 返回值个数, 19 20 21

    (7)lu表例子

(:p, i, k, a, b, c) =

    p=lu[1, "aaa", 33, "sss"],

    i=0, while{ k=in[p : &i, &a,&b,&c],

        o[k, " - 返回值个数, ", a, " ", b, " ", c, "\r\n"]

    };;

    结果(注意最后一行只有1个数有效,最后2个是无效值):

3 - 返回值个数, 1 aaa 33
1 - 返回值个数, sss aaa 33

    (8)in函数效率测试

(:p, i, k, s, t, a, b, c, d) =

    p=new[ints, data : 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],

    t=clock(), k=0,

    (++k<100000).while{

        //s=0, i=0, while{ in[p : &i, &a], s = s + a}   //in函数每次取1个元素

        //s=0, i=0, while{ in[p : &i, &a, &b, &c, &d], s = s + a + b + c + d}   //in函数每次取4个元素

        //s=0, i=0, while{ i<20, s = s + p[i++]}   //直接取1个元素

        s=0, i=0, while{ i<20, s = s + p[i++] + p[i++] + p[i++] + p[i++]}   //直接取4个元素

    },

    o{"\r\n耗时", [clock()-t]/1000., "秒。sum=", s, "\r\n"};;

    结果(四种取值方式分别测试的结果):

耗时0.281秒。sum=210   //in函数每次取1个元素

耗时0.125秒。sum=210
   //in函数每次取4个元素

耗时0.25秒。sum=210
   //直接取1个元素

耗时0.156秒。sum=210
   //直接取4个元素

    19.2 结构中的in函数

    结构(struct)是一个线性表, 因而可以使用in函数,但用法上与简单序列化对象的in函数稍有区别。另外,注意结构成员的存储顺序与其定义顺序不一定相同。

        in(p : &next, inc : 2, range : 3, 16, &a1, &b1, &a2, &b2, &... ...):

    &a1, &b1, &a2, &b2, &... ...:引用方式传递参数时, 参数个数必须为偶数,每对参数分别返回结构成员和成员的值。

    例子:

(: i, p, a, b) =

    p=sys::struct[#num, #姓名 : "王强", #年龄],

    i=0, while{ in[p : &i, &a, &b], o[a, " ", b, "\r\n"]};;

    结果(注意 #num#年龄 没有赋值,故为 nil):

#姓名 王强
#num nil
#年龄 nil

    19.3 字典中的in函数

    字典(dict)是一个可直接存取的双向链表, 虽然仍然可以使用in函数,但用法上与简单序列化对象的in函数有较大区别,in函数中不使用range参数。

        in(p : &next, inc : 2, &a1, &b1, &a2, &b2, &... ...):

    &next:迭代参数next必须初始化为p,即 next = p

    inc,2:常量标识符inc指定迭代增量,迭代增量是一个 非0整数。增量为正表示顺序迭代,增量为负表示反序迭代。inc及迭代增量可以缺省,缺省时增量为1。

    &a1, &b1, &a2, &b2, &... ...:引用方式传递参数时,参数个数必须为偶数,每对参数分别返回 字典成员的“键 - 值”。

    例子:

(: i, p, a, b, c, d, k) =

    p=sys::dict["ab1" : 11, "ab2" : 22, "ab3" : 33, "ab4" : 44, "ab5" : 55, "ab6" : 66, "ab7" : 77],

    i = p,  //迭代参数i初始化为p

    while{ k=in[p : &i,&a,&b,&c,&d],

    o[a," - ",b, " ; ",c," - ",d, " 返回数据个数 = ",k,"\r\n"]};;

    结果(注意:字典建立时总在链表头部插入数据,故顺序与定义时相反):

ab7 - 77 ; ab6 - 66 返回数据个数 = 2
ab5 - 55 ; ab4 - 44 返回数据个数 = 2
ab3 - 33 ; ab2 - 22 返回数据个数 = 2
ab1 - 11 ; ab2 - 22 返回数据个数 = 1

20 数学库中的代码矢量化及其他  [返回页首]

    Lu脚本数学库LuMath是一个Lu数值计算扩展动态库,该库以线性代数特别是矩阵运算为基础。

    在LuMath中的函数是通过二级函数命名空间“math”输出的,所有函数均具有类似“math::matrix(...)”的格式。使用!!!using("math");可简化LuMath中的函数访问。

    LuMath库的数组是C格式的,元素序号是基于0的。

    LuMath库函数具有内存消耗低、执行效率高、代码简洁、实用性强的特点。

    20.1 LuMath数组(矩阵)对象基本用法

    借助Lu编译器的支持,LuMath中的数组(矩阵)对象有以下简洁的用法。

    设A、B是LuMath数组(矩阵)对象:

      A.=B                 //将对象B赋值给对象A

      A.=A+B               //将对象A+B赋值给对象A

      A[2]=A[2]+1          //将一维数组A第2个单元的值加1

      A[2,3,5]=A[3,1,2]+1  //将三维数组A的第[2,3,5]个单元的值赋值为A[3,1,2]+1

      A.*2.0               //数组(矩阵)A的每一个元素乘以2.0

    若A是一个LuMath矩阵:

      A(type)              //返回与矩阵A同样大小的矩阵,并根据type的值进行初始化。type的可选值为zeros(全0矩阵)、ones(全1矩阵)、eye(单位矩阵)、rand(0~1之间均匀分布的随机数)、randn(均值为0,方差为1的标准正态分布的随机数)。

      A(all:j)             //取矩阵A第j列所有元素。all是一个常量,在这里表示取所有行

      A(i:all)             //取矩阵A第i行所有元素。all是一个常量,在这里表示取所有列

      A(i,i+m:all)         //取矩阵A第i~i+m行所有元素。all是一个常量,在这里表示取所有列

      A(all:j,j+m)         //取矩阵A第j~j+m列所有元素。all是一个常量,在这里表示取所有行

      A(i,i+m:j,j+n)       //取矩阵A第i~i+m行,并在第j~j+n列的所有元素

      A(all:j)=B           //设置矩阵A第j列所有元素,B是一个数组。all是一个常量,在这里表示取所有行

      A(i:all)=B           //设置矩阵A第i行所有元素,B是一个数组。all是一个常量,在这里表示取所有列

      A(i,i+m:all)=B       //设置矩阵A第i~i+m行所有元素,B是一个矩阵。all是一个常量,在这里表示取所有列

      A(all:j,j+m)=B       //设置矩阵A第j~j+m列所有元素 ,B是一个矩阵。all是一个常量,在这里表示取所有行

      A(i,i+m:j,j+n)=B     //设置矩阵A第i~i+m行,并在第j~j+n列的所有元素 ,B是一个矩阵。

    [例子]

!!!using["math"];

main(: t0, k, i, a, b) =
{
    t0=clock(),

    k=zeros(5,5),         //生成5×5矩阵k,初始化为0

    i=0,(++i<=1000).while{//循环计算1000次

        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(all:6)*b(3:all)
    },

    k.outa(),             //输出矩阵k

    [clock()-t0]/1000.    //得到计算时间,秒
};

    结果(因为使用rand函数随机生成矩阵,故每次运算结果有所不同):

3265.09 3189.61 3216.75 3217.72 3239.47
3330.69 3258.71 3334.78 3266.99 3329.4
3228.9 3164.3 3173.15 3212.68 3212.01
3222.46 3177.53 3231.74 3216.78 3226.51
3273.38 3203.32 3237.91 3229.84 3289.89

3.1e-002

    20.2 in函数

    LuMath中的in函数与Lu核心库中用于简单序列化对象的in函数用法完全相同,可迭代获取一维数组、矩阵(当作一维数组进行操作)等序列化对象的值。

    例子:

(:p, i, k, a, b, c) =

    p=math::matrix[4, 5 : 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],  //4×5矩阵

    i=5,  //从下标5开始取值

    while{ k=in[p : &i, inc,2 , range: 3, 19, &a, &b, &c],  //增量为2;范围为3~19;每次取3个数

        o[k, " - 返回值个数, ", a, " ", b, " ", c, "\r\n"]

    };;

    结果(注意最后一行只有2个数有效,最后一个是无效值):

3 - 返回值个数, 6. 8. 10.
3 - 返回值个数, 12. 14. 16.
2 - 返回值个数, 18. 20. 16.

    20.3 矢量化运算

    矢量化运算对数组进行整体处理(矢量化处理),故速度快,代码简洁。Lu脚本中的矢量化运算与Matblab类似。

    例子1:

!!!using["math"];

//linspace(a,b,n):产生一个一维数组。a、b为实数,n为整数。a和b是数组的第一个和最后一个元素,元素总数是n

[linspace(1.0, 5.0, 5)*2.0].outa[];;  //linspace产生的一维数组的 每一个元素乘以2.0,然后用outa函数输出该数组

sin[linspace(1.0, 5.0, 5)].outa[];;  //对linspace产生的一维数组的 每一个元素求正弦,然后用outa函数输出该数组

    结果:

2. 4. 6. 8. 10.

0.841471 0.909297 0.14112 -0.756802 -0.958924

    例子2:

!!!using["math"];

(: x ) =

    x = matrix[  //矩阵可用字符串参数定义

    "
    1 2 3
    4 5 6
    "],

    outa[x*2.0];;  //矩阵的每一个元素乘以2.0,然后输出该矩阵

    结果:

2. 4. 6.

8. 10. 12.

    例子3:

!!!using["math"];

mvar:

//ndgrid(x1,x2,...,xn : &y1,&y2,...,yn):生成n维函数的辅助格点阵列。x1,x2,...,xn:均是一维数组。&y1,&y2,...,yn:返回n维函数的辅助格点阵列

ndgrid[linspace(0.0, 1.0, 1000),linspace(1.0, 2.0, 1200), &x, &y],  //x和y均为1000×1200矩阵

Sum[cos(1.0-sin((1.2).*x.^(y./2.0)+cos(1.0-sin((1.2).*y.^(x./2.0))))),0];  //矩阵矢量化运算,最后求所有元素的和

    结果:

1196070.519834899

21 在脚本中编译脚本  [返回页首]

    编译源程序为一个模块:ComModule(Str,&nModule,&hModule,&err1,&err2);

    Str:源程序字符串;

    nModule:当nModule为0时,所有模块的模块号自动指定(MLu初始化时,设置最初的起始模块号为1,以后不断递增,但不一定连续,因为每次执行该函数,不管是否执行成功,模块号均加1),不会重复,也不会被编译为0#模块。任何时候,可用nModule传送给MLu一个起始模块号(必须大于0),以后的模块号将在该模块号的基础上递增,从而改变模块号序列。若要自己指定模块号,则每次编译源程序前,均指定一个起始模块号。MLu编译的多个模块序号是递增的,但不一定连续,因为如果MLu加锁一个模块不成功,就会继续加锁下一个模块号。函数返回时,nModule返回多个模块的最小模块号。一般返回主模块号,如果主模块中没有表达式,就返回有表达式的最小子模块号。

    hModule:返回模块的句柄。当该模块不用时,就用delete(hModule)立即销毁该模块 ,否则将导致某些全局函数无法编译。

    err1err2:返回编译出错位置,该值是否有意义取决于函数的返回值(返回值为-1、0、1、3、-2时无意义)。

    该函数返回值的意义如下:

    -7:递归调用指定的模块。(忽略此参数)
    -6:找不到指定的模块。
(忽略此参数)
    -5:缺少模块名。
(忽略此参数)
    -4:注释符号/* ... */不成对。
    -3:未使用模块编译功能,不能编译指定的模块。
    -2:无法加锁模块。
(由程序员处理,修改nModule为较小的值可能修正此错误,忽略此参数)
    -1:未用!
    0:没有错误,编译成功!
    1:内存分配失败!
    2:括号不成对!
    3:(等号后)没有表达式!
    4:非法的函数句柄!
    5:字符串中转义字符错误!
    6:字符串无效,即"..."不匹配!
    7:不可识别字符!
    8:表达式名称定义错误!
    9:不可识别的自变量,自变量只能以字母、中文字符或下画线开头!
    10:不可识别的自变量定义方法,“(,:,:,:,:,...)”冒号过多!
    11:自变量定义错误!
    12:continue()函数只能有0个参数!
    13:只能在while,until中使用continue函数!
    14:break()函数只能有0个参数!
    15:只能在while,until中使用break函数!
    16:if,while,until,which中的参数个数至少为2个!
    17:表达式中的数字错误!
    18:&单目取地址运算符只能用于单独的变量!
    19:单目运算符++、--错误!
    20:括号内没有数字!
    21:单目运算符+、-、!、!!错误!
    22:赋值“=”错误!
    23:不正确的运算方式或其他语法错误!
    24:不可识别变量名或常量名!
    25:不可识别函数名!
    26:一级函数参数不匹配!
    27:二级函数参数不匹配!
    28:关键字Static或Common的位置非法!
    29:(模块中)表达式有重名!
    30:对形如“-2^3”的式子,须用括号给前置单目运算符“-”和乘方运算符“^”(或点乘方运算符“.^”)指出运算顺序!
    31:类成员运算符(函数参数运算符)后只能是变量名、字符串、括号运算符或者类成员函数!
    32:后单目运算符'、.'错误!
    33:调用表达式时参数不匹配!
    34:未用!
    35:自变量重名!
    36:因检测到运行错误而退出!
    37:未用!
    38:未用!
    39:源代码太长或字符串太多!

    例子:

mvar:

main(: a) =

  t0=clock(), s=0, i=0,

  while{++i<=10000,

    //动态编译模块,该模块有2个函数,f为私有函数,ff为全局函数

    ComModule["f(x)=x+1; :::ff(x,y)=f(x)+y;", 0, &me, 0, 0],

    a=HFor("ff"), //获得函数ff的句柄

    s=s+a[1,1],   //动态调用函数ff

    delete[me]    //立即销毁模块
  },

  s;    //输出累加结果

[clock()-t0]/1000.;  //获取编译10000次的用时

    结果:

30000
0.171

22 Lu关键字  [返回页首]

    在一般的编程语言中,关键字是事先定义的有特别意义的字,关键字是保留字,不能用来做标识符(如变量名),使用关键字来做变量名是一种语法错误,不能通过编译。按此定义,则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 二级函数 获得对象的值。
yield 一级函数 挂起协程并立即返回表达式的值。
status 二级函数 查询表达式(包含协程)状态。
abort 二级函数 中止挂起的协程。
terminate 二级函数 终止协程。
resume 二级函数 重启一个终止的协程。

23 OpenLu及Lu扩展库  [返回页首]

    OpenLu直接加载模块化编译运行库MLu64.dll,MLu会自动加载Lu核心库(Lu64.dll)和动态加载多个Lu扩展库,简化了Lu系统的使用。

    Lu扩展库中的函数均为二级函数,地位相当于Lu核心库中的二级函数,它们的区别仅仅在于功能不同,来自于不同的库。目前的Lu扩展库主要有:

Lu扩展库 命名空间 简介
 Lu系统扩展动态库LuSystem using["sys"];     LuSystem是一个Lu系统扩展动态库,包含一些增强Lu系统功能的函数、对系统内置类型的扩展以及一些新增数据类型等等 (主要包括luu表、字符串、正则表达式、字典、结构、类、文件操作、格式化输入输出等)。LuSystem不仅是对Lu系统的扩充,更在于演示Lu是极易扩充其功能的脚本,很少有脚本允许用户能方便地添加像字典、结构、类等高级的数据结构,但Lu允许,而且实现这些很容易,因而,LuSystem也是编程用户极佳的练手工具。
 Lu数值计算扩展动态库LuMath using["math"];     LuMath的数值算法是以矩阵为基础,并进行了运算符重载,具有内存消耗低、执行效率高、代码简洁、实用性强的特点。LuMath是熟悉C/C++、Fortran的数学爱好者的极佳的练手工具,任何喜欢LuMath的个人、团队或商业公司可基于此库开发商业程序。
 Lu参数优化动态库LuOpt using["luopt"];     优化函数具有极强的全局优化能力,也可用于求解非线性方程(组)。
 Lu图形库Lu2D using["lu2d"];     基于Windows API的2D图形库,多线程动态绘制图形是Lu2D的基本特点。该库将逐步被LuWin替代。
 Lu窗口库LuWin using["win"];     基于Windows API的窗口库,由数据可视化库CChart提供支持,包含一些窗口函数,特别是绘制2D3D图形的函数多线程动态绘制图形是LuWin的基本特点。
 Lu图形库OpenLuGl OpenLuGl中绝大多数的OpenGl函数(以gl或glu开头)的 命名及用法与在OpenGl中完全相同,故未设置命名空间     基于OpenGL的Lu扩展动态库,在独立的线程中动态绘制三维图形。

    本教程的目的主要是介绍Lu脚本的基础语法知识,限于篇幅,关于OpenLu、Mlu及Lu扩展库的说明就不再展开了。这些说明都可以从OpenLu使用说明中找到。


版权所有© Lu程序设计 2011-2021,保留所有权利
E-mail: forcal@sina.com  QQ:630715621
最近更新: 2021年09月25日