欢迎访问 Forcal程序设计

Forcal 中的类模块及数据结构

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

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

以下函数OutClass可以输出类的结构，本文中的例子将直接使用该函数，不再给出函数的实现。要演示这些例子，将该函数放到例子的前面和例子一起编译即可。

> i:OutClass(pClass,n:len,i,j,str,BType,PArray,nPArray,pc)= //pClass是类指针，n是缓冲区大小，n太小时，有些基类将无法输出
> {
> printff{"\r\n\r\n***** 类简易分析程序 *****"},
> PArray=new[pointer_s,n],nPArray=CDFSBase[pClass,PArray],
> str="\[80]",
> i=0,
> (i<nPArray).while{
> pc=PArray.get(i),len=FCDLen(pc),
> printff{"\r\n\r\n类：{1,i}\r\n",pc},
> j=0,
> (j<len).while{
> CGetNameI[pc,j,str,80],
> printff{"\r\n类成员：{1,ns,-20}指针：{2,i,-12}删除属性：{3,i,-5}数据类别：",str,pc.CGetPI[j],pc.CGetAI[j]},
> FCDType[pc.CGetPI[j],&BType],
> which{
> BType==int8, printff{"int8"},
> BType==Uint8, printff{"Uint8，char"},
> BType==int16, printff{"int16"},
> BType==Uint16, printff{"Uint16"},
> BType==wchar, printff{"wchar"},
> BType==int32, printff{"int32"},
> BType==Uint32, printff{"Uint32"},
> BType==int64, printff{"int64，int"},
> BType==Uint64, printff{"Uint64，pointer"},
> BType==real32, printff{"real32"},
> BType==real64, printff{"real64，real"},
> BType==complex, printff{"complex"},
> BType==int8_s, printff{"int8_s"},
> BType==Uint8_s, printff{"Uint8_s，char_s"},
> BType==int16_s, printff{"int16_s"},
> BType==Uint16_s, printff{"Uint16_s"},
> BType==wchar_s, printff{"wchar_s，string"},
> BType==int32_s, printff{"int32_s"},
> BType==Uint32_s, printff{"Uint32_s"},
> BType==int64_s, printff{"int64_s，int_s"},
> BType==Uint64_s, printff{"Uint64_s，pointer_s"},
> BType==real32_s, printff{"real32_s"},
> BType==real64_s, printff{"real64_s，real_s"},
> BType==complex_s,printff{"complex_s"},
> BType==class, printff{"class，基类"},
> BType==0, printff{"非FcData数据"},
> printff{"外部数据类型"}
> },
> j++
> },
> i++
> },
> printff{"\r\n\r\n"},
> if{nPArray==n,printff{"警告：指定的缓冲区太小，可能有些基类无法搜索到。\r\n\r\n"}},
> printff{"***** 类分析结束 *****\r\n\r\n"},
> delete[PArray]
> };

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(class,EndType,"a","b",... ...)定义。关键字class说明要定义一个类。字符串"a","b",... ...定义了类的成员，每一个字符串标识一个类成员，区分大小写。注意类成员名称不要重复，否则后定义的类成员将无法访问。在类定义时无法确定类成员的类型，类成员的类型在申请类对象并进行赋值时确定，类成员的类型可以是一个类对象。

定义完类后，可以通过函数CSetItem(pClass:"a",x:"b",y,NotDelete:... ...)给类的成员赋值。pClass是类指针。本例中，给成员"a"赋初值为x；给成员"b"赋初值为y，关键字NotDelete指出，删除该类对象时，不删除y。一般情况下，x和y都是FcData数据指针，当然也可以是任意的整数。

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

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

类通常是一棵树或者是一张连通图。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代码表示为：

> pB=new(class,EndType,"B":"C","D","E":"set","get","me"), //定义类，返回一个类指针pB
> pB.CSetItem("C",pC,"D",pD,"E",pE:"me",new(int)); //给类pB赋初值，为了简单，"B","set","get"等类成员没有赋值

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

> i::A()={ //i:表示是个整数表达式，第二个冒号表示该函数仅在被调用时执行
> new(class,EndType,"A","me").CSetItem["me",new(int,1)] //定义类 ，并给类成员赋值
> };

> i::B(:pB,pC,pD,pE,pF,pG,pH)={
> pB=new(class,EndType,"B":"C","D","E":"set","get","me"),
> pC=new(class,EndType,"C":"set","get","cc"),
> pD=new(class,EndType,"D":"F","G":"set","get","me"),
> pE=new(class,EndType,"E":"H":"set","get","me"),
> pF=new(class,EndType,"F":"set","get","me"),
> pG=new(class,EndType,"G":"set","get","gg"),
> pH=new(class,EndType,"H":"set","get","hh"),
> pH.CSetItem("hh",new(int,8)),
> pG.CSetItem("gg",new(int,7)),
> pF.CSetItem("me",new(int,6)),
> pE.CSetItem("H",pH:"me",new(int,5)),
> pD.CSetItem("F",pF,"G",pG:"me",new(int,4)),
> pC.CSetItem("cc",new(int,3)),
> pB.CSetItem("C",pC,"D",pD,"E",pE:"me",new(int,2))
> };

> i::I(:pI,pJ,pK,pL,pM,pN,pP)={
> pI=new(class,EndType,"I":"J","K","L":"set","get","me"),
> pJ=new(class,EndType,"J":"K","M":"set","get","jj"),
> pK=new(class,EndType,"K":"M","N":"set","get","me"),
> pL=new(class,EndType,"L":"P":"set","get","me"),
> pM=new(class,EndType,"M":"set","get","me"),
> pN=new(class,EndType,"N":"set","get","nn"),
> pP=new(class,EndType,"P":"I":"set","get","pp"),
> pP.CSetItem("pp",new(int,15)),
> pN.CSetItem("nn",new(int,14)),
> pM.CSetItem("me",new(int,13)),
> pL.CSetItem("P",pP:"me",new(int,12)),
> pK.CSetItem("M",pM,"N",pN:"me",new(int,11)),
> pJ.CSetItem("K",pK,"M",pM:"jj",new(int,10)),
> pI.CSetItem("J",pJ,"K",pK,"L",pL:"me",new(int,9)),
> pP."I".CSetP[pI], //让类对象pP的类成员"I"指向类对象pI
> pI
> };

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

例1 输出类的结构：用到了本文开头定义的函数 OutClass。

> i: A().OutClass(8);
> i: B().OutClass(8);
> i: I().OutClass(8);

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

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

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

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

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

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

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

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

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

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

例6 类成员的循环调用

> 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();

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

> i: (::pI)= pI=I(), pI.CSB("P")."get".CSetP[new(int,111)];
> i: (::pI)= pI.CSB("P")."get".get();
> i: (::pI)= pI."L"."P"."get".get();

> i: (::pP)= pP=I().CSB("P"), pP."get".CSetP[new(int,222)];
> 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()= //申请类Num3，该函数不会自动执行
> {
> new(class,EndType:"a","b","c").CSetItem["a",new(int),"b",new(int),"c",new(int)]
> };
> i: SetNum3(p,a,b,c)= p."a".set(a), p."b".set(b), p."c".set(c), p; //设置三角形的三条边

> 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().itor(), p."b".get().itor(), p."c".get().itor()].rtoi()
> };
> !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();

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

版权所有© Forcal程序设计 2002-2010，保留所有权利
E-mail: forcal@sina.com QQ：630715621
最近更新: 2010年01月23日