欢迎访问 Lu程序设计

用C/C++设计Lu结构、字典、类等高级数据类型

1 说明

    本文没有直接给出演示例子,而是对Lu系统扩展库LuSystem实现的结构、字典、类等高级数据类型做说明,但你仍然需要下载Lu64脚本系统,并下载软件包lu2code.zip,LuSystem的源代码就在这个软件包中,源代码中用到的Lu核心库函数仍然参考Lu编程指南。 由于LuSystem是一个Lu扩展动态库,你可能还要参考Lu扩展动态库说明

    本文假定你已经阅读了本教程系列的前面的内容,对 涉及到的Lu基本数据类型、Lu键树、以#开头的标识符(字符串)确定的唯一的整数等内容不再重复说明。

2 关于Lu系统扩展库LuSystem

    LuSystem64.dll是一个Lu系统扩展动态库,包含一些增强Lu系统功能的函数、对系统内置类型的扩展以及一些新增数据类型等等。LuSystem不仅是对Lu系统的扩充,更在于演示Lu是极易扩充其功能的脚本,很少有脚本允许用户能方便地添加像字典、结构、类等高级的数据结构,但Lu允许,而且实现这些很容易,因而,LuSystem也是编程用户极佳的练手工具。

    为了便于理解本文,可以先看一下头文件LuSystem64.h(包含了字典、结构、类等数据结构的定义)和该库的入口文件LuSystem64.cpp(定义了函数LuDll64,Lu扩展库唯一的输出函数)。

3 结构(struct)实现原理及难点分析

    结构(struct)是一个线性表,可以存放若干任意的Lu数据。结构成员必须以#开头。结构成员的存储顺序(递增排列)与其定义顺序不一定相同,当然这并不影响使用。 例如以下脚本代码:

!!!using("sys");    //使用命名空间sys
struct[#num, #姓名 : "luuu", #年龄].o[];    //函数o用于输出一个对象

    结果(成员#num#年龄没有赋值,故值为nil):

struct{#num : nil , #姓名 : luuu , #年龄 : nil}

    从头文件LuSystem64.h中可找到的结构(struct)定义如下:

struct SStruct{		//结构
	luINT max;	//成员数目
	luVOID *Name;	//成员名字(递增排列以加快访问速度),由一个以#开头的标识符(字符串)确定的唯一的整数
	LuData *Member;	//成员
};

    注意结构定义中,成员名字不是使用字符串,而是使用由该字符串确定的一个唯一整数来标识,显然这样访问速度更快,并且成员是递增排列的,更加快了访问速度;另外,结构成员是Lu基本数据类型,这样可以存储任意的数据 ,包括另一个结构对象,甚至是自身。

    从结构的实现文件Struct.cpp中可以找到如下两个函数:

void _stdcall DelStruct(void *vStruct)	//删除一个结构
{
	SStruct *pSStruct;

	pSStruct=(SStruct *)vStruct;
	if(pSStruct->Name) delete[] pSStruct->Name;
	if(pSStruct->Member) delete[] pSStruct->Member;
	delete pSStruct;
}

void _stdcall SignKeyStruct(void *me)	//标记子键值(对象)
{
	SStruct *pSStruct;
	luINT i;

	pSStruct=(SStruct *)me;
	for(i=0;i<pSStruct->max;i++)
	{
		pSignGoodObj(pSStruct->Member+i);	//用函数SignGoodObj标记子对象
	}
}

    函数DelStruct用于删除一个结构对象,但DelStruct是由锁定键的类型函数LockKey(LuSystem中使用的是函数指针pLockKey,下同,不再一一说明)注册到Lu系统中由Lu来调用的。函数SignKeyStruct是由插入键值函数InsertKey注册到Lu系统中由Lu来调用的,该函数的作用是:当Lu垃圾收集器工作时,如果一个结构是有效的,则该结构成员也应该是有效的,故Lu垃圾收集器会调用函数SignKeyStruct对结构成员做标记。顺便说一下,Lu垃圾收集器使用标记清除算法 。

    查看文件LuSystem64.cpp,会发现定义了全局变量keyStruct用以标识结构对象,该变量的值作为一个键被LockKey加锁(从其初始值keyStruct=-123115开始加锁,不成功时keyStruct--,直至加锁成功),加锁成功后,键keyStruct就只能存储结构对象。函数LockKey同时指明了销毁结构对象的函数是DelStruct;结构对象的运算符重载函数是OpLockStruct。另外,文件LuSystem64.cpp中还定义了标识对象keyStruct的字符串"struct",该字符串被注册为Lu整数常量,在Lu源代码中可用new(sys::struct,... ...)申请该对象。

    再来看结构的实现文件Struct.cpp,结构对象的运算符重载函数OpLockStruct在该文件中定义,重载了函数set、len、copy、new、oset、oget、o等函数,相信你会在下面找到这些函数的具体实现,参考LuSystem的说明,可以更好地理解这些函数。为了便于操作结构对象,还向Lu系统注册(参考文件LuSystem64.cpp中的SetFunction函数)了函数sys::struct(对应C++函数lu_NewStruct)、sys::struct_oset(对应C++函数lu_osetStruct)和sys::struct_oget(对应C++函数lu_ogetStruct);当然,重载函数new、oset和oget也能实现对应的功能,因为它们对应相同的C++函数。

    在扩展库LuSystem中测试了结构的效率。

4 字典(dict)实现原理及难点分析

    字典(dict)是一个可直接存取的双向链表 (使用双向链表便于删除一个字典元素),可以存放若干任意的Lu数据。字典元素由“键-值”对组成,键只能是字符串,但值可以是任何Lu数据类型,包括另一个字典,甚至自身。 例如以下脚本代码生成了一个字典a并初始化,然后又在字典a中添加了一个元素:

!!!using("sys");
main(:a)= a=dict["aa":1.2, "abc":"luuu"], a."姓名"="王刚", o[a];

    结果:

dict{aa : 1.2 , abc : luuu , 姓名 : 王刚}

    从头文件LuSystem64.h中可找到的字典(dict)定义如下:

struct SDictItem{		//字典元素结构
	wchar_t *KeyStr;	//键
	LuData KeyValve;	//值
	SDictItem *pAhead;	//前一结点
	SDictItem *pNext;	//后一结点
};
struct SDict{		//字典结构
	luKEY KeyDict;	//标识一个字典,将锁定该键以存放字典元素
	SDictItem MainSDI;	//字典元素头结点
};

    查看文件LuSystem64.cpp,会发现定义了全局变量keyDict用以标识字典对象,该变量的值作为一个键被LockKey加锁(从其初始值keyDict=-123114开始加锁,不成功时keyDict--,直至加锁成功),加锁成功后,键keyDict就只能存储字典对象。函数LockKey同时指明了销毁字典对象的函数是DelDict;字典对象的运算符重载函数是OpLockDict。另外,文件LuSystem64.cpp中还定义了标识对象keyDict的字符串"dict",该字符串被注册为Lu整数常量,在Lu源代码中可用new(sys::dict,... ...)申请该对象。

    当创建一个字典(设字典指针为pSDict)时,先使用随机算法加锁一个私有键pSDict->KeyDict(参考Dict.cpp中的LockKeyDictItem函数),用于存放字典元素,然后将pSDict注册为keyDict类型的私有键。当往字典中存放字典元素(设字典元素指针为pSDictItem)时,将pSDictItem注册为pSDict->KeyDict类型的私有键,并将pSDictItem链接到pSDict->MainSDI。故定位字典元素a."me"(a为字典,"me"为字典元素)的操作为:根据a的指针和键keyDict找到a的结构(设为pSDict),然后根据字符串"me"和pSDict->MainSDI找到该字典元素。

    从字典的实现文件Dict.cpp中可以找到如下三个函数:

void _stdcall DelDict(void *vDict)	//删除一个字典
{
	SDict *pSDict;

	pSDict=(SDict *)vDict;
	pLockKey(pSDict->KeyDict,NULL,OpLockDictItem);	//删除字典所有元素
	delete pSDict;
}

void _stdcall DelDictItem(void *vSDI)	//删除一个字典元素
{
	SDictItem *pSDI;

	pSDI=(SDictItem *)vSDI;
	pSDI->pAhead->pNext=pSDI->pNext;
	if(pSDI->pNext) pSDI->pNext->pAhead=pSDI->pAhead;
	if(pSDI->KeyStr) delete[] pSDI->KeyStr;
	delete pSDI;
}
void _stdcall SignKeyDict(void *me)		//标记子键值(对象)
{
	SDictItem *pSDI;

	pSDI=((SDict *)me)->MainSDI.pNext;
	while(pSDI)
	{
		pSignGoodObj(&(pSDI->KeyValve));	//用函数SignGoodObj标记子对象
		pSDI=pSDI->pNext;
	}
}

    函数DelDict用于删除一个字典对象,函数DelDictItem用于删除一个字典元素对象,但DelDict和DelDictItem都是由锁定键的类型函数LockKey注册到Lu系统中由Lu来调用的。函数SignKeyDict是由插入键值函数InsertKey注册到Lu系统中由Lu来调用的,该函数的作用是:当Lu垃圾收集器工作时,如果一个 字典是有效的,则该字典元素也应该是有效的,故Lu垃圾收集器会调用函数SignKeyDict对字典元素做标记。

    文件Struct.cpp中定义了字典的运算符重载函数OpLockDict,重载了函数len、new、oset、oget、o等函数,相信你会在下面找到这些函数的具体实现,参考LuSystem的说明,可以更好地理解这些函数。为了便于操作字典对象,还向Lu系统注册(参考文件LuSystem64.cpp中的SetFunction函数)了函数sys::dict(对应C++函数lu_NewDict)、sys::dict_oset(对应C++函数lu_osetDict)和sys::dict_oget(对应C++函数lu_ogetDict);当然,重载函数new、oset和oget也能实现对应的功能,因为它们对应相同的C++函数。 此外,还注册了函数sys::dict_del(对应C++函数lu_dict_del)、sys::dict_clear(对应C++函数lu_dict_clear)和sys::dict_reverse(对应C++函数dict_reverse)。

    在扩展库LuSystem中测试了字典的效率。

5 类(class)实现原理及难点分析

    (class)是一个具有数据成员和方法成员的自定义数据结构。类可以继承和派生,类的层次结构是一棵树。

    (1)类定义:class{... ...}

class{#cMyClass: #cBase1, #cBase2, ...  //定义类cMyClass,继承自cBase1和cBase2, ...
private:                  //私有数据成员
  #pvMem1, #pvMem2, ...
method:                   //私有方法(私有函数成员)
  #vmFun1 :@Fun1, #vmFun2 :@Fun2, ...
public:                   //公有数据成员
  #plMem1, #plMem2, ...
method:                   //公有方法(公有函数成员)
  #__init__ : @init, #__del__ : @delme, #lmFun1 :@fun1, #lmFun2 :@fun2, ...
};

    类定义中,类名称、基类名称、成员及方法都是以#开头的标识符,不可重复。类名称在最前面,其次是基类名称;privatepublic的次序是任意的,且可多次定义;在private后定义的method为私有方法,在public后定义的method为公有方法,若method前面没有privatepublic,默认是公有方法;方法标识符后必须提供函数句柄。类成员的存储顺序与其定义顺序不一定相同。

    #__init__#__del__只可被定义为公有方法,其中#__init__用于定义构造函数,#__del__用于定义析构函数。若这两个方法被缺省,其函数句柄被自动指定为0。构造函数和析构函数只有一个参数,即对象本身。

    若方法有不止一个参数,则方法被调用时,第一个参数总是传入对象本身,相当于C++的this指针。

    约定:类名称以字母c开头;私有数据成员以pv开头;公有数据成员以pl开头;私有方法成员以vm开头;公有方法成员以lm开头。

    (2)创建类对象:obj(pClass)

    pClass是类定义的句柄。

    创建类对象时,将自动调用每一个基类的构造函数#__init__初始化基类对象。对象被销毁时,将自动调用每一个基类的析构函数#__del__销毁基类对象。

    语句 a.=b 将对象b的内容复制到对象a,要求a和b具有相同的类定义,若类定义中定义了方法#__copy__(有且只有2个参数),将自动调用该方法进行对象复制,否则仅依次复制类成员的值。

    例如以下脚本代码:

!!!using("sys");
setA(p,x)= p.#a=x;		//类方法定义
getA(p)  = p.#a;		//类方法定义
main(:a,b)=
  a=class{#A,		//类定义
    private:
       #a,		//私有数据
    public:
    method :
       #mGetA : @getA,	//类公有方法
       #mSetA : @setA	//类公有方法
    },
  b=obj[a],		//类对象
  b.#mSetA=5,		//通过类公有方法赋值
  o[a, b, "\r\n", b.#mGetA, "\r\n"];	//输出类定义、类对象及类成员的值

    结果:

class{#A : 
private:
  #a, 
method:
  
public:
  
method:
  #__init__ : 0, #__del__ : 0, #mGetA : 10838112, #mSetA : 10835952
}

class obj{#A : 
private:
  #a : 5,
public:
}

5

    从头文件LuSystem64.h中可找到的(class)定义如下:

struct SClass{		//类定义
	luVOID me;	//类名字,是一个以#开头的标识符(字符串)确定的唯一的整数
	luINT maxBaseClass;//基类数目
	luVOID *BaseClass;	//基类,是一些以#开头的标识符(字符串)确定的唯一的整数
	luINT maxDMember;	//数据成员数目
	luINT maxMember;	//成员数目
	luVOID *Member;	//成员(递增排列),是一些以#开头的标识符(字符串)确定的唯一的整数
	char *attrMember;	//成员属性。0:私有数据成员,1:公有数据成员,2:私有成员函数句柄,3:公有成员函数句柄
	luVOID *indexMember;//数据成员序号(递增排列),或者成员函数句柄。仅对私有数据成员和公有数据成员进行编号,由该序号决定SClassObj的数据成员地址。
	bool bFind;	//用于发现循环继承
};
struct SClassObj{		//类对象
	luVOID me;	//类名字,是一个以#开头的标识符(字符串)确定的唯一的整数
	SClassObj *ahead;	//派生类(父类),使用前需要验证其有效性
	luINT maxBaseClass;//基类数目
	LuData *BaseClass;	//基类
	luINT maxDMember;	//数据成员数目
	LuData *DMember;	//数据成员
};

    查看文件LuSystem64.cpp,会发现定义了全局变量keyClass用以标识类定义,该变量的值作为一个键被LockKey加锁(从其初始值keyClass=-123116开始加锁,不成功时keyClass--,直至加锁成功),加锁成功后,键keyClass就只能存储类定义。函数LockKey同时指明了销毁类定义的函数是DelClass;类定义的运算符重载函数是OpLockClass。另外,文件LuSystem64.cpp中还定义了标识对象keyClass的字符串"class",该字符串被注册为Lu整数常量,在Lu源代码中可用new(sys::class,... ...)申请该对象。

    在文件LuSystem64.cpp中还定义了全局变量keyClassObj用以标识类对象,该变量的值作为一个键被LockKey加锁(从其初始值keyClassObj=-123117开始加锁,不成功时keyClassObj--,直至加锁成功),加锁成功后,键keyClassObj就只能存储类对象。函数LockKey同时指明了销毁类对象的函数是DelClassObj;类对象的运算符重载函数是OpLockClassObj。另外,文件LuSystem64.cpp中还定义了标识对象keyClassObj的字符串"obj",该字符串被注册为Lu整数常量,在Lu源代码中可用new(sys::obj,... ...)申请该对象。

    在Lu脚本中,p=class{... ...}创建一个类,根据返回的类句柄p用函数obj(p)创建类对象。class对应Class.cpp中的C++函数lu_NewClass;obj对应Class.cpp中的C++函数lu_NewClassObj。

    在理解结构字典的基础上,不难理解Class.cpp中定义的函数DelClass、SignKeyClass、OpLockClass、DelClassObj、SignKeyClassObj、OpLockClassObj,类定义重载函数new(lu_NewClass)、o(lu_oClass),以及类对象重载函数copy(lu_copyClassObj)、new(lu_NewClassObj)、oset(lu_osetClassObj)、oget(lu_ogetClassObj)、o(lu_oClassObj)等等。向Lu系统注册了函数 除了sys::class、sys::obj外,还有sys::class_handle(对应C++函数lu_class_handle)、sys::obj_oset(对应C++函数lu_osetClassObj)和sys::obj_oget(对应C++函数lu_ogetClassObj)。

    为了便于由类对象获得类的成员,文件Class.cpp中定义了函数GetClassMember,该函数使用缓冲池SClassBuf以快速定位类成员,同时实现了类成员的广度优先定位。最后,不难发现,还有两个函数FindClass和FreeClassObj;FindClass是在函数lu_NewClass中使用的,用于查找类是否循环定义及未定义;FreeClassObj是在函数lu_NewClassObj中使用的,用于释放一个类对象。

    在扩展库LuSystem中测试了类的效率。

6 其他

    你可能注意到了,我的联系方式就在下面,如有不明之处或有什么建议,可随时与我进行联系。


版权所有© Lu程序设计 2002-2021,保留所有权利
E-mail: forcal@sina.com
  QQ:630715621
最近更新: 2021年05月23日