欢迎访问 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定义结束
注意到函数Module和OutFun前面有一个感叹号,表示该函数编译后将立即执行。本例中,如果Module和OutFun没有执行,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 -->