“易语言.飞扬”白皮书

版本:1.2.3

作者:大连大有吴涛易语言软件开发有限公司

来源:http://dotef.eyuyan.com/docs/whitebook/

综述

“易语言.飞扬”(EF)是一门简单易学、高效实用、面向对象、跨平台的计算机通用编程语言。

它是完全面向对象的编程语言,因而在面向对象机制上,与同为面向对象的Java、C#等编程语言,有相似甚至相同之处。

它的语法脱胎自“类C语言”,因而在语法上,与C、C++、Java、C#等编程语言,有相似甚至相同之处。

它是一个全新的易语言版本,从核心架构上明显区别于原有的易语言(4.x及以前版本),它与以前的易语言共同构成了一个可以面向更广泛应用层次的软件开发平台。

“易语言.飞扬”系统架构图

“易语言.飞扬”系统架构图

“易语言.飞扬”的主要特性

最近更新

1.2.3版,2007.12.15

1.2.2版,2007.11.20

1.2.1版,2007.9.28

1.2版,2007.6.15

1.1版,2007.2.10

1.0版,2006.12.29

《“易语言.飞扬”白皮书》第一个公开发布版本,主要描述语法、类库、编译器等核心内容。

第一个程序

公开 类 启动类
{
	公开 静态 启动()
	{
		控制台.输出("祖国您好!");
	}
}

请将以上内容存储为“hello.ef”文件,注意编码格式必须为 Unicode(UTF-8/UCS-2/UCS-4)。请参考:字符和编码。

Windows操作系统下请使用以下命令行将“hello.ef”编译为可执行文件“hello.exe”,并运行编译生成的“hello.exe”:

efc hello.ef -out="hello.exe"
hello.exe

Linux操作系统下请使用以下命令行将“hello.ef”编译为可执行文件“hello”,并运行编译生成的“hello”:

./efc hello.ef -out="hello"
./hello

以上程序运行后,将在控制台输出以下内容:

祖国您好!

注意:要编译EF源文件或运行EF编译生成的可执行文件,需确保系统类库(系统.efn)位于当前目录中,或位于系统环境变量“EF_LIB_PATHS”所指定的目录内。 请参考:类库的加载,类库的启动。

EBNF语法

本文主要使用 EBNF 描述EF语法。

EBNF要点:

EBNF表达式中,粗体部分表示EF代码,斜体部分表示用户定义名称(标识符)或另一个EBNF表达式。

代码组织

EF代码由任意多个源代码文件(*.ef)和一个可选的类库信息定义文件(*.inf)文件组成。

字符和编码

源代码文件

源代码文件结构:

引入类库
类定义 | 接口定义 | 枚举定义 | 友好名称定义

“引入类库”语法:

引入 类库名称 {, 类库名称};

使用任何非本程序或本类库中定义的类型之前,都必须“引入”相应的类库。系统类库因为必然被所有程序和类库使用,将被自动引入。

“引入”语句应位于源代码文件的首部,所引入的类库只对当前文件有效。

使用已被“引入”类库中的类型时,如果不存在歧义,可以使用类型的“短名称”。 如“系统.对象”可简化为“对象”。请参考:类型名称。

类、接口、枚举、友好名称等的定义语法详见下文。

类库信息定义文件

类库信息定义文件用于给编译器提供类库定义信息,其文件后缀名固定为“.inf”,文件名称可任意。

文件格式:

类库 类库名称 [属性表];

示例:

类库 我的类库 <启动类 = "启动类" 作者 = "大连大有吴涛易语言软件开发有限公司">;

编译时,请将 .inf 文件跟其他所有源代码文件一并提供给编译器,文件名之间以空格隔开,如:

efc lib.inf 1.ef 2.ef ...

.inf 文件是可选的。如果不提供该文件,则必须通过编译器命令行参数指定“类库名称”和/或“启动类”。请参考:编译器。

标识符

标识符是用户定义的用于标识特定代码元素的字符组合。 变量名称、类型名称(类名称/接口名称/枚举名称)、方法名称、成员名称(类成员名称/枚举成员名称)等均被称为标识符。

标识符由英文字母、下划线、数字、和其他非ANSI字符(包括汉字)组成,其中数字不允许出现在标识符首部。

标识符最多允许255个字符。

注释

“//”表示单行注释的开始,直到本行结束。

“/*”表示多行注释的开始,“*/”表示多行注释的结束。

多行注释内部允许嵌套使用单行注释和多行注释。

注释不属于可编译代码,编译时将被忽略。

//这是单行注释
整数 i = 100; //后半行是注释

/*
	这是多行注释,
	可以写多行
*/

/*
	支持嵌套使用注释。
	//这是第一层嵌套
	/*
		这是第二层嵌套
		/* 第三层嵌套 */
	*/
*/

数据类型

EF是强类型语言,每个数据都有其明确的数据类型,不同数据类型的数据之间进行类型转换时会执行严格的检查。

EF中的数据类型分为“基本数据类型”和“扩展数据类型”。

基本数据类型是系统内置的,扩展数据类型是在程序和类库中定义的,这是两者的主要区别。

基本数据类型

EF的基本数据类型共有 5 种:整数、小数、逻辑、文本、字节集:

中文名称英文名称占用字节数取值范围数据示例初始值说明
整数int4-2,147,483,648 ~ 2,147,483,647123032位有符号整数
小数double81.7e-308 ~ 1.7e+3081.230.064位双精度浮点数
逻辑bool4真/假真假只有两个值,真或假
文本string    "中国人" 或 ["易语言.飞扬"]""被双引号""或“["”和“"]”包含的为文本常量。文本以Unicode格式存储,多个文本或文本变量之间可以用“+”连接。
字节集binary    { 0, 1, 2, 3 }{}字节集是字节值的顺序组合,每一个成员数值都必须大于等于0且小于等于255

整数的表示方法:

整数示例表示方法
10进制123, -123[-]{0|1|2|3|4|5|6|7|8|9}+
16进制0xE00x|0X{0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|A|B|C|D|E|F}+,16进制数值以“0x”或“0X”为前缀,后续字母大小写均可
2进制0b101000110b|0B{0|1}+,2进制数值以“0b”或“0B”为前缀

小数的表示方法:

[-]数.数[d|D|e|E[-]数]

其中“数”的表示方法:

{0|1|2|3|4|5|6|7|8|9}+

小数示例:

1.0
1.2e30
1.2e-30

字节集的表示方法:

{ [整数 {, 整数}] }

字节集示例:

{ }
{ 1, 2, 255 }

文本的表示方法:

1、用双引号表示文本:

此时可在文本中使用以下转义字符:

转义字符含义
\n换行
\r回车
\"英文双引号
\'英文单引号
\\\
\xNN 为任意16制数值,\xN 表示数值 N 所对应的Unicode字符

含转义字符的文本示例:

"这是第一行\n这是第二行哦"
"易语言.飞扬:\x6613\x8BED\x8A00\x002E\x98DE\x626C"
"因为字符 \\ 在文本中有特殊含义,要使用 \\\\ 表示字符 \\"

单引号“'”也可在文本中直接使用,如 Ji'nan 等效于 Ji\'nan,支持对它的转义是为了兼容C/C++语言用户的使用习惯。

2、用“["”和“"]”表示文本:

此时,除文本中如有“"]”内容必须用“\"]”表示之外,其它所有字符(包括换行和回车符等)均保留其原始形式(“\”不再充当转义字符前缀)。

用这种方式可以很方便地表示多行文本或正则表达式文本,如:

["第一行
第二行
\n\r\x7第三行"]

["\\n"]

基本类型对象

在EF中,基本类型数据被视为一个特殊的对象,有自己的成员方法和属性。

例如可以这样使用:

文本 s = 123.到文本();
整数 n = s.长度;
控制台.输出("易语言.飞扬".左边(3));

各基本类型对象的成员方法和属性,分别由系统类库中的“整数类”“小数类”“逻辑类”“文本类”“字节集类”等类提供技术支持:

当基本类型对象的成员方法或属性方法被调用时,基本类型数据将被作为第一个参数传入“整数类”“小数类”“逻辑类”“文本类”“字节集类”等类中定义的相应静态方法。

基本类型对象并不是真正的对象,它不是“系统.对象”的实例。使用基本类型对象,不涉及创建销毁临时对象和“装箱”“拆箱”等动作,不会付出任何效率上的代价。

请参考:数组对象。

扩展数据类型

扩展数据类型目前有以下三种:类、接口、枚举。

鉴于类、接口和枚举在面向对象编程中的重要作用,将在下文用专门的章节对其进行详细说明。

类型名称

扩展数据类型的名称有“完整名称”和“短名称”之分,其中“完整名称” = “类库名称” + “.” + “短名称”。

假设在名称为“中国”的类库中定义有“北京”类,则“北京”是“短名称”,“中国.北京”是“完整名称”。

程序源代码中定义的类型名称,和被引入的类库中定义的类型名称,在无歧义的情况下可使用“短名称”,编译器会自动将其转换成“完整名称”。

类型反射

EF拥有完善的类型反射机制。

通过类型反射机制,可以在运行时获取类库或类型的定义信息。

“反射”类库本身就是应用“类型反射机制”开发的。

用户可以在反射类库的基础上,根据自身需求进行相关处理。甚至可以彻底抛弃反射类库,开发另外一个反射类库。

更多信息请参考“反射”类库相关文档。

数据类型转换

不同数据类型的两个数据或变量之间如需要互相转换,部分情况可由编译器自动进行类型转换,部分情况需由用户进行强制类型转换。

可自动类型转换的情况:

  1. 整数可自动转换到小数,反之亦可  
  2. 一维整数数组常量或一维小数数组常量可自动转换到字节集  
  3. 字节集常量可自动转换到一维整数或小数数组  
  4. 整数数组常量可自动转换到小数数组,小数数组常量可自动转换到整数数组  
  5. 多维整数数组常量或多维小数数组常量可自动转换到字节集数组  
  6. 枚举可自动转换到整数或小数,枚举数组可自动转换到整数数组  
  7. 子类对象可自动转换为基类对象,子类对象数组可自动转换为基类对象数组  
  8. 子接口对象可自动转换到为基接口对象,子接口对象数组可自动转换为基接口对象数组  
  9. 实现了某接口的类对象,可自动转换到该接口对象  
  10. 枚举类型值可转换到整数或小数,枚举类型数组可转换到整数数组  
  11. 具有“类型转换方法”的类,可以实现对相应类型数据的转入或转出,请参考:类型转换方法

可强制类型转换的情况:

  1. 整数或小数可强制转换到枚举类型值(用户必须保证该数值是合法的枚举成员值)  
  2. 基类对象可强制转换到其子类对象(转换失败则引发运行时错误)  
  3. 基接口对象可强制转换到其子接口对象(转换失败则引发运行时错误)  
  4. 接口对象可强制转换到类对象(转换失败则引发运行时错误)  
  5. 类对象可强制转换到接口对象(转换失败则引发运行时错误)  
  6. 基类对象数组可强制转换为其子类对象数组(转换失败则结果未知)  
  7. 基接口对象数组可强制转换为其子接口对象数组(转换失败则结果未知)

强制类型转换的语法:

(欲强制转换到的类型)被转换类型的数据;

强制类型转换示例:

小数 f = 1.23;
整数 n = (整数)f;

立即数

所有能够在编译时确定其最终值的数据称为立即数。

立即数通常由“原始值”、“常量”、“常量和或原始值的计算表达式”表示,譬如:

123  //123为直接的原始数值
常量 整数 常量1 = 123;  //此时,所定义的“常量1”是立即数的一种。
常量1 + 1  //此为常量计算表达式,也是立即数的一种。

只有基本数据类型及其数组才可以用立即数表示,基本数据类型立即数的表示方法请参考“基本数据类型”。

数组立即数的定义格式为:

{ {立即数 {, 立即数} } }

以配对的花括号“{”“}”表示数组或数组的某一维,以“,”作为各数组、各维、各成员之间的分隔符。

示例:

{ 1, 2, 3 } //表示一维整数数组,有3个成员,三个成员的值分别是 1, 2, 3。
{ { 真, 假, 真, 假 }, { 真, 真, 假, 假 } } //表示二维逻辑数组,共8个成员。

多维数组必须保证各对应维中的成员个数的均衡,以让编译器能够确认出该数组的维信息,如:

{ { 真 }, { 假 } }     //均衡,正确
{ { 真 }, { 假, 假 } } //不均衡,错误

关键字

EF有以下关键字:

中文名 英文名 说明

定义类

用作定义各种类型
类库 library 定义类库信息
类 class 定义类
枚举 enum 定义枚举
接口 interface 定义接口
友好名称 FriendlyName 定义友好名称

流程控制类

控制代码流程
开始 do 逻辑判断循环
循环 while
计次循环 counter 循环指定次数
遍历循环 foreach 逐次取出数组或可遍历对象中的所有数据
C循环 for 类似C/C++语言中的for循环
到循环尾 continue 跳到当前循环尾部(循环尾语句块)
跳出 break 跳出当前循环语句,或跳出当前“假如”语句
循环尾 tail 定义当前循环尾部代码
如果 if 建立分支流程代码
又如 else if
否则 else
假如 switch 建立开关类分支流程代码
为 case
为其他 default
返回 return 返回或返回数据到当前方法的调用方

预编译类

进入语法分析前根据条件过滤掉某些代码
#如果 #if 条件过滤
#又如 #elif
#否则 #else
#结束 #endif
#定义 #define 定义可以在预编译类命令中使用的常量
#错误 #error 报告用户提供的错误信息并中止编译
是否定义 defined 用作在预编译类命令中检查指定预编译常量是否被定义

常量/引用类

 
真 true 逻辑常量值"真"
假 false 逻辑常量值"假"
空 null 代表空类对象/空接口对象/无成员的数组对象
本对象 this 用作在代码中引用方法所处类对象
基类 base 用作在代码中引用方法所处类的基类
其它  
引入 import 将指定类库引入到当前源代码文件,此语句最好放置在文件首部。
只有被引入的类库才能在当前源代码文件中被使用。
创建 new 建立类对象或数组
检查 assert 如果所检查条件不符合,将产生运行时错误。
本命令仅在程序编译为调试版本时有效,在编译为发行版(非调试版)时,本命令将被忽略。
下一组参数 GetNextParameterGroup 接收下一组扩展参数
编译信息 GetCompilingInf 取各种编译信息,返回对应文本型数据,支持以下编译属性(属性名必须以文本立即数形式提供):
编译属性名 说明 举例
当前文件名 返回当前正在编译的文件名 编译信息("当前文件名")
当前行号 返回当前编译行号 编译信息("当前行号")
当前类型 返回当前代码所处类型名称 编译信息("当前类型")
当前方法 返回当前代码所处方法名称 编译信息("当前方法")
类型全名 返回第二个参数所指定类型的全名 编译信息("类型全名", "控制台")
文件数据 FileData 读入指定文件内容并返回相应常量数据,调用格式为(注意参数必须是立即数):
   文件数据 (欲导入的文件名, 文件格式)
在程序中使用本命令可以将指定文件中的数据嵌入到编译结果中去。
文件格式 说明 举例
文本 读入指定UTF8/UCS2/UCS4格式文本文件并返回相应文本常量数据 常量 文本 s = 文件数据("文件1.txt", "文本");
字节集 读入指定文件并返回相应字节集常量数据 字节集 bn = 文件数据("文件1.mp3", "字节集");

由于在某些特殊场合可能会造成语义混淆,不建议将以上关键字用作标识符。请参考:标识符。

运算符

EF中的运算符全部是半角符号。

常用运算符

运算符 示例 含义
 ( ) (1 + 2) * 3 调整运算符优先级 / 定义方法参数表 / 强制类型转换 / 定义多返回值接收表
 [ ] 数组1 [3] 访问数组成员
 { } { 1, 2, 3 } 定义类、接口、枚举、方法体 / 定义字节集、数组立即数 / 定义语句块
 < > 整数 n <公开> 定义属性表
 = a = b 赋值
 . 系统.对象 访问成员 / 描述数据类型完整名称
 <=> a <=> b 交换两个相同数据类型变量的值
如“a <=> b”等效于以下代码“c = a; a = b; b = c”,其中c是和a,b同类型的临时变量

算术运算符

运算符 示例 含义
 + a + b 加;求两数和
 - a - b 如果“-”在两个数值间,表示求此两数差;
如果“-”在单个数值前,表示反转其符号(正值变负值,负值变正值,为0则保持不变)
 * a * b 乘;求两数积
 / a / b 除;求两数商
 % a % b 求余;求两个整数相除(a/b)后的余数
 ++ a++; 递增;将变量值加1,等效于 a = a + 1。只能在一条语句中单行使用。
 -- a--; 递减;将变量值减1,等效于 a = a - 1。只能在一条语句中单行使用。
 += a += b 加赋值;等效于 a = a + b
 -= a -= b 减赋值;等效于 a = a - b
 *= a *= b 乘赋值;等效于 a = a * b
 /= a /= b 除赋值;等效于 a = a / b
 %= a %= b 求余赋值;等效于 a = a % b

双数运算结果精度保留规则(仅适用于能对两个操作数进行运算的运算符):

逻辑运算符

逻辑运算的结果只有两种可能,要么为“真”,要么为“假”。

运算符示例含义
 ==a == b判断两值是否相等
 !=a != b判断两值是否不相等
 >a > b判断a是否大于b
 >=a >= b判断a是否大于等于b
 <a < b判断a是否小于b
 <=a <= b判断a是否小于等于b
 &&a && b且;判断a和b是否都为“真”。
如果a为“假”,由于结果一定为“假”, 将不再判断b的值(如果b是一个表达式,该表达式不会被求值)。
 ||a || b或;判断a和b是否有一个为“真”。
如果a为“真”,由于结果一定为“真”, 将不再判断b的值(如果b是一个表达式,该表达式不会被求值)。
 !!a取反;如果a为“真”,则取反后结果为“假”,如果a为“假”,则取反后结果为“真”
三联判断式  见下文

EF支持“三联判断式”,其语法为:

a (==|!=|>|>=|<|<=) b (==|!=|>|>=|<|<=) c

其中a,b,c均可以是变量、常量或表达式。

“三联判断式”可以被理解为两个逻辑表达式的“&&”组合,只有两个表达式的值都为“真”,结果才为“真”,否则结果为“假”。 如“0 < x < 10”等效于“x > 0 && x < 10”。

位运算符

所谓“位运算”,即以“二进制位”为基础进行逐位运算。位运算的操作数均为整数,结果也是整数。

运算符示例含义
~~a位取反
&a & b位与
|a | b位或
^a ^ b位异或
<<a << 1左移位
>>a >> 1右移位
&=a &= b位与赋值;等效于:a = a & b
|=a |= b位或赋值;赋值等效于 a = a | b
^=a ^= b位异或赋值;等效于 a = a ^ b
<<=a <<= 1左移位赋值;等效于 a = a << 1
>>=a >>= 1右移位赋值;等效于 a = a >> 1

运算符优先级

在一条EF语句中,总是先计算优先级较高的运算符,后计算优先级较低的运算符。 如果同时出现优先级相同的多个运算符,则根据其出现位置按从左到右的顺序进行计算。

在下表中,排在最前面的优先级最高,排在最后面的优先级最低。

优先级运算符
1 ()(调整优先级,方法调用) []
2 .
3 ! ~ -(符号反转)
4 ++ --
5 ()(类型转换)
6 * /
7 %
8 + -(减)
9 << >>
10!= == < <= > >=
11 & ^ |
12 &&
13 ||
14 = *= /= %= += -= <<= >>= &= ^= |= <=>

可用小括号“()”调整运算优先级。如“1 + 2 * 3”先计算乘法再计算加法(因为 * 比 + 优先级高), 而“(1 + 2) * 3”则先计算加法再计算乘法(因为小括号的优先级高于 * )。

属性表

EF中广泛使用“属性表”。

属性表用于辅助定义“类库、类、接口、枚举、常量、变量、参数、友好名称……”等几乎所有程序实体。 属性表的位置通常紧跟在实体名称的后面(成员方法的属性表在其参数表之后)。

属性表中的属性分为“系统属性”和“扩展属性”。“系统属性”被编译器直接支持;“扩展属性”可由用户自行添加,并可通过反射机制读取。

属性表语法:

< {属性名称 | 属性名称 = 属性值} [/]>

不同的属性之间以空格间隔。属性值的数据类型只能为整数、小数、文本或逻辑型之一。

如果属性值为逻辑值“真”,则“属性名称 = 属性值”可以简化为“属性名称”。

逻辑型系统属性如其属性值为“真”,属性名称可以移出“<”和“>”之外,放到属性表所属实体的实体名称之前。

如果文本型属性值是合法名称,或是以逗号“,”分隔的多个合法名称,其两端的双引号可以被省略。

示例:

类 类1 <基类型 = 系统.对象  实现接口 = 接口1, 接口2, 接口3>
{
	//...
}

以下是系统属性及其说明:

中文名称 英文名称 数据类型 适用实体 说明
基类型BaseType文本类/接口
  • 指定类的基类。EF中的类有且只有一个基类。如果未指定其基类,其默认基类是“系统.对象”,它是所有类的最基础类。
  • 指定接口的基接口。EF中的接口有且只有一个基接口。如果未指定其基接口,其默认基接口是“系统.接口”,它是所有接口的最基础接口。
实现接口interfaces文本类指定类所实现的所有接口,多个接口名称之间用“,”号分隔。
输出事件表events文本类指定该类中可能引发的所有事件类名的列表,用作辅助可视化界面设计。多个表项之间用“,” 号分隔。
数据类型DataType文本类方法、类成员、变量、常量、参数指定实体的数据类型。
友好名称FriendlyName文本类方法定义方法的友好名称,用于实现类自然语言编程。请参考:友好名称
扩展属性 文本任意扩展属性是在属性表中由用户自行定义并使用的属性集合。该集合文本的格式等同于属性表,即各属性之间用空格分开,具有属性值的将用“=”号连接。可以在程序中通过反射机制读取到扩展属性值并进行相关处理。
注意:
扩展属性不能被直接设置,其设置方法为:如果属性表中的某属性名称以“$”符号引导,则被视为扩展属性的一部分,将被编译器组合到扩展属性中,例如:
  类 我的类 <公开 $我的属性 = 1>
其中“公开”是系统属性,而“我的属性”是扩展属性。
帮助文本DocString文本任意提供对本实体的详细说明信息
启动类MainClass文本类库指定当启动一个类库时从其哪一个类开始执行。请参考:类库的启动
分类信息表category文本类库指定类库的分类信息,多个表项之间用“,” 号分隔。请参照下面的“分类索引”属性。
依赖文件表DependentFiles文本类库提供类库所依赖的外部文件,多个表项之间用“,” 号分隔。
作者author文本类库指定类库的作者
其它信息others文本类库指定本类库的其他说明信息
版本version数值类库、类、接口、枚举指定类库或类型的版本号,格式为“a.b”,如 1.0
创建号BuildNumber数值类库指定类库的创建号
分类索引CategoryIndex数值类、接口、枚举指定类型在其所在类库中的所属分类,索引值(>=0)基于类库的“分类信息表”属性。
公开public逻辑类、接口、枚举、类方法、类成员公开的类、接口、枚举可以被其他类库所访问;公开的类成员和方法可以在其子类或该类外部被访问。
扩展protected逻辑类方法、类成员扩展的类方法和成员可以被其子类或嵌入类所访问,但不能在该类外部访问。
私有private逻辑类、接口、枚举、类方法、类成员私有的类、接口、枚举不能在其类库外被访问;私有的类方法和成员仅能在该类本身中被访问。
隐藏hidden逻辑任意具有此属性的程序实体说明其作者由于某种原因希望将其隐藏起来而不让用户知晓,因此探测器类程序应该将其跳过而不列出。
静态static逻辑类方法、类成员、变量请参考:静态变量和非静态变量,静态和常量数据成员,静态成员方法
常量const逻辑类成员、变量请参考:常量
事件处理EventHandler逻辑类方法指定本方法为事件处理方法。请参考:事件处理方法
属性property逻辑类方法指定本方法为属性方法。请参考:属性和属性方法
可为空nullable逻辑参数如果本参数的数据类型为类或接口,设置此属性表示该参数可以接收空对象,否则空对象将因为被编译器提前检查而不能被传递过来。
最终final逻辑类、接口表明本类或本接口为最终类或最终接口,不允许再作为其它类和接口的基类型。
扩展开始extendable逻辑参数表明从本参数开始到方法的最后一个参数之间的所有参数(包括两端)被视为一组扩展参数。请参考:方法扩展参数。
遍历方法enumerater逻辑方法表明本方法是某个遍历器类中的遍历方法。请参考:遍历循环。
类型转换TypeConverter逻辑方法表明本方法用作数据类型转换。请参考:类型转换方法。
可访问父对象AccessParent逻辑嵌入类表明本嵌入类需要访问其所在类的数据

变量和常量

变量

变量是程序中的可变元素,在程序运行过程中变量的值可以被改变。

所有的变量都必须定义在类的内部。不存在类之外的变量。请参考:类。

定义在类方法之内的变量被称为“局部变量”,定义在类方法之外(类之内)的变量被称为类的“成员”。

定义变量

定义变量使用以下语法:

数据类型 变量名称 [属性表] [= 初始值] {, 变量名称 [属性表] [= 初始值]};

注意:如果定义的是类成员,初始值只能是立即数,如果定义的是局部变量,初始值可以使用非立即数。

示例:

整数 整数1 = 123;
对象 对象1 <静态>; //等效于:静态 对象 对象1;
整数 整数2 = 100, 整数3 <静态> = 1; //等效于:整数 整数2 = 100; 静态 整数 整数3 = 1;

可以应用在属性表中部分系统属性(所有可用属性请参见属性表,下同):

系统属性类型含义
静态逻辑表明该变量仅在其第一次进入其作用域时被初始化,直到其所在类被卸载才结束其生命周期。
公开/扩展/私有逻辑仅在定义类成员变量时使用,可选择其中之一。
“公开”表示可以在任意类中访问该变量;“扩展”表示只能在本类、子类、嵌入类中访问该变量,“私有”表示只能在本类中访问该变量。如果三者均未指定,默认为“私有”。

变量的初始化

变量会在适当的时机被自动初始化。变量在第一次被使用之前必然已经被初始化。

如果定义变量时指定了初始值,将使用该初始值初始化该变量;如果定义变量时未指定初始值,将使用“零值”初始化该变量。

“零值”的含义与其类型相关:

数据类型“零值”的含义
整数0
小数0.0
逻辑假
文本""(长度为0的文本)
字节集{}(长度为0的字节集)
类空(空类对象)
接口空(空接口对象)
枚举0
数组成员数目为0的空数组

变量的初始化时机(视变量类别的不同而不同):

变量的类别初始化时机
类的静态成员类被加载时
类的非静态成员对象被创建时
方法中的静态变量类被加载时
方法中的非静态变量变量定义语句被执行时

使用变量

对变量赋值使用赋值运算符“=”,语法如下:

被赋值的变量 = 表达式;

其中表达式可以是常量值、变量、方法调用,或其他表达式。

读取变量的值,直接使用该变量的名称即可,语法如下:

欲读取其值的变量

示例代码:

整数 x, y;
x = 1;
y = x;
y = x + y;
x = 控制台.输入整数();

变量的作用域和生命周期

变量的作用域是“空间”上的概念,变量仅在其作用域内才可以被访问。

变量的生命周期是“时间”上的概念,变量仅存在于其生命周期之内(生命周期结束意味着变量的“消亡”)。

不同类别的变量,对其作用域和生命周期有不同的规定:

变量的类别作用域生命周期
类的静态成员公开成员的作用域为全局
扩展成员的作用域为本类及其子类、嵌入类
私有成员的作用域为本类
始于类被加载,止于类被卸载
类的非静态成员公开成员的作用域为全局
扩展成员的作用域是本类及其子类、嵌入类
私有成员的作用域是本类
始于对象被创建,止于对象被销毁
方法中的静态变量所在语句块始于所处类被加载,止于所处类被卸载
方法中的非静态变量所在语句块始于该变量定义语句被执行,止于该变量定义语句所处语句块被执行完毕

静态变量和非静态变量

具有“静态”属性的变量称为静态变量;不具有“静态”属性的变量称为非静态变量。请参考“定义变量”。

静态变量和非静态变量的作用域是一致的,所不同的是它们的生命周期。请参考“变量的作用域和生命周期”。

值变量和引用变量

以下数据类型的变量为“值存储”——即变量中存储的是实际值:

整数 小数 逻辑 枚举

这些类型称为“值类型”,类型为“值类型”的变量称为“值变量”。

以下数据类型的变量为“引用存储”——即变量中存储的是实际数据的“引用”:

文本 字节集 类 接口 数组

这些类型称为“引用类型”,类型为“引用类型”的变量称为“引用变量”。

可以有多个引用变量同时“引用”到同一个数据(文本、字节集、对象、数组等), 此时对这些变量的操作,均完全等效于对该数据的直接操作。

对变量赋值时,对“值变量”而言,被赋予的是数据的“值”,对“引用变量”而言,被赋予的是数据的“引用”。

对“引用变量”赋值,意味着取消其对原有数据的“引用”,并重新“引用”新的数据。

当“引用变量”结束其生命周期时,将被自动取消其对当前引用数据的“引用”。请参考“变量的作用域和生命周期”。

在程序运行中,如果某数据没有或不再被任何变量所“引用”,则该数据会随时被垃圾收集器清除。详见“垃圾收集器”。

常量

常量是程序中的不可变元素,常量的值在程序运行过程中不可被改变(由编译器保证)。

常量和变量是非常类似的。所不同的是,变量的值可以被改变,而常量的值不允许被改变。

所有的常量都必须定义在类的内部。不存在类之外的常量。请参考:类。

定义常量时所用语法与定义变量类似,但需要在属性表中添加“常量”属性:

数据类型 常量名称 [属性表] = 常量值 {, 常量名称 [属性表] = 常量值};

常量值可以是任意合法的立即数。

定义常量示例:

整数 常量1 <常量> = 101;
常量 整数 常量2 = 常量1 + 1;

可以通过常量名称访问常量的值。

数组

数组,顾名思义,是数据的组合,是“同一数据类型的数据”的“顺序组合”。

定义数组变量

语法:

数据类型[] 数组变量名称 [属性表] {, 数组变量名称 [属性表]};

在数据类型后面使用“[]”表示定义此数据类型的数组变量。

示例:

整数[] 整数数组;
对象[] 对象数组 <静态>;
对象[] 对象数组1 <静态>, 对象数组2;

注意:这里只定义了数组变量,数组的实际数据此时并不存在。 因为数组变量是“引用变量”(请参考“值变量和引用变量”),它本身并不存储数组数据,它存储的是对数组数据的“引用”。

在数组变量没有被赋值之前,它固定引用到一个对应数据类型的成员数目为0的空数组。

创建数组数据

语法:

创建 数据类型{[成员数]}+

创建出来的数组数据的各成员值均被初始化为“零值”。

示例:

创建 整数[3]; //创建有三个成员的整数数组,可存储3个整数,各成员被初始化为0。
创建 对象[2][3]; //创建二维对象数组,每维三个成员,共可存储6个“对象的引用”,各成员被初始化为“空”。

使用数组

访问数组成员:

数组名称{[索引值]}+

索引值用于指定成员在数组中的位置(维索引或成员索引),必然大于等于0且小于数组对应维的成员数。

可以按访问一维数组成员的形式访问多维数组成员,只需提供多维数组成员在“所有数组成员(而非其所在维的所有成员)”中对应的索引值(按“行优先”排列)。

示例:

整数[] 数组1 = { 1, 2, 3 };
控制台.输出行(数组1[0]); //输出数组1的第一个成员的值,1
控制台.输出行(数组1[1]); //2
控制台.输出行(数组1[2]); //3
//控制台.输出行(数组1[3]); //错误,不存在索引为3的数组成员
数组1[0] = 2006; //对数组的第一个成员赋值
控制台.输出行(数组1[0]); //2006

数组1 = 创建 整数[2][3]; //创建新的二维数组,并赋值给数组变量
控制台.输出行(数组1[1][2]); //输出数组1的第二维第三个成员的值,0
数组1[1][2] = 1 + 2; //对“数组1[1][2]”赋值
控制台.输出行(数组1[1][2]); //3
控制台.输出行(数组1[5]); //3,等效于:控制台.输出行(数组1[1][2]);

数组对象

同“基本类型对象”一样,数组数据在EF中也被视为一个特殊的对象,也有自己的成员方法和属性。

例如可以这样使用:

整数[] 整数数组 = { 1, 2, 3 };
控制台.输出行(整数数组.长度);
控制台.输出行(整数数组.到文本());

数组对象的成员方法和属性,由系统类库中的“数组类”提供技术支持,所有的数组变量和数组常量自动拥有“数组类”中定义的所有方法和属性。

当数组对象的成员方法或属性方法被调用时,数组数据将被作为第一个参数传入“数组类”类中定义的相应静态方法。

数组对象并不是真正的对象,它不是“系统.对象”的实例。使用数组对象,不涉及创建销毁临时对象和“装箱”“拆箱”等动作,不会付出任何效率上的代价。

请参考:基本类型对象。

语句和语句块

以半角分号“;”结尾的表达式称为语句。

被配对的大括号“{ }”包含的零或多条语句称为语句块。

语句块内部可以再嵌套定义语句块。

定义在语句块中的变量,其作用域范围就是其所在语句块。请参考:变量的作用域和生命周期。

如果嵌套使用语句块,不同层次的语句块中可以定义相同的名称(变量名称、嵌入方法名称等),但此时内层名称将覆盖外层名称。

流程控制语句

如果(if)

语法:

如果 (条件1) 语句块1 {又如 (条件2) 语句块2} [否则 语句块N]

其中,条件1、条件2……等必须是逻辑表达式;各语句块可以是一条语句,也可以是被“{”“}”包含的多条语句。 请参考:语句和语句块。

以上N个语句块最多只有一个被执行。

程序运行时,从前到后依次判断各条件(条件1、条件2……)是否成立。 如果某条件成立,则执行其对应的语句块(语句块1、语句块2……),并结束整个如果语句; 如果所有条件都不成立,且存在“否则”语句,则执行语句块N; 如果所有条件都不成立,且不存在“否则”语句,则所有语句块都不被执行。

代码示例:

整数 a, b, c;
文本 s;

//这是一个最简单的如果语句
如果 (a < 0)
	a = 0;

//下面是另一个如果语句(含“否则”)
如果 (b == 0)
	a++;
否则
	a += 2;

//下面是另一个如果语句(含“又如”“否则”)
如果 (c == 1)
	s = "甲";
又如 (c == 2)
	s = "乙";
又如 (c == 3)
	s = "丙";
否则
	s = "未知";

//另一个如果语句
如果 (a == 1)
{
	a++;
	b += 2;
}
否则
{
	a--;
	b -= 2;
}

假如(switch)

“假如”是一个多分支条件判断语句,其语法是:

假如 (条件表达式)
{
    {为 值表达式{, 值表达式}: 分支语句块}
    [为其他: 分支语句块]
}

从某分支的“:”分隔符开始到下一分支(或“假如”结束)之前的所有语句或语句块,均算做该分支中的分支语句块; “为其他”分支可以出现在其他分支之前,但最多只能出现一次。

“假如”语句首先计算条件表达式的值,然后从前到后逐个查找与该值相等的值表达式, 如果找到则执行其所属分支的分支语句块,如果未找到,且存在“为其他”分支,则执行“为其他”分支的分支语句块。

在一个“假如”语句中,最多只有一个“为”或“为其他”分支中的分支语句块被执行。 一旦某个分支语句块执行完毕,整个“假如”语句结束。

在分支语句块中,可以使用“跳出”关键字提前结束本“假如”语句。请参考:跳出。

示例:

假如 (控制台.输入整数())
{
	为 0:
		控制台.输出行("您输入的是0");

	为 1 + 1:
		控制台.输出行("您输入的是2");
		跳出;  //演示“跳出”语句的用法,在此处将导致后面的语句被跳过。
		控制台.输出行("没错,是2");  //此语句将不被执行

	为 3, 4, 5, "6".到整数():
		控制台.输出行("您输入的是3,4,5,6中间的某个数");

	为其他:
		控制台.输出行("猜不到您输入的是啥……");
}

条件循环(while)

条件循环语句有两种语法格式:

循环 (循环条件) 循环体

开始 循环体 循环 (循环条件);

其中循环体可以是任意合法的语句块,循环体中的代码会一直被循环执行,除非循环条件不再满足,或循环被“跳出”“返回”等语句提前结束。

第一种形式的循环语句,循环体可能不被执行(循环条件为假时);第二种形式的循环语句,循环体至少被执行一次。

循环语句可以嵌套(即某循环语句的循环体中可以包含其他循环语句),嵌套层次不限。

示例:

整数 i = 10;

循环 (i > 0)
{
	i--;
}

i = 10;
开始
{
	i--;
}循环 (i > 0);

计次循环(counter)

语法:

计次循环 (循环次数[, [数据类型] 整数型循环变量]) 循环体

“计次循环”的循环次数由参数循环次数指定。 如果提供了整数型循环变量,则该变量值将在第一次循环之前被初始化为0,并在每个单次循环结束后递增1。 通过提供数据类型可以就地定义欲使用的循环变量,此时该变量的作用域被限定在循环体内部。

示例:

计次循环 (3, 整数 i)
{
	控制台.输出(i); //将分别输出 0 1 2
}

遍历循环(foreach)

遍历循环主要用于逐个遍历某个遍历器对象或数组中的所有成员。

遍历循环的基本语法为:

遍历循环 (遍历器对象/数组, [数据类型] 接收变量) 循环体

其中,遍历器对象的类必须支持遍历循环(见下文),接收变量的数据类型必须与遍历器对象或数组的成员相匹配。 通过提供数据类型可以就地定义欲使用的接收变量。

遍历循环将逐次从遍历器对象/数组中取出一个数据赋值给接收变量,直到取出所有数据为止。

示例:

整数[] 数组1 = { 1, 2, 3, 4, 5, 6 };
遍历循环 (数组1, 整数 i)
{
	控制台.输出(i); //将分别输出 1 2 3 4 5 6
}

支持遍历循环

若想让自己的类支持“遍历循环”,需满足以下条件:

下面的示例代码定义了一个支持遍历循环的类:我的可遍历类。

公开 类 我的可遍历类
{
	公开 我的遍历器 创建遍历器()
	{
		返回 (创建 我的遍历器());
	}
}

公开 类 我的遍历器
{
	私有 整数 num = 0;

	公开 整数,逻辑 取下一个整数() <遍历方法>
	{
		num++;
		如果(num >= 6)
			返回 (0, 假);
		否则
			返回 (num, 真);
	}
}

因为“我的遍历器.取下一个整数”这个遍历方法的第一个返回值为整数类型,所以利用“遍历循环”可以从“我的可遍历类”对象中遍历出整数数据:

我的可遍历类 x = 创建 我的可遍历类;
遍历循环 (x.创建遍历器(), 整数 i)
{
	控制台.输出(i); //将分别输出 1 2 3 4 5
}

注意上面的代码中,“遍历循环”的第二个参数只能是整数类型(或兼容整数的类型,如小数、通用型等),这是因为前面“我的遍历器”中只提供了返回整数的遍历方法。

要想让“我的遍历器”在“遍历循环”中支持更多的数据类型,就需要为其增加相应的遍历方法。 作为示例,下面为“我的遍历器”类添加一个可遍历出文本数据的遍历方法:

公开 文本,逻辑 取下一个文本() <遍历方法>
{
	(整数 i, 逻辑 b) = 取下一个整数();
	返回 (i.到文本(), b);
}

有了上面这个遍历方法,就可以遍历出文本了:

遍历循环 (x.创建遍历器(), 文本 s)
{
	控制台.输出(s);
}

C循环(for)

“C循环”类似于C语言中的 for 循环。提供此循环语句主要是为了兼容部分C/C++/Java/C#程序员的使用习惯。

C循环的语法:

C循环 ([初始化语句]; [判断表达式]; [循环尾语句]) 循环体

其中,初始化语句、循环尾语句可以是一条语句也可以是被“{ }”包含的多条语句,而判断表达式必须是一个逻辑表达式。

对C循环的执行步骤具体描述如下:

步骤1:执行初始化语句
步骤2:计算判断表达式的值,如果结果为“假”则结束循环
步骤3:执行循环体(如果遇到“跳出”“返回”等语句,则结束循环)
步骤4:执行循环尾语句
步骤5:回步骤2

示例:

//以下循环将输出 "0123456789"
for (int i = 0; i < 10; i++)
{
	控制台.输出(i);
}

//以下循环将输出 "0 01 012 0123 01234 012345 "
C循环 ({ 整数 i = 0; 文本 s = "0"; }; i < 6; { i++; s += i.到文本(); })
{
	控制台.输出(s + " ");
}

注意:C循环的循环体中不能再定义“循环尾语句块”(见下文),因为已经有了循环尾语句参数。

到循环尾(continue)

除“C循环”之外的各循环语句都具有一个“循环尾语句块”,位于循环体的最后。

“循环尾语句块”是其所属循环语句的循环体的一部分,总是会在每个单次循环过程结束时被执行,除非循环被“跳出”或“返回”等语句提前结束。

默认的循环尾语句块是空的,可以在循环体的尾部使用“循环尾”关键字来显式定义非空的循环尾语句块,格式为:

循环尾 循环尾语句块

循环尾语句块可以是单行语句也可以是被“{ }”包含的语句块。

“到循环尾”语句可以将下一执行语句位置转移到当前循环的循环尾语句块首部,例如:

//以下循环将输出 "02"
计次循环 (3, 整数 i)
{
	如果 (i == 1)
		到循环尾;
	控制台.输出(i);
	//由于没有显式定义循环尾,前面的“到循环尾”语句执行后将转移到此处。
}

显式定义了循环尾的示例:

//以下循环将输出 "02"
整数 i = 0;
循环 (i < 3)
{
	如果 (i == 1)
		到循环尾;
	控制台.输出(i);
	循环尾 //显式定义循环尾,前面的“到循环尾”语句执行后将转移到此处。
	{
		i++;
	}
}

跳出(break)

在“假如”中,可以随时使用“跳出”语句将下一执行语句位置转移到当前假如语句块之后。

在各循环语句(循环,计次循环,遍历循环,C循环)中,可以随时使用“跳出”语句将下一执行语句位置转移到当前循环语句块之后。

示例:

//以下代码将输出 "0123。"
计次循环 (10, 整数 i)
{
	如果 (i > 3)
		跳出;
	控制台.输出(i);
}
控制台.输出("。");

友好名称

友好名称,是对“人”友好的名称,也是EF支持类自然语言编程的具体体现。

EF认为,程序员不应该总是面对冰冷的、抽象的语法。程序员首先是“人”,编程语言理应对“人”友好,对“机器”严格。

通过引入“友好名称”,EF实现了“类自然语言编程”。

友好名称也有“参数”的概念,但它的参数可以出现在友好名称中间的任意位置,参数的顺序也不重要——而不象类方法那样,参数只能顺次放在方法名称的后面(还要用小括号括起来)。

全局友好名称

语法:

友好名称 名称 = 值;

其中,名称中间的任意位置均可以定义参数:

<参数名称>

参数名称要求是合法的标识符。参数没有数据类型,参数的个数不限。

值应是合法语句,其中可以通过参数名称直接使用名称中定义的友好名称参数。

定义友好名称示例:

友好名称 '在控制台上显示<某东西>' = 控制台.输出行(某东西);
友好名称 '将<某东西>显示到控制台上' = 控制台.输出行(某东西);

使用友好名称示例:

//以下三行都相当于调用:控制台.输出行(123);
'在控制台上显示123';
'将123显示到控制台上';
'将<某数值>显示到控制台上'(某数值 = 123);

//以下三行都相当于调用:控制台.输出行("祖国您好!");
'在控制台上显示"祖国您好!"';
'将"祖国您好!"显示到控制台上';
'在控制台上显示<我的祝福>'(我的祝福 = "祖国您好!");

//在友好名称中使用已有变量
文本 祝福语 = "祖国您好!";
'在控制台上显示<祝福语>';

下面是一个应用友好名称的完整的“祖国您好”例程:

友好名称 '将<某东西>显示到控制台上' = 控制台.输出行(某东西);

公开 类 启动类
{
	公开 静态 启动()
	{
		'将"祖国您好!"显示到控制台上';
	}
}

方法友好名称

在定义类成员方法时,可以通过属性表中的“友好名称”属性定义适用于本方法的友好名称。请参考:属性表。

“友好名称”的属性值为文本型,其中可以通过 <n> 指定相应参数出现在友好名称中的位置 ,n为大于等于1的整数,用作代表该方法对应位置处的参数,如 <1>表示方法的参数1,<2>表示方法的参数2,依次类推。

定义示例:

公开 邀同学散步(学生 某同学) <友好名称 = "邀<1>散步">
{
	某同学.散步();
}

使用示例:

学生 张三 = 创建 学生("张三");
学生 李四 = 创建 学生("李四");
张三.'邀<李四>散步'; //等效于:张三.邀同学散步(李四);
张三.'邀<某人>散步' (某人 = 李四); //同上

类

定义类及其成员

定义类:

类 类名称 [属性表]
{
    {类数据成员定义}
    {类方法成员定义}
}

定义类的数据成员:

数据类型 数据成员名称 [属性表] [ = 初始值] {, 数据成员名称 [属性表] [ = 初始值]} ;

类数据成员是变量的一种,它的作用域是其所属类。

定义类的方法成员:

[返回值数据类型 {,返回值数据类型}] 方法名称 ([参数定义表]) [属性表]
{
    方法体
}

其中参数定义表的语法为:

数据类型 参数名称 [属性表] [ = 参数默认值] {, 数据类型 参数名称 [属性表] [ = 参数默认值] }

在类成员方法的方法体中,可以使用本类中所有数据成员和方法成员。 如果方法体中有参数或局部变量的名称与类成员名称同名,则后者被覆盖,但仍可通过“本对象”关键字明确地访问后者。

在类成员方法的方法体中,也可以通过“基类”关键字明确访问基类的非私有数据成员和方法成员。

在定义类及其成员和方法时 可使用的系统属性请参见“属性表”。

示例:

公开 类 我的类1
{
	整数 i;

	公开 整数 取数值()
	{
		返回 i;
	}

	公开 置数值(整数 i)
	{
		本对象.i = i;
	}
}

静态和常量数据成员

具有“静态”属性的类数据成员是“静态数据成员”,具有“常量”属性的类数据成员是“常量数据成员”。

静态数据成员数据只有一份,被本类的所有对象所共享,在类被加载时自动初始化;常量数据成员属于立即数范畴。

外界访问类的静态或常量数据成员必须通过“类名称”:

类名称.静态或常量数据成员名称

外界访问类的其它数据成员必须通过该类的对象:

[对象.]对象数据成员名称
如果对象为本对象,则可以被省略。

静态成员方法

具有“静态”属性的类成员方法是“静态成员方法”。

静态成员方法体中只能访问类的静态数据成员或静态成员方法。它属于“类”,而不属于类的“对象”。

外界调用类的静态成员方法必须通过“类名称”:

类名称.静态成员方法名称([参数表])

外界调用类的非静态成员方法必须通过该类的对象:

[对象.]对象成员方法名称([参数表])

如果对象为本对象,则可以被省略。

参数表是以半角逗号“,”分隔的参数数据的组合:

参数{, 参数}

初始化、清理、静态初始化

“初始化”和“清理”是类的特殊成员方法。当类的对象被创建时,相应“初始化”方法被自动调用;当类的对象被销毁时,“清理”方法被自动调用。

“静态初始化”是类的特殊静态成员方法。当类被加载时,此方法将被调用。

“初始化”、“清理”、“静态初始化”的英文名称分别为:“init”、“clean”、“StaticInit”。

“初始化”方法的原型是:

公开 初始化([参数定义表])

“清理”方法的原型是:

公开 清理()

“静态初始化”方法的原型是:

公开 静态 静态初始化()

在定义类时,以上所有方法均不是必须被定义的。

编译器在编译子类的任一初始化方法时,如果发现其中不存在调用其基类初始化方法的语句,将自动在该初始化方法首部插入一条调用其基类默认初始化方法(即无参数的初始化方法)的语句,以确保对象中的基类数据能够得到相应初始化。

系统在调用类的“清理”方法之后,总是会调用其基类的“清理”方法。这是一个递归过程,最终的结果是,该类的最基础类(系统.对象)的“清理”方法被最后调用。

属性和属性方法

具有“属性”属性且符合特定格式的类成员方法称为“属性方法”。请参考:属性表。

类通过定义“属性方法”的形式定义“属性”。属性方法的名称即属性的名称。

属性方法分为属性读取方法(getter)和属性设置方法(setter)。这两种属性方法的方法名称是相同的,只有返回值和参数定义不同。如果定义了属性读取方法,则此属性“可读”;如果定义了属性设置方法,则此属性“可写”(可赋值)。

属性读取方法的原型是:

公开 [静态] 属性 数据类型 属性名称()

属性设置方法的原型是:

公开 [静态] 属性 属性名称(数据类型 参数)

示例:

类 动物
{
	私有 整数 年龄值; //私有成员,用于存储属性值

	公开 属性 整数 年龄()
	{
		返回 年龄值;
	}

	公开 属性 年龄(整数 值)
	{
		年龄值 = 值;
	}
}

读取属性值的语法是:

[对象.]属性名称

对属性赋值的语法是:

[对象.]属性名称 = 值;

如果对象为本对象,则可以被省略。

综上所述:读取属性的值,实质上是调用属性读取方法;对属性赋值,实质上是调用属性设置方法。

示例:

动物 狗 = 创建 动物();
狗.年龄 = 3; //对属性赋值
控制台.输出(狗.年龄); //读取属性的值

属性和数据成员的区别:访问属性实际上是调用相关属性方法,访问数据成员则是直接访问该数据成员所在地址。

事件处理方法

具有“事件处理”属性且符合特定格式的类成员方法称为“事件处理方法”。

事件处理方法的原型是:

公开 [静态] 事件处理 逻辑 事件处理方法名称(事件类名称 参数)

示例:

公开 事件处理 逻辑 按钮1_被单击(被单击事件 事件)
{
	//......
	返回 真;
}

类型转换方法

具有“类型转换”属性的公开非静态方法,称为“类型转换方法”。

通过类型转换方法,可以实现从其它特定类型数据转换到本类型对象(转入),或从本对象转换到其它特定类型数据(转出)。

类型转换方法只支持基本类型数据或所有数组类型数据的转入或转出。

类型转换方法可以细分为类型转入方法和类型转出方法。

类型转入方法的原型是:

公开 初始化(欲转入的类型 欲转入的值) <类型转换>

类型转出方法的原型是:

公开 欲转出的类型 类型转换方法名称() <类型转换>

当需要将其它类型数据转换到本类型时,编译器将查找具有相应参数类型的类型转入方法(同时也是初始化方法), 根据此初始化方法创建出一个新的对象,并将此新对象作为转换后的结果。

当需要将本对象转换到其它类型数据时,编译器将查找并调用具有相应返回值类型的数据转出方法,并将其返回值作为转换后的结果。

定义类型转换方法示例:

公开 类 某类
{
	私有 整数 n;

	公开 初始化(整数 n) <类型转换>
	{
		本对象.n = n;
	}

	公开 文本 到文本() <类型转换>
	{
		返回(n.到文本());
	}
}

使用类型转换示例:

某类 obj = 123; //等效于:某类 obj = 创建 某类(123);
文本 s = obj;   //等效于:文本 s = obj.到文本();
如果编译器同时找到多个符合要求的类型转出方法,将自动优先使用能够返回最大精度数据的类型转出方法。

示例:

公开 类 启动类
{
	公开 静态 启动()
	{
		类1 obj = "123.456";
		//下面两行输出,不管2是整数还是小数,根据最大精度原则,
		//都将自动选择“取小数”转换方法,所以结果都是246.912。
		控制台.输出行(obj * 2);
		控制台.输出行(obj * (小数)2);
		//同样原因:
		控制台.输出行(obj == 123); //等效于:控制台.输出行(123.456 == 123)
		控制台.输出行(obj == 123.456);
		控制台.输出行(-obj); //等效于:控制台.输出行(-123.456)
	}
}

类 类1
{
	文本 m_s;

	公开 初始化(文本 s) <类型转换>
	{
		m_s = s;
	}

	公开 整数 取整数() <类型转换>
	{
		返回 m_s.到整数();
	}

	公开 小数 取小数() <类型转换>
	{
		返回 m_s.到小数();
	}
}

方法重载

在一个类中,如果多个方法(不包括基类中的方法)具有“相同的方法名称和不同的参数形式”,那么称这个方法名称被“重载”。

“不同的参数形式”意味着:参数个数不同,或参数个数相同但参数类型不完全不同。

编译器将根据实际传入的参数数据选择调用匹配的重载方法。

方法参数默认值

定义一个方法时,可以为任意参数指定一个“默认值”。一旦某参数有了默认值,调用方法时可以省略提供该参数值,此时参数默认值将被视为实际参数值。

定义具有默认参数值的方法示例:

某方法(整数 a = 0, 文本 b = "")
{
	//...
}

其中,参数a的默认值被指定为0,参数b的默认值被指定为""。

可以通过以下几种形式调用此方法:

某方法(1, "abc"); //普通调用形式,为所有参数都指定参数值
某方法(1); //第二个参数值被省略,等效于:某方法(1, "") 或 某方法(1, )
某方法(, "abc"); //第一个参数值被省略,等效于:某方法(0, "abc")。
某方法(); //两个参数值都被省略,等效于:某方法(0, "") 或 某方法(,)。

方法参数扩展

定义一个方法时,可以指定其最后一个或多个参数为可扩展参数,调用此方法时可以同时提供多组被扩展参数值。

从具有“扩展开始”系统属性的参数到最后一个参数之间的所有参数(包括两端),作为一个整体,被视为一组可扩展参数。

参数被扩展时,是以“组”为单位的,即同时扩展“组”内所有参数。

同一个方法中最多只能有一个参数具有“扩展开始”属性。

假设有以下方法定义:

某方法1(整数 a <扩展开始>)
{
	//...
}

可以用以下方式调用此方法:

某方法1(1); //不提供扩展参数(本方法本来就定义有一个参数,不算扩展)
某方法1(1, 2); //提供一个扩展参数
某方法1(1, 2, 3); //提供两个扩展参数
某方法1(1, 2, 3, 4, 5, 6); //提供五个扩展参数
//...

前面已经提到,可扩展参数是“组”为单位的,个数可以多于一个:

某方法2(整数 a <扩展开始>, 文本 b)
{
	//...
}

可以用以下方式调用此方法——注意必须以“组”为单位提供扩展参数值:

某方法2(1, "a"); //不提供扩展参数
某方法2(1, "a",  2, "b"); //提供一组扩展参数
某方法2(1, "a",  2, "b",  3, "c"); //提供两组扩展参数
//...

在具有可扩展参数的方法实现代码中,可借助系统函数“下一组参数”逐一获取调用者提供的每一组扩展参数:

某方法2(整数 a <扩展开始>, 文本 b)
{
	开始
	{
		控制台.输出行(a);
		控制台.输出行(b);
	}
	循环(下一组参数()); //切换到调用者所提供的下一组参数值
}

方法的多返回值

在EF中,方法可以有多个返回值。

定义多返回值数据类型语法:

数据类型 {, 数据类型}

定义多返回值方法示例:

文本,整数 取姓名和年份()
{
	返回("易语言.飞扬", 2007);
}

多返回值接收表语法:

( [数据类型] [接收变量] {, [数据类型] [接收变量]} )

其中“数据类型”用作在多返回值接收表中现场定义一个局部接收变量。

接收多返回值示例:

(文本 姓名, 整数 年份) = 取姓名和年份(); //接收多个返回值
姓名 = 取姓名和年份(); //只接收第一个返回值
(,年份) = 取姓名和年份(); //只接收第二个返回值

嵌入方法

EF允许在方法的方法体内定义其它方法,此方法称为嵌入方法。

说明:

整数 方法1(整数 x)
{
	整数 y = 0;

	整数 方法2() //嵌入方法,可以放在方法内的任意位置
	{
		整数 z = 123;
		返回 x + y + z;
	}

	返回 方法2(); //所调用嵌入方法必须在调用语句位置前定义
}
静态 方法1()
{
	方法2() //虽然此方法没有给定“静态”属性,但由于其在静态方法“方法1”中,所以默认具有静态属性。
	{
		控制台.输出行("方法2");
	}

	方法2();
}
方法1(整数 n1)
{
	整数 n2;

	方法2(整数 n3)
	{
		整数 n4;
 
		整数 方法3(整数 n5) //嵌入方法内的嵌入方法
		{
			返回 n1 + n2 + n3 + n4 + n5; //访问父方法的参数及所处语句块内定义的局部变量
		}

		整数 n6; //n6不能被“方法3”访问,因为它定义在“方法3”的后面。
		方法3(n3 + n6);
	}

	方法2(n1);
}
方法1()
{
	方法2()
	{
	}

	方法3()
	{
		方法4()
		{
			方法1(); 方法2(); 方法3(); //访问定义在“方法4”前的方法
			方法4(); //访问自身
			方法5(); 方法6(); 方法7(); //访问与“方法4”紧接定义的方法
		}

		方法5()
		{
		}
	}

	方法6()
	{
	}

	//整数 n = 1; //如果本语句被允许,则由于“方法7”不再与“方法4”紧接,后者中的代码将不能再访问前者。

	方法7()
	{
	}
}
公开 类 启动类
{
	公开 静态 启动()
	{
		你好()
		{
			控制台.输出行("嵌入方法");
		}

		你好(); //由于未明确指定,嵌入方法“你好”将覆盖同名非嵌入成员方法。
		启动类.你好(); //有明确指定时将强制调用非嵌入成员方法
	}

	静态 你好()
	{
		控制台.输出行("非嵌入成员方法");
	}
}

创建对象

语法:

创建 类名称[([参数表])]

编译器将根据参数表自动搜寻并调用合适的“初始化”方法。

创建类的对象,会导致该类及其所有基类中的“初始化”方法被调用。最基础类(系统.对象)的“初始化”方法被最先调用,该类自身的“初始化”方法被最后调用。 如果其中某个类有多个重载的“初始化”方法,编译器将选择能够匹配参数表的方法调用。

示例:

类 学生
{
	私有 文本 姓名;

	公开 初始化()
	{
		姓名 = "未知";
	}

	公开 初始化(文本 姓名)
	{
		本对象.姓名 = 姓名;
	}
}
学生 学生1 = 创建 学生; //创建“学生”对象同时调用其“初始化()”方法。
学生 学生2 = 创建 学生("张三"); //创建“学生”对象同时调用其“初始化(文本 姓名)”方法。

对象的销毁

对象何时被销毁以及如何销毁,是编译器和“垃圾收集器”的工作,用户完全不需要关心。

一般情况下,如果不存在循环引用,对象将在“引用此对象的所有变量生命周期都结束后”被立刻销毁。 但因为循环引用的可能性始终存在,对象在何时被销毁是不可知的。 请参考:变量的作用域和生命周期。

在对象被销毁之前,其“清理”方法会被系统调用,用户可在此方法中做一些处理,但这种需求并不多见。

类的封装、继承和多态

类可以使用私有成员存储内部数据,可以使用私有方法对内部数据进行处理, 可以通过公开或扩展的成员和方法对外界提供操作接口,这些都是类的封装机制的体现。

子类继承自基类,则自动拥有基类中定义的所有公开、扩展的成员和方法,这是类的继承机制的体现。

子类可以覆盖基类的公开或扩展非静态方法,当通过对象来调用这些公开或扩展的非静态方法时,采用的是动态绑定策略, 即根据对象的真实类型(运行时才能确定)而非变量类型(编译时即可确定)来决定实际被调用的方法。 这是多态机制在类和对象方面的体现。

欲获取更多信息,请查阅任何一本面向对象编程的相关书籍。

接口

定义接口

接口 接口名称 [属性表]
{
    {方法声明}+
}

接口只声明一个或多个公开的方法,但不提供方法的实现代码。

定义接口方法时可以省略掉方法的“公开”属性,编译器默认其具有此属性。请参考:属性表。

定义接口示例:

接口 计算接口 <公开>
{
	整数 相乘(整数 数值1, 整数 数值2);
	整数 相加(整数 数值1, 整数 数值2);
}

实现接口

说明:

接口类似一种“规范”,如果某个类实现了此接口,表明此类遵循了此规范,以后就可以按照这种规范的要求来使用此类。

定义类时,通过“实现接口”属性(请参考“属性表”)来指定该类所实现的 所有接口:

实现接口 = 接口名称 [, 接口名称]

示例:

类 类1 <实现接口 = 计算接口> //实现前面定义的“计算接口”
{
	公开 整数 相乘(整数 数值1, 整数 数值2)
	{
		返回 (数值1 * 数值2);
	}

	公开 整数 相加(整数 数值1, 整数 数值2)
	{
		返回 (数值1 + 数值2);
	}
}
计算接口 接口对象 = 创建 类1; //实现了某接口的类对象可以被转换到该接口的对象
控制台.输出行(接口对象.相加(1, 2)); //调用接口对象的“相加”方法实际上是调用“类1”的“相加”方法。

接口与多态

如上文所述,一个非空接口对象总是持有一个对相关类对象的引用,对该接口对象的方法调用实际上等效于对该类对象的方法调用。 因而,多态在接口上的表现与在类和对象上的表现完全一致。请参考:类的封装、继承和多态。

枚举

枚举是一种扩展类型,枚举成员是“常量值”,其数据类型即为其所属的枚举类型。

枚举变量的取值范围是固定的,只能是其对应枚举类型的枚举成员之一,因此枚举数据类型可以看作是“具有有限整数值集合”的整数数据类型。

定义枚举:

枚举 枚举类型名称 [属性表] { 成员 [属性表] [= 值] {, 成员 [属性表] [= 值]} }

其中,值只能为整数值。如果未指定第一个枚举成员的值,其值默认为0;如果后面某个枚举成员的值未指定,其值默认为其前一个成员值加1。

示例:

枚举 X { a, b, c}
枚举 星期 { 星期一 = 1,  星期二, 星期三, 星期四, 星期五, 星期六, 星期日 }

访问枚举成员使用如下语法:

枚举类型名称.枚举成员

如:

X.a
星期.星期六
星期 星期几 = 星期.星期六;

嵌入类型

类、接口、枚举除了单独定义,同样可以定义在其它的类或接口体内,此时称为嵌入类型,嵌入类型所处的类型称为其父类型。

嵌入类型可以嵌套,也就是说嵌入类型内部可以继续定义嵌入类型。

可以采用类似成员访问的方式来访问定义在其它类型内的嵌入类型,格式为:

[所处类型名称.]嵌入类型名称

如果在嵌入类型的直接父类型/嵌入类型本身/其内部嵌套嵌入类型中,引用嵌入类型时所处类型名称可以被省略。

示例:

公开 类 启动类
{
	公开 静态 启动()
	{
		创建 类1.嵌入类1().方法1(); //外部访问“类1”中的“嵌入类1”必须使用“类1.嵌入类1”形式。
	}
}

类 类1
{
	公开 类 嵌入类1  //嵌入类
	{
		公开 方法1()
		{
			控制台.输出行("嵌入类");
		}
	}

	公开 方法2()
	{
		创建 嵌入类1().方法1(); //“嵌入类1”的直接父类“类1”中可以直接访问“嵌入类1”。
	}
}

访问嵌入类的父对象:

嵌入类是嵌入类型的一种,嵌入类如果不能访问父类型中的成员,其使用方法与普通类没有区别。若想嵌入类可以访问其父类型中的成员,必须在定义该嵌入类时设置其具有“可访问父对象”属性。

定义了此属性后,该嵌入类将自动具有一个名为“父对象”(英文名称为"parent")的数据成员,其数据类型为该嵌入类的直接父类型,可以通过此“父对象”来访问父类型 中的任意方法和数据成员。

示例:

类 类1
{
	公开 类 嵌入类1 <可访问父对象> //定义“可访问父对象”属性
	{
		公开 方法1()
		{
			控制台.输出行(父对象.s1); //访问其父对象中的"s1"数据成员。
			父对象.方法2(); //访问其父对象中的“方法2”成员方法。
		}
	}

	文本 s1;

	方法2()
	{
		//方法体省略
	}
}

具有“可访问父对象”属性的嵌入类对象创建规则:

如果嵌入类可访问父对象且其父类型为接口,按照上面的第二条规则,它可以在实现了该接口的类中创建其对象。这是一个很有用的特性,可以用作在设计接口时就提供相应的接口应用类。

示例:

接口 输出接口
{
	输出(文本 s);

	公开 类 输出类 <可访问父对象> //利用接口中定义的方法建立一个接口应用类
	{
		公开 输出(整数 n)
		{
			//此时父对象为类型为“输出接口”的接口对象,
			//虽然其“输出”方法目前还未实现,但在创建“输出类”对象时创建方将提供此方法的实现。
			父对象.输出(n.到文本());
		}
		公开 输出(小数 f)
		{
			父对象.输出(f.到文本());
		}
	}
}

类 类1 <实现接口 = 输出接口>
{
	公开 输出(文本 s) //实现“输出接口”中的“输出”方法
	{
		控制台.输出行(s);
	}

	公开 方法1()
	{
		输出接口.输出类 obj = 创建 输出接口.输出类; //使用“输出接口”中定义好的接口应用类
		obj.输出(123);
		obj.输出(123.456);
	}
}

匿名嵌入类

匿名嵌入类是嵌入类型的一种,用作在创建对象的同时提供此对象的类实现。

语法:

创建 类或接口名称[([参数表])]
{
    {类数据成员定义}
    {类方法成员定义}
} [属性表]

如果类或接口名称为类名称,将创建一个以此类名为基础类的匿名子类对象,其子类成员和方法在后续的"{ }"中定义。

如果类或接口名称为接口名称,将创建一个实现了此接口的匿名类对象,其类成员和方法在后续的"{ }"中定义。

属性表用作为所建立的匿名类提供附加属性,可以被省略。

示例:

创建 对象 //创建基于“系统.对象”类的匿名类对象并调用其方法
{
	公开 方法1()
	{
		控制台.输出行("方法1");
	}
}.方法1();

示例:

接口 接口1
{
	方法1();
}
创建 接口1 //创建实现了“接口1”接口的匿名类对象并调用其方法
{
	公开 方法1()
	{
		控制台.输出行("方法1");
	}
}.方法1();

访问匿名嵌入类的父对象:

可以利用属性表为匿名类提供“可访问父对象”属性,以支持访问其所在类的成员和方法。

示例:

类 类1
{
	公开 方法1()
	{
		创建 对象
		{
			公开 方法2()
			{
				控制台.输出行(父对象.s);
			}
		}<可访问父对象>.方法2(); //通过加上“可访问父对象”属性来支持访问匿名类的父对象成员。
	}

	文本 s = "类1";
}

“动态类型”和“弱类型”

通过在系统类库中引入“通用型”、“数值型”、“数组型”等类,EF在一定程度上做到了“动态类型”和“弱类型”。

通用型可以匹配所有数据类型(包括数组类型);数值型可以匹配整数和小数类型;数组型可以匹配所有数组类型。 这三个类是通过提供多个“类型转换方法”达到此目的的,请参考:类型转换方法。

以通用型为例,可以将其它类型数据赋值给通用型变量,也可以将通用型变量赋值给其它数据类型变量:

通用型 v = 123;
v = 真;
v = 100.123;
文本 s = v;

如果某方法的参数为“通用型”,则该参数可以接收各种类型的参数值。

如系统类库中的类方法“控制台.输出行”,其参数就是通用型:

公开 静态 控制台.输出行(通用型 数据)
控制台.输出行(123);
控制台.输出行(100.123);
控制台.输出行(真);
控制台.输出行("文本");
控制台.输出行( {1, 2, 3} );
//...

预编译

EF支持预编译处理。“预编译”是“编译”前的一个环节。通过预编译可提供“条件编译”等特性。

预编译常量

预编译常量可以是以下三种数据类型之一:逻辑、整数、文本。其数据类型不需要明确指定,预编译处理器会依据定义此预编译常量时所指定的值自动判断其类型。

注意:预编译常量只能在预编译指令中使用。

预编译常量可以在源代码文件(.ef)或类库信息定义文件(.inf)中定义,其语法为:

#定义 预编译常量名称 [预编译常量值]

其中,预编译常量值可以是立即数(限逻辑、整数、文本类型)、其它已定义的预编译常量,或二者组成的表达式。 如果预编译常量值被省略,其默认值为逻辑值“真”。

此外,也可以在编译命令行中定义值为“真”的逻辑型预编译常量。请参考:编译器。

如果使用了未定义的预编译常量, 编译器将报错。

可通过预编译函数“是否定义”来判断某预编译常量是否已被定义:

是否定义(预编译常量)

预编译函数只能在预编译指令中使用。

预编译常量的作用域

预编译常量仅在其所属作用域内有效,出了作用域,将被视为未定义。

预编译指令

EF中的预编译指令有:#如果、#又如、#否则、#结束、#错误。

“#如果、#又如、#否则、#结束”,用于实现条件编译(只有满足条件的代码会被编译,其它代码将被编译器忽略), 其用法与流程控制语句中的“如果、又如、否则、结束”等类似,稍有不同的是“#如果”和“#结束”必须配对使用。 请参考:如果。

“#错误”用于向编译器报告相应错误并停止编译,其用法为:

#错误 错误文本

示例:

#定义 操作系统类型 "linux"

#如果 操作系统类型 == "linux"
	控制台.输出行("您好,Linux!");
#又如 操作系统类型 == "windows"
	控制台.输出行("您好,Windows!");
#否则
	#错误 "未知操作系统"
	控制台.输出行("您好,贵姓?");
#结束

类库

EF的类库分为两大类:

被加载之前,“EF类库”和“EF本地类库”有表面的、形式上的不同;被加载之后,从形式到内容都完全一致。双方可以相互使用另一方定义的公开类型。除非明确区分,两者可统称为EF类库。

EF类库的“文件名称(不含路径和后缀名)”与“类库名称”必须相同,类库中定义的类型完整名称也必须以“类库名称.”开头。 例如:系统类库(系统.efn)的文件名称是“系统”,库名称也是“系统”,系统类库中定义的所有类型的完整名称均以“系统.”开头(如“系统.对象”、“系统.控制台”等)。

系统类库

系统类库(系统.efn)是本地类库,它是EF的核心类库,为EF程序和其他类库提供最基础支持:

系统类库是运行EF程序所必需的类库。

类库的加载

EF采用动态的类库加载机制——仅在第一次使用某类库时才加载它。

加载类库时,首先在系统环境变量“EF_LIB_PATHS”所指定的目录中查找类库文件,如果未找到,会继续在可执行文件所在目录中查找。 对于本地类库(*.efn),还会在操作系统默认路径中查找。如果最终仍未找到相应的库文件,则加载失败。

系统环境变量“EF_LIB_PATHS”的内容格式为:

目录 {(,|;) 目录}

其中目录必须是绝对路径。

已经加载的类允许卸载或强制重新加载。(尚未实现)

类库的启动

如果定义类库时指定了“启动类”属性(参见“属性表”),那么此类库可以被“启动”。

类库中定义的任何类都可以被指定为本类库的“启动类”(可通过“类库信息定义文件”或编译命令行将“启动类”的类名称告知编译器)。

“启动”一个类库,仅仅意味着调用该类库内所指定启动类中的“启动方法”,至于具体的执行动作,由该方法内的代码决定。

对“启动方法”的要求:

1、名称必须是“启动”或“main”,必须是“公开”和“静态”的,必须有特定的参数和返回值。

2、启动方法可以是以下几种形式之一:

公开 静态 启动()
公开 静态 整数 启动()
公开 静态 启动(文本[] 命令行)
公开 静态 整数 启动(文本[] 命令行)

其中最后一种是最完整的形式,参数和返回值均被省略之后,即形成第一种最简单的形式。

如果“启动”方法具有“命令行”参数,它将从启动者(启动方法的调用方,可能是操作系统或其它程序)处接收一个文本数组,其内容为启动者所提供的命令行系列文本(如:当前被执行文件,参数1,参数2……)。

如果“启动”方法具有返回值,该返回值将被反馈给启动者。

任何类都可以定义一个启动方法。如果某类定义有合法的启动方法,那么此类也可以被启动。

标准库

经标准库委员会核实满足特定条件(待定)的类库的集合,称为EF标准库。

EF标准库将随EF新版本发布时一起发行。

用户可将自己开发的类库或类提交给标准库委员会,申请加入标准库。

附录

中英文双语关键字

为了兼顾已有程序员的思维习惯,EF中所有关键字和系统属性,都同时具有中英文两种名称。 请参考:关键字,属性表。

中英文关键字可以同时在程序中使用,不会互相影响。

下面是用英文关键字书写的例程:

public class MainClass
{
	public static main()
	{
		for (int i = 0; i < 10; i++) //print number 0 to 9
		{
			控制台.输出行(i);
		}
	}
}

用英文关键字书写的EF代码,与C++、Java、C#代码极为相似,例如以下代码段,对以上四种编程语言而言,都是合法的:

int dox(int x)
{
	int i = 0;

	if(x == 0)
		i++;
	else if(x == 1)
		i += 1;
	else if(x == 2 || x == 3)
		i += 2;
	else
		i += 0;

	int sum = 0;
	for(i = 1; i < 10; i++)
		sum += i;

	i = 10;
	while(i > 0)
		i--;

	i = -10;
	do
	{
		if(i == -5) continue;
		i++;
	}while(i <= 0);

	switch(i)
	{
	case 0: 
		x = 1; break;
	case 1:
		x = 2; break;
	default:
		x = 0; break;
	}

	return x;
}

对象模型和异常管理

EF的基石来自于首创的“EF对象模型”(EFOM,EF Object Model,将在其它文档中详细说明), 此模型是“语言无关”的,也就是说:使用任何现有编程语言,在满足EFOM的基础上,都可以用来开发EF类库。 这样带来的好处有:

有得必有失,正因为EFOM是语言无关的,且现有各种编程语言本身对异常机制支持的程度和方式的不同,导致很难抛出并管理能够顺利通过程序调用栈中堆积的可能由不同编程语言构造的代码层的异常消息包。即使能够支持异常,也必将大大增加EFOM的复杂性,导致开发效率降低。经过权衡,在找到完美解决方案之前,我们决定暂不支持异常管理,而代之增强类方法的输入输出能力,增强传统调用返回机制的数据携带量,来弱化对异常管理的需求。

垃圾收集器

EF内置垃圾自动回收机制。在程序运行时,垃圾收集器在后台线程工作,自动检查并释放内存垃圾。

“不再被引用的”文本、字节集、对象、接口、数组,将被垃圾收集器在适当的时机释放(释放对象之前会调用其“清理()”方法)。

程序或类库中创建的所有文本、字节集、对象、接口、数组,都不需要用户手工删除(EF也不提供手工删除机制)。这个工作由垃圾收集器自动完成。

垃圾收集器垃圾收集动作的启动时机对用户是透明的,并且可能在版本升级时发生变化,用户不应对此做任何假设。

数据库链接、网络连接、文件句柄、GDI(图形设备接口)句柄、端口、打印机等类似资源不属于垃圾收集器管理范围,用户需手动释放此类资源。

编译器

EF编译器负责将EF源代码编译为可执行文件或类库。

EF编译器拥有针对不同操作系统的多个版本,如Windows下为"efc.exe",Linux下为"efc"。

以下是EF编译器命令行说明文本:

易语言.飞扬 编译器1.00版 / 大连大有吴涛易语言软件开发有限公司版权所有
命令行格式: efc 待编译的文件名 ... [-选项] [-选项=值]
欲编译文件必须是UTF-8文本编码格式。
可能的编译选项(选项名不区分大小写):
1. ?:显示本帮助
2. dbg:加入调试信息
3. out_mode = xxx:说明编译输出类型,可以为以下值之一:
     efl:编译到类库;
     runable:编译到可执行文件;
     gui_runable:编译到支持图形界面的GUI可执行文件。
     如果未指定,默认值为runable。
4. out = xxx:提供编译输出文件名。如果是编译到类库,由于其文件名称被固定,本选项将被忽略。
5. efl_name = xxx:提供类库名称。如果未指定,默认为"程序"。
6. main_class = xxx:提供启动类名称。如果未指定,默认为"启动类"。
7. d xxx:加入名称为xxx,值为"真"的逻辑型全局预编译常量。
8. makefile = xxx:导入指定文本文件中以换行符分隔的所有待编译文件名和编译选项

对于编译选项 main_class,只在编译可执行文件时才有默认值“启动类”,编译类库时无默认值。

将一个EF源代码文件(假设为“hello.ef”)编译为可执行文件(EXE)的典型命令行是:

efc hello.ef -out="hello.exe"

将一个EF源代码文件(假设为“hello.ef”)编译为EF类库(EFL)的典型命令行是:

efc hello.ef -out_mode=efl -efl_name="库名称"

同时编译多个源代码文件时,源代码文件名之间以空格分隔:

efc hello1.ef hello2.ef hello3.ef ...

makefile选项

可以将编译命令行各部分用换行符分隔后写到文本文件中提供给编译器,该文本文件称为 makefile 文件。

makefile 文件更适合源代码文本比较多或编译选项比较复杂的情况。

这是一个典型的 makefile 文件(hello.makefile):

-dbg
-out_mode=runable
-out=c:\hello.exe

//以下列出所有源代码文件
c:\hello1.ef
c:\hello2.ef
c:\hello3.ef

以下是使用 makefile 文件的编译器命令行:

efc -makefile=hello.makefile

注意:


大连大有吴涛易语言软件开发有限公司 版权所有 2006,2007