Forcal中的类模块及数据结构

欢迎访问 Forcal数学软件

Forcal中的类模块及数据结构

如果看不到文中的图形,请访问:http://xoomer.virgilio.it/forcal/sysm/forcal8/fchtm/forcalclassmodule.htm

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

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

本文的例子需要用OpenFC进行演示,可以从作者网站、天空软件站、华军软件园等多家站点下载到该程序。

1 Forcal模块命名空间

使用命名空间可以有效地避免函数重名问题。一个Forcal模块命名空间是一棵树或者是一张连通图。Forcal所有的模块命名空间是一张图。图包含了树,因此下面的例子是用图来说明的。

图1是Forcal中可能存在的某种模块布局:

模块命名空间

图1 模块命名空间举例

图中每个文本框表示一个模块,文本框内冒号前为模块名,冒号与分号之间为该模块的基模块,分号之后为该模块通过命名空间输出的函数,模块私有函数没有包含在该说明中。例如:

B:C,D,E;
set,get,me

表示模块B继承自模块C,D,E,模块B通过命名空间输出的函数为set,get和me。该模块命名空间定义及输出函数说明用Forcal代码表示为:

#MODULE# //定义模块B

!Module("B":"C","D","E"); //定义模块命名空间
... ...;
//模块中的函数定义
!OutFun("set","get","me"); //输出模块命名空间中的函数

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

注意到函数ModuleOutFun前面有一个感叹号,表示该函数编译后将立即执行。本例中,如果ModuleOutFun没有执行,Forcal编译器将不知道模块命名空间B及其输出函数的存在,后续的编译过程将无法正常进行。

图1中所有模块的一种定义如下:

//单个模块

#MODULE# //定义模块A
!Module("
A"); //定义模块命名空间
me()=1;
//模块中的函数定义
!OutFun("me"); //输出模块命名空间中的函数
#END# //模块A定义结束

//树形模块

#MODULE# //定义模块B
!Module("
B":"C","D","E"); //定义模块命名空间
set(x::xx)=xx=x;
//模块中的函数定义
get(::xx)=xx;
//模块中的函数定义
me()=2;
//模块中的函数定义
!OutFun("set","get","me"); //输出模块命名空间中的函数
#END# //模块B定义结束

#MODULE# //定义模块C
!Module(
"C");
set(x::xx)=xx=x;
get(::xx)=xx;
cc()=3;
!OutFun("set","get","cc");

#END#

#MODULE# //定义模块D
!Module(
"D":"F","G");
set(x::xx)=xx=x;
get(::xx)=xx;
me()=4;
!OutFun("set","get","me");

#END#

#MODULE# //定义模块E
!Module(
"E":"H");
set(x::xx)=xx=x;
get(::xx)=xx;
me()=5;
!OutFun("set","get","me");

#END#

#MODULE# //定义模块F
!Module(
"F");
set(x::xx)=xx=x;
get(::xx)=xx;
me()=6;
!OutFun("set","get","me");

#END#

#MODULE# //定义模块G
!Module(
"G");
set(x::xx)=xx=x;
get(::xx)=xx;
gg()=7;
!OutFun("set","get","gg");

#END#

#MODULE# //定义模块H
!Module(
"H");
set(x::xx)=xx=x;
get(::xx)=xx;
hh()=8;
!OutFun("set","get","hh");

#END#

//连通图形模块

#MODULE# //定义模块I
!Module("
I":"J","K","L"); //定义模块命名空间
set(x::xx)=xx=x;
//模块中的函数定义
get(::xx)=xx;
//模块中的函数定义
me()=9;
//模块中的函数定义
!OutFun("set","get","me"); //输出模块命名空间中的函数
#END# //模块B定义结束

#MODULE# //定义模块J
!Module(
"J":"K","M");
set(x::xx)=xx=x;
get(::xx)=xx;
jj()=10;
!OutFun("set","get","jj");

#END#

#MODULE# //定义模块K
!Module(
"K":"M","N");
set(x::xx)=xx=x;
get(::xx)=xx;
me()=11;
!OutFun("set","get","me");

#END#

#MODULE# //定义模块L
!Module(
"L":"P");
set(x::xx)=xx=x;
get(::xx)=xx;
me()=12;
!OutFun("set","get","me");

#END#

#MODULE# //定义模块M
!Module(
"M");
set(x::xx)=xx=x;
get(::xx)=xx;
me()=13;
!OutFun("set","get","me");

#END#

#MODULE# //定义模块N
!Module(
"N");
set(x::xx)=xx=x;
get(::xx)=xx;
nn()=14;
!OutFun("set","get","nn");

#END#

#MODULE# //定义模块P
!Module(
"P":"I");
set(x::xx)=xx=x;
get(::xx)=xx;
pp()=15;
!OutFun("set","get","pp");

#END#

下面是一些测试代码,你可以将这些测试代码添加到上面的模块代码的最后进行测试,或者是先将上面的代码保存为模块文件并进行编译,然后输入下面的测试代码进行测试。

例1 直接调用不同模块命名空间中的同名函数

A::me();
B::me();
F::me();
I::me();
M::me();

例2 间接调用模块命名空间B中的函数

B::cc();
B::gg();
B::hh();

例3 间接调用模块命名空间I或P中的函数

I::jj();
I::nn();
I::pp();
L::jj();
L::nn();
L::pp();

例4 模块命名空间的多级调用

B::me();
B::D::me();
B::D::F::me();
L::me();
L::P::me();
L::P::I::me();
L::P::I::J::me();
L::P::I::J::K::me();
L::P::I::J::K::M::me();

例5 通过多个模块命名空间间接调用同一个函数

I::nn();
J::nn();
K::nn();
L::nn();
P::nn();

例6 模块命名空间的循环调用

I::nn();
I::L::nn();
I::L::P::nn();
I::L::P::I::nn();
I::L::P::I::L::nn();
I::L::P::I::L::P::nn();

以上例子中没有测试模块命名空间中的函数set和get,大家自己可以进行测试。你也可以在任意的模块中增加私有函数,看其他模块能否调用。

2 FcData中的类

FcData中的类通过函数new(DefineClass,"a","b",... ...)定义。关键字DefineClass说明要定义一个类。字符串"a","b",... ...定义了类的成员,每一个字符串标识一个类成员,区分大小写。注意类成员名称不要重复,否则后定义的类成员将无法访问。在类定义时无法确定类成员的类型,类成员的类型在申请类对象并进行赋值时确定,类成员的类型可以是一个类对象。

定义完类后,可以通过函数new(class,DC:"a",x:"b",y,NotDelete:... ...)创建类的对象(或变量)。关键字class说明要创建一个类对象。DC是预定义的类指针,根据该类定义创建类的成员。在创建类时可以给类成员赋值,赋值的顺序是任意的。本例中,给成员"a"赋初值为x;给成员"b"赋初值为y,关键字NotDelete指出,删除该类对象时,不删除y。一般情况下,x和y都是FcData数据指针,当然也可以是任意的整数。

每一个类成员都有删除属性,如果属性为true,在销毁类对象时,将先销毁该类成员,如果属性为false,将不销毁该类成员。在创建类对象时,类成员的删除属性缺省情况下为true,但可以用关键字NotDelete指明该类成员的删除属性为false。对于已经存在的类对象,可以用函数GetCM()SetCM()获得或设置类成员及属性。

如果类A是类B的对象成员,则称类A为类B的基类(父类),类B为类A的派生类(子类)。类可以多重继承,也可以形成循环链表互为基类或派生类。可以用函数DCM()BCM()获得基类的对象成员,这两个函数搜索基类对象成员的方法不同,DCM()是深度优先的,而BCM()广度优先。

类通常是一棵树或者是一张连通图。FcData中所有的类是一张图。图包含了树,因此下面的例子是用图来说明的。

图2是FcData中可能存在的某种类定义的布局:

类定义布局

图2 类定义举例

你一定注意到了图1和图2是完全相同的。但图1是模块命名空间的布局,图2是类定义的布局,有本质上的区别。模块命名空间是函数的集合,因而按图1实现的函数集,在内存中只能存在一个;而按图2的类定义实现的类对象,在内存中可存在任意多个。

图中每个文本框表示一个类定义,文本框中的每一个字符串都表示一个类的成员,类的成员可以是任意一个FcData指针,当然也可以是一个类指针,如果是一个类指针,表示了类的继承关系。为了方便,规定:冒号前的类成员表示该类的名称(但类的名称不是该类的唯一标识,类的唯一标识是类指针);冒号与分号之间的类成员为类指针,表示该类的基类;分号之后为该类的普通成员(非类成员)。例如:

B:C,D,E;
set,get,me

表示类B继承自类C,D,E,有三个普通的类成员set,get和me。该类定义及类对象实现用Forcal代码表示为:

DC_B=new(DefineClass,"B":"C","D","E":"set","get","me"), //定义类B,返回一个类定义指针DC_B,以“DC_”开头的标识符表示一个类定义指针,下同
pB=new(class,DC_B:"C",DC_C,"D",DC_D,"E",DC_E:"me",new(int)); //申请类B的对象,返回一个类对象指针pB,为了简单,"B","set","get"等类成员没有赋值

下面我们实现图2的类定义及类对象,我们用函数A实现(a)单个类,用函数B实现(b)树形类,用函数I实现(c)连通图形类。每一个函数被调用时,都返回一个相应的类对象指针,通过该指针可访问该类或其基类的任意一个对象成员。所有类的定义隐藏在三个函数中,没有输出任何一个类定义指针。在这些例子中没有自己管理申请的内存,是由系统自动回收的。

i::A(:static,DC_A)={ //i:表示是个整数表达式,第二个冒号表示该函数仅在被调用时执行
if[!DC_A,DC_A=new(DefineClass,"A","me")],
//第一次调用该函数时,申请类定义
new(class,DC_A:"me",new(int,1))
//返回类对象指针
};

i::B(:pB,pC,pD,pE,pF,pG,pH,static,DC_B,DC_C,DC_D,DC_E,DC_F,DC_G,DC_H)={
if{ !DC_B,
DC_B=new(DefineClass,
"B":"C","D","E":"set","get","me"
),
DC_C=new(DefineClass,
"C":"set","get","cc"
),
DC_D=new(DefineClass,
"D":"F","G":"set","get","me"
),
DC_E=new(DefineClass,
"E":"H":"set","get","me"
),
DC_F=new(DefineClass,
"F":"set","get","me"
),
DC_G=new(DefineClass,
"G":"set","get","gg"
),
DC_H=new(DefineClass,
"B":"set","get","hh"
)
},
pH=new(class,DC_H:"hh",new(int,8)),

pG=new(class,DC_G:"gg",new(int,7)),
pF=new(class,DC_F:"me",new(int,6)),
pE=new(class,DC_E:"H",pH:"me",new(int,5)),
pD=new(class,DC_D:"F",pF,"G",pG:"me",new(int,4)),
pC=new(class,DC_C:"cc",new(int,3)),
pB=new(class,DC_B:"C",pC,"D",pD,"E",pE:"me",new(int,2))
};

i::I(:pI,pJ,pK,pL,pM,pN,pP,static,DC_I,DC_J,DC_K,DC_L,DC_M,DC_N,DC_P)={
if{ !static,static=1,
DC_I=new(DefineClass,
"I":"J","K","L":"set","get","me"
),
DC_J=new(DefineClass,
"J":"K","M":"set","get","jj"
),
DC_K=new(DefineClass,
"K":"M","N":"set","get","me"
),
DC_L=new(DefineClass,
"L":"P":"set","get","me"
),
DC_M=new(DefineClass,
"M":"set","get","me"
),
DC_N=new(DefineClass,
"N":"set","get","nn"
),
DC_P=new(DefineClass,
"P":"I":"set","get","pp"
)
},
pP=new(class,DC_P:"pp",new(int,15)),

pN=new(class,DC_N:"nn",new(int,14)),
pM=new(class,DC_M:"me",new(int,13)),
pL=new(class,DC_L:"P",pP:"me",new(int,12)),
pK=new(class,DC_K:"M",pM,"N",pN:"me",new(int,11)),
pJ=new(class,DC_J:"K",pK,"M",pM:"jj",new(int,10)),
pI=new(class,DC_I:"J",pJ,"K",pK,"L",pL:"me",new(int,9)),
pP."I".SetCM[pI,true],
//让类对象pP的类成员"I"指向类对象pI
pI

};

下面是一些测试代码,你可以将这些测试代码添加到上面的模块代码的最后进行测试。注意这些例子中使用的函数get不是文本框中定义的类的成员"get",该函数由FcData定义,可得到一个FcData数据的值。

例1 直接输出不同类中的同名成员

i: A()."me".get(); //i:表示是个整数表达式,下同
i: B()."me".get();
i: I()."me".get();

例2 间接输出类B中的成员:尽管每一次函数调用B()返回的对象指针都不同,但由前面的定义可知,所有类B的相应的基类对象中的"cc""gg""hh"都相同。

i: B().BCM("cc").get(); //用函数BCM得到基类成员指针(广度优先),下同
i: B().BCM("gg").get();
i: B().BCM("hh").get();

例3 直接输出类B中的成员:尽管每一次函数调用B()返回的对象指针都不同,但由前面的定义可知,但所有类B的相应的基类对象中的"me"都相同。

i: B()."me".get();
i: B()."D"."me".get();
//输出基类成员D的"me"成员

i: B()."D"."F"."me".get();
//输出基类成员F的"me"成员

例4 输出类I中的成员:注意每一次函数调用I()返回的对象指针都不同,但不影响演示这个例子。

i: I().BCM("jj").get();
i: I().BCM("nn").get();
i: I().BCM("pp").get();

i: I().BCM("P").BCM("jj").get(); //先用函数BCM得到基类成员P的指针,再用函数BCM得到类对象P的基类成员jj的指针,下面与此类似
i: I().BCM("P").BCM("nn").get();
i: I().BCM("P").BCM("pp").get();

i: I().BCM("J").BCM("me").get();
i: I().BCM("K").BCM("me").get();
i: I().BCM("L").BCM("me").get();
i: I().BCM("M").BCM("me").get();
i: I().BCM("P").BCM("me").get();

例5 类成员的循环调用

i: I()."me".get();
i: I()."L"."me".get();
i: I()."L"."P"."pp".get();
i: I()."L"."P"."I"."me".get();
i: I()."L"."P"."I"."L"."me".get();
i: I()."L"."P"."I"."L"."P"."pp".get();

例6 类P的成员"get"的使用举例

i: (::pI)= pI=I(), pI.BCM("P")."get".SetCM[new(int,111),true];
i: (::pI)= pI.BCM("P")."get".get();
i: (::pI)= pI."L"."P"."get".get();

i: (::pP)= pP=I().BCM("P"), pP."get".SetCM[new(int,222),true];
i: (::pP)= pP."get".get();

3 Forcal类模块

FcData中的类与Forcal模块的结合即类模块,相当于C++中的类。下面举一个例子说明一下。

在这个例子中,类Num3有三个数据成员"a"、"b"和"c",用于表示三角形的三条边;模块Area有三个输出函数NewNum3、SetNum3和Area3,NewNum3用于获得一个Num3类对象,SetNum3用于设置三角形的三条边,Area3用于计算三角形的面积。模块中的函数和数据都是整数。代码如下:

#MODULE# //定义模块

!Module("Area");

i::NewNum3(:k,static,CNum3,free,nFree)= //申请类Num3的对象,该函数不会自动执行
{
if{!static,static=1,nFree=nDeleteAllFCD(),CNum3=new(DefineClass:"a","b","c")},
//初始化,记录函数DeleteAllFCD()执行的次数,申请类Num3的定义
k=nDeleteAllFCD(),if{nFree!=k,nFree=k,CNum3=new(DefineClass:"a","b","c")},
//检查类定义CNum3是否被函数DeleteAllFCD()销毁,如果销毁,重新申请
if[free,delete(CNum3),return(0)],
//销毁表达式时,自动销毁申请的对象
new[class,CNum3:"a
",new(int),"b",new(int),"c",new(int)] //返回一个Num3对象
};

i:
SetNum3(p,a,b,c)= p."a".set(a), p."b".set(b), p."c".set(c), p; //设置三角形的三条边

i: F(a,b,c:s)= s=(a+b+c)/2,sqrt[s*(s-a)*(s-b)*(s-c)]; //定义三角形面积公式,私有函数

i: Area3(p)=
//调用私有函数F计算三角形面积
{
F[p."a".get(), p."b".get(), p."c".get()]
};

!OutFun("NewNum3","SetNum3","Area3");

#END#

i: (:p)= p=Area::NewNum3(), //申请Num3对象
p.Area::
SetNum3(3,4,5),//Num3对象赋值
p.Area::Area3();
//计算Num3的面积

i: Area::NewNum3().Area::SetNum3(30,40,50).Area::Area3();

在以上的例子中,DeleteAllFCD()是FcData的一个函数,可销毁所有的FcData对象。为了使类模块Area在任何情况下都能正常工作,需要要函数nDeleteAllFCD()检查其他线程是否调用了DeleteAllFCD(),如果调用了DeleteAllFCD(),则类定义CNum3已经销毁,需要重新申请。nDeleteAllFCD()内置一个计数器,每当执行函数DeleteAllFCD(),计数器增1.

类模块可以实现C++中类的功能,大家自己可以给出这方面的例子。


版权所有© Forcal数学软件 2002-2009,保留所有权利
E-mail: forcal@sina.com QQ:630715621

最近更新: <!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%Y年%m月%d日" startspan -->2009年05月23日<!--webbot bot="Timestamp" i-checksum="1376" endspan -->

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值