欢迎访问 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定义结束

    注意到函数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(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日