




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
/:数据类型与语法作者:谢兴转载需注明出处Symbian系统已经提供了一套已经定义好的内置的数据类型。为了保证你的代码是编译器无关的,应当使用下面symbian系统提供的数据类型,而不要使用原生数据类型(nativetypes,这里指标准C中的int,char等)。根本类型TIntX和TUintX(其中X=8,16和32)分别用来表示8位,16位和32位的有符号和无符号的整数。一般情况下,使用TInt和TUint就可以了,除非是在考虑代码优化或兼容性的时候,才会用到TInt8,TInt16这样的类型。TInt或TUint类型分别对应有符号和无符号的整数。TInt64.在版本8.0之前,Symbian系统中不支持64位的算术运算,而是用两个32位的值来实现64位的整数,在8.0版本之后,TInt64和TUInt64才被定义为longlong类型,真正使用64位的内置数据类型。TReal32和TReal64(TReal相当于TReal64)这两个数据类型相当于单精度和双精度的浮点数,由于浮点数的运算要比整数慢,所以一般应尽量防止使用浮点数的运算。TTextX(其中X=8或16)分别对应窄或宽的字符(注:所谓窄字符通常ASCII码字符,而宽字符是指unicode字符集的字符)TAny*TAny*意为指向任意内容的指针,在这种意义上讲,TAny相当于void,TAny*相当于TAny*。但是,在某些场合下,void标示‘空’,如:voidhello(void);这时,不要将它改写为:TAnyhello(TAny);TBool标示布尔类型。Symbian系统提供了两个常量:ETrue(=1)和EFalse(=0),分别表示真和假。注意:在Symbian系统中,TBool被定义为int,而ETrue和EFalse被定义为enum,所以,如果一个函数的返回值为TBool,不要用如下的代码来比较函数的返回值:TBoolisLarger(TInta,TIntb){return(a>b)?ETrue:EFalse;}if(isLarger(4,3)==ETrue){...}//错误,编译不过。if(isLarger(4,3)){...}//正确2类和对象2.1Symbian系统中的命名习惯:在Symbian系统中编写代码时,应当遵守种样几个规则:成员变量的命名以小写字母i开头,方法的参数以小写字母a开头,例如:classPernon{public: TIntiAge; voidSetAge(TIntaAge){iAge=aAge};}在symbian系统中存在几种不同类型的类(class),不同类型的类,其特性也各不相同。有的在堆(heap)上创立,有的在栈(stack)上创立,特别的是,类的实例(instance)的去除方式也不尽相同(下面,为了方便我们把类的类别称为型别)。型别(classtype)可以表达这些不同的特点。每个型别都有一套定义好的关于如何创立和去除实例的规则。为了容易区分型别,Symbian系统使用了一个简单的命名规则:类名以大写字母开头(T,C,R或M)。作为类的设计者,你先要考虑这个类的行为,看它到底与哪种型别匹配,一旦确定了它的类型,然后你就可以专注于该类的功能。同样,对一个类的使用者来讲,如果他不熟悉这个类,但类的命名规则可以帮助他弄清你的意图如何用平安的方式初始化、使用和销毁一个类的对象(object)。下面,我主要讨论不同型别的主要特性。T类T类的行为类似于C++中的内置类型,因此,它们以T作前缀(〞T〞代表〞Type〞)。象内置类型一样,它们没有析构方法(destructor),这导致的结果是:T类不能包含具有析构方法的成员变量。所以,一般情况下,T类的成员变量只能是内置类型的数据或者是其它的T类的对象。在某些的情况下T类也可以包含其它对象的指针或引用,不过,这时它们之前是“使用〞关系,而不是“拥有〞关系(也就是说,这个T类对象并不负责对成员的创立和销毁的工作)。不能拥有外部数据的原因是因为T类没有析构方法。正是由于没有析构方法,T类的对象可以在栈上创立,当程序流程退出函数或产生leave(一种代码异常)的时候,系统自动去除它。即使T类有一个析构方法,在发生异常(在Symbian系统中,异常被称为leave)时Symbian系统也不会调用它,因为leave没有模仿标准C++的抛出异常的做法。T类的对象也可以在堆上创立。但是,应当在调用有可能发生异常的代码之前,将这个对象放入到去除栈(cleanupStack),在发生异常的时候,去除栈(cleanupStack)会释放这个对象。C类这种类都是从CBase派生来的(直接或间接)。//.hfileclassCStudent:publicCBase{ public: CStudent(){ RDebug::Print(_L("iamastudent")); }; ~CStudent() { RDebug::Print(_L("please,don'tkillme!")); } voidSampleFunction(){}; private: TIntiCode; TIntiScore;};CBase有两个特点:首先,它有一个虚的析构方法,这样,可以通过CBase指针来删除它的子类。代码如下所示:CBase*pStu=newCStudent();deletepStu;结果:iamastudentplease,don'tkillme!其次,CBase类和它的子类,重载了new操作符,这使得当它在堆上创立的时候,自动初始化为0,也就是说,当它一开始被创立出来的时候,所有的成员变量都被初始化为0,所以您不必在构造方法中去做这件事情。但是,在栈上创立对象时,情况并非这样,因为这时没有用到new操作。这将潜在地导致堆上创立的对象和栈上创立的对象的行为不一致。因此,C类的对象一定要在堆上创立。很明显,当一个堆上的C类对象不再被需要时,我们需要消耗它。一个C类的对象可能以两种方式存在:其它类的指针成员变量或是一个局部的针指变量。在前一种情况下,我们可以在类的析构方法中调用delete来删除它;后一种情况要复杂一些,在调用任何有潜在的异常(leave)的代码之前,要把这个指针放到去除栈(cleanupstack)中,否则有可能发生内存泄露。CBase类声明了私有的拷贝构造方法和赋值操作(=)。这是一个很好的策略,它可以用来防止客户代码不小心地使用了浅拷贝或赋值的方法。由于基类的拷贝构造和赋值是私有的,所以,如果您希望您的类可以能够使用拷贝构造方法,您必须显式地声明和定义拷贝构造方法和赋值操作。但是,考虑到C类的特性,深拷贝可能造成发生异常(leave)的隐患,而您绝对不能让类的构造方法(或析构方法)发生异常(我们在本教程的后面解释原因)。所以,如果您确实需要一个拷贝的方法,那么您可以为类添加一个的可能会发生异常的方法来完成同样的任务,例如:CloneL()或CopyL()。如果您提供的这个类是从CBase派生的,您就不必为了防止客户代码使用有潜在平安问题的“浅〞拷贝,而在代码中将这些方法声明为私有的。R类前缀“R〞在这里代表资源(Resource),通常是外部资源,例如:文件的句柄(handle)。和C类不同,Symbian系统中不存在一个对应的RBase类,所以一个R类应当有一个构造方法来将它的资源句柄置为0,说明还没有资源和这个新建的对象关联在一起。但是,不要在构造方法中初始化资源句柄,因为这样有可能使构造方法产生异常。R类中常常有类如Open(),Create()或Initialize()这样的方法,它们用来分配资源,设置句柄成员变量的值,并返回错误代码或是产生异常。R类通常也有对应的Close()或Reset()类,用来释放资源,重置句柄的值说明没有资源和该对象关联。使用R类时,一个常见的错误是忘记调用它的Close()方法(当然,该方法也可以是其它名字,但它经常被命名为Close())或是有一个析构方法释放资源,这会引起资源的泄露。R类通常都很小,除了资源句柄没有其它的成员变量。因为不需要。它通常也没有析构方法,资源的释放都是在Close()方法中进行的。大多数情况下,R类都是作为类的成员变量或局部变量存在的。只有少数情况下,在堆上创立。您必须确保,当程序发后异常的时候,资源能被正确地释放通常是使用资源栈。如果一个R类是一个堆上的自动变量(相对于成员变量),您一但要保证资源被释放,而且,变量本身也要被释放。typicallybyusingtwopushcalls:CleanupClosePushL(),orasimilarfunction,toensurethattheresourceiscleanedup,andastandardCleanupStack::PushL(TAny*)whichsimplycallsUser::Free()ontheheapcell.R类的成员变量通常都很简单,所以一般不需要深拷贝(bitwisecopy)。R类的拷贝可能会引起混乱(想象一下:如果两个对象同时在一个资源句柄上调用Close()方法,或两个对象都没有释放资源,会发生什么情况?)如果,您想阻止任何对R类的拷贝,您应当声明(但不定义)一个私有的构造方法和赋值操作。M类当提到多继承的时候,它意味着从一个主要的类派生,同时也混杂基它类的功能。前缀M是单词Mixin的首字母。Symbian系统不赞成多继承的做法,因为这个引入额外的复杂性,M类是一个抽象类,它的作用相当于java中的接口(interface)。在Symbian系统中,M类常被用来定义回调接口或者是观察者(observer)类。M类也可以被其它类继承。下面我们给出两个例子。classMAnimal{ public: virtualvoidEatL()=0;};classMDomesticAnimal:publicMAnimal{ public: virtualvoidNameL()=0;};classCCat:publicCBase,publicMDomesticAnimal{ public: virtualvoidEatL(){};//从MAnimal,经过MDomesticAnimal继承 virtualvoidNameL(){};//从MDomesticAnimal继承 //Otherfunctionsomittedforclarity};上面的例子演示了一个从CBase类和一个M类派生的具体类。而类MDomesticAnimal又是从MAnimal派生的。象接口一样,由于不能被实例化,M类只能有虚(virtual)函数,不能有成员变量和构造方法。但它可以有析构方法,条件是,实现它的具体类必须是从CBase派生的。在定义完类以后,然后可以用使用它。代码如下:CCat*cat1=newCCat;deletecat1;//正确然下面的代码却是错误的。MAnimal*cat2=newCCat;deletecat1;//错误当用M类的指针引用一个对象的时候,如果用delete删除这个指针,则这个M类必须提供一个虚拟的析构方法,否则会出现系统异常(paniccode42)。将MAnimal的代码改写,则上面代码没有问题。classMAnimal{ public: virtualvoidEatL()=0; virtual~MAnimal();//增加一个虚的析构方法。};3描述符(descriptor)在Symbian系统中,字符串被称为“描述符〞(descriptor),因为它们是自我描述的。在描述符中保存了它所表示的字符串的长度和它的底层的内存布局的信息。描述符比标准C中的字符数组和字符指针要复杂,您可能需要多花些时间来学习和掌握它的用法。关键是,它们的特殊设计使得它们在少量内存的设备上非常有效率,仅用非常少的内存就可以保存自己的长度和内存布局的信息。现在,让我们来深入了解描述符的设计思想。在Symbian系统中,描述符是相当让人迷惑的,因为它的种类繁多。不同种类的描述符具有不同的特性和用法,但又经常能相互转换。它们不同于标准C++中的string,java语言中的string类或MFC中的CString,因为程序员必须自己管理底层的内存分配和去除工作。它们具有防治内存溢出的机制,并且不依赖NULL终结符号来决定字符串的长度,从这方而来讲,它也不同于C语言中的字符串。现在我们来讨论:什么是描述符?它们是如何工作的?在探讨这些不同的描述符之前,先让我们需要弄清楚一个根本的概念:什么是字符串数据的“宽度〞?这个长度指的是单个字符是8bit的,还是16bit的宽度。在早期的版本中,字符的宽度都是8bit的,后来为了支持Unicode字符集,从第5版起,Symbian系统将16bit的字符作为标准。Symbian系统现在支持这两种字符长度的描述符,除了Copy()和Size()两个方法以外,这两种宽度的描述符的行为是完全一致的,这两个方法的使用,我们后面再介绍。另外,有一套中立的描述符类型,它们既可以被定义为窄的描述符类型,也可以被定义为宽的描述符类型,这要取决于编译时的宽度。您可以从它的名字上很容易看出这个类型所表示的宽度。假设,它以8结尾(例如:TPtr8,就意味着它表示是的8bit的窄字符,而以16结尾的描述符类(例如:TPtr16)则操作16bit的宽字符。对中立(neutral)的类型来讲,没有数字结尾(例如:TPtr),在Symbian系统第5版以后,默认的情况下,它们表示宽度为16bit的字符串。它们之间的关系比较类似于TInt,TInt16或TInt32之间的关系,这一点应当是比较易于理解的。一般情况下,您没有必要指明是字符串的宽度,用中立的类型就可以了,这样使你的代码易于在宽字符版本和窄字符版本之间转换(有过编程经验的朋友应该有这样的印象,我们平常写代码,大多情况下,仅仅使用UINT类型,而较少考虑使用UINT16,UINT32类型)。另外一个问题是:描述符和字面量(literal)的区别。所谓字面量是指在编码的时候就已经确定的量,例如,标准C中的char*p="Helloworld";其中的"Helloworld"就是字面量。在Symbian系统中,对它们的处理是很不一样的,这点我们在后面再介绍。有了这样的一些认识,现在我们可以来看看有哪些描述符类型。在Symbian系统中描述符类型有两大种类:不可修改(non-modifiable)的描述符和可修改(modifiable)的描述符。3.1不可修改(non-modifiable)的描述符在Symbian系统中,所有的描述符都继承自TDesC,在前面我们已经讨论了类名前缀T所代表的意义,在这里,我们更关心类名的后缀C所代表的意义,这个C是单词Constant的首字符,表示这个类是不可更改的。这个类提供了一些用来返回字符串的长度和操作数据的方法。Length()方法返回了描述符的长度,因为,每个描述符对象在内存中的布局都是同样的,用4个字节来表示它所包含的数据的长度(实际上,只用了32个bit中的28个bit,剩余的4bit留作它用,所以描述符能表示的最大的长度为228字节,256MB,不过这已经足够了)。所以,Length()方法没有被它的子类重写,它对所有子类都有效。但是,根据实现子类的方法的不同,子类访问数据的方式也不一样,Symbian系统不要求它的子类通过虚函数的方式来实现自己的访问数据的方法。不用虚函数重写的原因是因为,虚函数会给每个被派生的描述符对象增加4节字的额外负担,c++用这4个字节来存放指向虚函数表的指针。我们前面说过,在设计描述符的时候要让它尽可能高效,额外的字节开销被认为是不理想的。存放长度的4个字节中,28bit用来表示长度,剩下的4bit用来表示描述符的类型。目前,symbian系统中有5种派生的描述符类型,4bit限制了描述符的种类最多只能有16种,但这已经足够了。子类可以通过调用基类TDesC的Ptr()方法来访问描述符的数据,Ptr()方法检查这4个bit,确定描述符的类型并返回它的数据在内存中的地址。当然,这要求TDesC基类清楚它的子类的内存布局,并在Ptr()方法中使用硬编码的方法。后面,为了表述上的方便,我们也把这种不可修改的描述符也称为常量描述符(constantdescriptor)总结:不可修改的描述符类TDesC是所有的非字面量描述符的基类,它提供了确定描述符长度和访问数据的方法,另外,它实现了所有的您想用来处理常量字符串的操作。3.2可修改(modifiable)的描述符所有的可修改的描述符都从TDes基类派生,而TDes本身又是从TDesC派生的。TDes有一个额外的成员变量用来存放为该描述符分配数据的最大长度。MaxLength()方法返回了这个最大的长度。像TDesC中的Length()方法一样,MaxLength()方法也不被TDes的子类继承。 TDes类提供了一系列的方法,用来对可修改字符串数据的操作,包括对字符串的附加、填充和格式化操作。所有的这些方法都被派生类继承,派生类只实现一些特定的构造方法和复制赋值的方法。这些方法都不负责分配内存,假设它们超过了描述符的数据长度,例如,用Append()方法在某个字符串后面附加另一个字符串时,在调用该方法之前,您必须确保有足够的内存空间。当然,只要不超过描述符的最大存储容量,描述符的长度可以自由地伸缩。当描述符长度比最大长度短的时候,描述符的后面局部是多余未用的。这些方法使用了断言(assertion)来确保描述符的最大长度不会被超出。如果发生内存溢出,将会产生一个panic(关于panic,我们将在后面的章节介绍),这样可以方便您检查和修正程序的错误。事实上,不可能使描述符溢出,这一点保证了您代码的强壮性,而且不易产生难以跟踪的内存陷阱。但需要注意的是,由于基类的构造方法是proteced类型的,所以您无法直接实例化一个TDesC或TDes类的实例。现在我们来看看描述符的派生类,您可以实例化和使用派生类的对象。正如前面所说,这个地方是比较让人迷惑的,因为描述符存在大量的派生类。前面,我们已经解释过为什么每个类会有三个不同的版本,例如:TDes8,TDes16和TDes,分别对应窄字符,宽字符和中立的类。现在,让我们看看有哪些主要的描述符类型,在深入讨论每种类型的细节之前,我们先考察一下它们在一般情况下的内存布局。描述符有两种根本的内存布局:指针描述符和缓存区描述符。不同之处在于,指针描述符持有一个指向字符串的指针,而这个字符串存储在内存中的基它位置。与指针描述符不同,缓存区描述符本身持有字符数据,也就是说字符数据本身构成了描述符的一局部。总结:TDes是所有的可修改的描述符的基类,并且它自己也是从TDesC派生的。它有一个能返回最大的内存容量的方法和一系列的用来修改字符串数据的方法。3.3指针描述符(pointerdescriptor)指针描述符可分为两种:TPtrC和TPtr(我们前面说过,每种类型的描述符,按照字符宽度,都可以分为三个版本,例如:窄字符版本TPtrC8,宽字窄版本TPtrC16和中立的版本TPtrC,所以严格来讲,有六种指针描述符)。指针描述符所持有的字符串是跟描述符本身分开来存放的,它可以被存储在ROM中,堆中或栈中。由于保存数据的内存既不为描述符所拥有,也不通过它来管理。所以,如果要该描述符是在堆上分配的,那么应通过堆描述符(HBufC,下面将要讲解)来操作内存的分配和销毁;如果指针描述符所指向的字符串是在栈上分配的,那这个内存必须是已经在栈上分配好的。通常情况下,指针描述符是基于栈的,但有时候,它们也可以在堆上使用,例如:作为一个CBase派生类的成员变量的时候。在不可修改的描述符(TPtrC)中,指向数据的指针存放在长度的后面,因此,指针描述符的总长度为2个字(word);在可修改的指针描述符中,它存放在最大长度的后面,因此,总长度为3个字。下列图比较了TPtr和TPtrC内存布局.TPtrCTPtrC相当于C语言中的constchar*。被它指向的数据可以被访问但不能被修改:也就是说,描述符中的数据是常量。所有的从基类TDesC中继承的操作都是可访问的。TPtrC定义了一系列的构造方法,使得它能从其它的描述符、指向内存的指针或以0结尾的C语言字符串构造。//字面量描述符将在后面介绍_LIT(KLiteralDes,"Sixtyzipperswerequicklypickedfromthewovenjutebag");TPtrCpangramPtr(KLiteralDes);//从字面量描述符构造TPtrCcopyPtr(pangramPtr);//从其它的描述符构造TBufC<100>constBuffer(KLiteralDes);//常量缓存区描述符,后面介绍TPtrCptr(constBuffer);//ConstructedfromaTBufC//TText8isasingle(8-bit)character,equivalenttounsignedcharconstTText8*cString=(TText8*)"Waltz,badnymph,forquickjigsvex"; //从以0结尾的字符串构造TPtrC8anotherPtr(cString);TUint8*memoryLocation;//PointerintomemoryinitializedelsewhereTIntlength;//Lengthofmemorytoberepresented...TPtrC8memPtr(memoryLocation,length);//从一个指针构造。这个指针本身可以改变成指向其他的字符串数据(通过Set()方法)。如果您想指明,不能改变您的TPtrC所指向的数据,那么您可以将TPtrC声明为const,这样,当您试图用Set()方法更改TPtrC所指向的数据时,编译器会产生警告。//字面量描述符_LIT(KLiteralDes1,"Sixtyzipperswerequicklypickedfromthewovenjutebag");_LIT(KLiteralDes2,"Waltz,badnymph,forquickjigsvex");TPtrCalpha(KLiteralDes1);TPtrCbeta(KLiteralDes2);alpha.Set(KLiteralDes2);//alphapointstothedatainKLiteralDes2beta.Set(KLiteralDes1);//betapointstothedatainKLiteralDes1constTPtrCgamma(beta);//Pointstothedatainbeta,KLiteralDes1gamma.Set(alpha);//Generatesawarning,butpointstoalpha这里应当加一些示范代码TPtrTPtr是可修改的指针描述符,它可用来访问和修改字符串或二进制数据。TDesC和TDes所提供的所有的操作都适用于TPtr。这个类定义了一些构造方法,使得它能从指向内存的指针构造,并设置适当的长度值和最大长度值。编译器也会产生隐式的构造方法和拷贝构造方法,因为它们没有被声明为保护的或私有的。一个TPtr对象可以从其它的可修改描述符构造,例如:通过在不可修改的描述符上调用Des()方法,这个方法返回一个如下所示的TPtr对象:_LIT(KLiteralDes1,"Jackdawslovemybigsphinxofquartz");TBufC<60>buf(KLiteralDes1);//TBufCaredescribedlaterTPtrptr(buf.Des());//Copyconstruction;canmodifythedatainbufTIntlength=ptr.Length();//Length=37TIntmaxLength=ptr.MaxLength();//Maximumlength=60,asforbufTUint8*memoryLocation;//Validpointerintomemory...TIntlen=12;//LengthofdatatoberepresentedTIntmaxLen=32;//Maximumlengthtoberepresented//ConstructapointerdescriptorfromapointerintomemoryTPtr8memPtr(memoryLocation,maxLen);//length=0,maxlength=32TPtr8memPtr2(memoryLocation,len,maxLen);//length=12,max=32另外,TPtr提供了赋值运算符=(),用来拷贝数据到指针所指向的内存(数据源可以是可修改、不可修改的指针描述符,或以0结尾的字符串)。如果要拷贝的数据的长度超过了描述符的最大长度,会引发一个系统异常。像TPtrC一样,TPtr也定义了一个Set()方法,用来改变描述符所指向的数据。_LIT(KLiteralDes1,"Jackdawslovemybigsphinxofquartz");TBufC<60>buf(KLiteralDes1);//TBufCaredescribedlaterTPtrptr(buf.Des());//PointstothecontentsofbufTUint16*memoryLocation;//Validpointerintomemory...TIntmaxLen=40;//MaximumlengthtoberepresentedTPtrmemPtr(memoryLocation,maxLen);//length=12,maxlength=40//CopyandreplacememPtr=ptr;//memPtrdataisKLiteralDes1(37bytes),maxLength=40_LIT(KLiteralDes2,"Thequickbrownfoxjumpsoverthelazydog");TBufC<100>buf2(KLiteralDes2);//TBufCaredescribedlaterTPtrptr2(buf2.Des());//Pointstothedatainbuf//Replacewhatptrpointstoptr.Set(ptr2);//ptrpointstocontentsofbuf2,maxlength=100memPtr=ptr2;//AttempttoupdatememPtrwhichpanicsbecausethe//contentsofptr2(43bytes)exceedsmaxlengthofmemPtr(40bytes)您一定不要混淆了Set()方法和=()赋值操作。前者将描述符的指针重置,使它指向新的数据区域,而后者将数据拷贝到描述符中,一般来说,这会更改描述符的长度,但不会更改它的最大长度值。3.5基于栈(stack-based)的缓冲区描述符基于缓冲区的描述符也可以分为可修改的TBuf和不可修改TBufC的两种类型。对这种描述符来讲,字符串数据本身就是描述符的一局部。下列图给出了描述符的内存布局:这两种描述符通常用来存储定长的或相对较小的字符串,常用来存放长度小于256个字符的文件名。类似于C语言中的char[],但是,它们具有检查内存溢出的功能。TBufC<n>TBufC<n>是不可修改的缓冲区类型,它主要用来存放字符串常量或是二进制数据。该类从TBufCBase类派生,尖括号<>内的数字表示分配给该描述符的数据区的大小。它定义了一些构造方法,允许从其它的描述符或以0结尾的字符串构造。也允许创立一个空的描述符,然后再填充。由于该描述符的数据是不可修改的,它的整个内容可以被置换(通过该类的所定义的赋值操作),用来置换的数据可以是其它的不可修改的描述符或是0结尾的字符串,但是,无论是何种情况,新数据的长度都不能超过长度n(也就是创立该类的时候指定的模板参数)。_LIT(KPalindrome,"Satan,oscillatemymetallicsonatas");TBufC<50>buf1(KPalindrome);//ConstructedfromliteraldescriptorTBufC<50>buf2(buf1);//Constructedfrombuf1//ConstructedfromaNULL-terminatedCstringTBufC<30>buf3((TText*)"Neveroddoreven");TBufC<50>buf4;//Constructedempty,length=0//Copyandreplacebuf4=buf1;//buf4containsdatacopiedfrombuf1,lengthmodifiedbuf1=buf3;//buf1containsdatacopiedfrombuf3,lengthmodifiedbuf3=buf2;//Panic!Maxlengthofbuf3isinsufficientforbuf2data该描述符中的数据可以被整体置换,但不能被直接修改,但有时候我们确实需要修改缓存区中的数据,该怎么办呢?系统提供了另一种途径来修改数据。该类定义了Des()方法,它为缓存区中的数据返回一个可修改的指针描述符(TPtr)。我们可以通过这个指针描述符间接地修改缓冲区中的数据。当数据通过指针描述符被修改以后,指针描述符和缓冲区描述符中的iLength的值会跟着改变,但要记住,缓存区描述符的长度值只可能减小,而是不可能增大的,因为,描述符类是不提供内存管理管理功能的。_LIT8(KPalindrome,"Satan,oscillatemymetallicsonatas");TBufC8<40>buf(KPalindrome);//ConstructedfromliteraldescriptorTPtr8ptr(buf.Des());//dataisthestringinbuf,maxlength=40//Illustratestheuseofptrtocopyandreplacecontentsofbufptr=(TText8*)"DoGeeseseeGod?";ASSERT(ptr.Length()==buf.Length());_LIT8(KPalindrome2,"Arewenotdrawnonward,wefew,drawnonwardtonewera?");ptr=KPalindrome2;//Panic!KPalindrome2exceedsmaxlengthofptr(=40)TBuf<n>这也是一个模板类,它是一个可修改的缓冲区描述符类,后面的<n>表示缓冲区大小。TBuf从TBufBase类派生,而TBufBase是从TDes派生的,因此,它继承了TDes和TDesC类所有的方法。像TBufC<n>一样,TBuf<n>也定义了一系列的构造方法和赋值操作。对所有的描述符类型来讲,内存管理是您的责任,尽管这个缓冲区中的数据是可修改的,但它的长度不能超过在构造方法中所给定的最大值(n)。假设缓冲区的内容需要扩展,那么您必须决定是在编译的时候就给定一个足够大的值,或是在运行的时候动态分配内存。但无论哪种情况,都要确保数据长度不要超过缓存区的最大长度。如果需要使用动态分配的内存,您可以使用基于堆的描述符,这个我们在后面要讲到。要是您觉得管理内存分配的任务太过繁重,您也可以选择使用动态数组。不过,您应当记住,使用动态数组的额外开销是很高的。_LIT(KPalindrome,"Satan,oscillatemymetallicsonatas");TBuf<40>buf1(KPalindrome);//ConstructedfromliteraldescriptorTBuf<40>buf2(buf1);//ConstructedfromconstantbufferdescriptorTBuf8<40>buf3((TText8*)"DoGeeseseeGod?");//fromCstringTBuf<40>buf4;//Constructedempty,length=0,maximumlength=40//Illustratecopyandreplacebuf4=buf2;//buf2copiedintobuf4,updatinglengthandmaxlengthbuf3=(TText8*)"Murderforajarofredrum";//updatedfromCstring3.6基于堆的(Heap-Based)缓冲区描述符当您要使用非常长的字符串时,有另外一种选择:基于堆的描述符。它能拥有比它的创立者更长的生存期。当您在编译的时候还不能确定缓冲区长度的时候,堆描述符也是很有用的,这时,它的作用相当于C语言中的malloc。HBufC也许您已经发现,HBufC的类名以“H〞开头,这不符合Symbian系统中惯用的命名习惯。这确实是一个特例,“H〞表示这个类一般是在堆(Heap)上分配的。HBufC定义了静态的NewL()方法,用来在堆上创立一个缓存区。正如您所见到,HBufC中的字母“C〞表示这个表述符是不可修改的。对该类的操作几乎和TBufC<n>一样:该类提供了一套赋值操作,允许整个缓冲区中的内容被替换掉;同样,新内容的长度不能超过缓存区的大小,否则会引起系统异常;通过调用Des()方法,可以返回一个可修改的指针描述符(TPtr),可以通过这个指针描述符来更改缓冲区中的内容。_LIT(KPalindrome,"DoGeeseseeGod?");TBufC<20>stackBuf(KPalindrome);//Allocatesanemptyheapdescriptorofmaxlength20HBufC*heapBuf=HBufC::NewLC(20);TIntlength=heapBuf->Length();//Currentlength=0TPtrptr(heapBuf->Des());//Modificationoftheheapdescriptorptr=stackBuf;//CopiesstackBufcontentsintoheapBuflength=heapBuf->Length();//length=17HBufC*heapBuf2=stackBuf.AllocLC();//Fromstackbufferlength=heapBuf2->Length();//length=17_LIT(KPalindrome2,"Palindrome");*heapBuf2=KPalindrome2;//CopyandreplacedatainheapBuf2length=heapBuf2->Length();//length=10CleanupStack::PopAndDestroy(2,heapBuf);记住,堆描述符可以按您的要求的尺寸动态分配内存,但它不会自动按您的期望更改缓冲区的大小。在修改缓存区的内容之前,您要确保缓存区的内存是足够的。为了帮您简化这些操作,HBufC提供的一套ReAllocL()方法,它可以用来扩展堆的缓存区(这个操作有可能会使缓冲区从一个内存区域搬到另一个区域)。IftheHBufC*isstoredonthecleanupstack,movingthepointerasaresultofmemoryreallocationcancausesigni.cantproblemseitherintheeventofaleaveorifthecleanupstack’sPopAndDestroy()functionisusedtodestroythememory.如果您在HBufC上调用Des()方法来获取了TPtr,在经过重新分配内存后,TPtr中的成员变量iPtr有可能变成无效的。因此,为了确保平安,在重新分配内存后,应该再次调用Des()来创立一个新的TPtr对象。注:出于性能上的考虑,Symbian系统并没有提供可修改的堆描述符HBuf。总结:Symbian系统中总共有5种类型的描述符,TPtrC,PTtr,TBufC<n>,TBuf<n>和HBufC。下面的图示说明了它们的继承关系。3.7字面量描述符(LiteralDescriptors)下面我们来看看字面量描述符,它相当于C语言中的staticchar[]。字面量描述符是通过一系列的宏来创立的,这些宏可在头文件e32def.H中找到#define_L8(a)(TPtrC8((constTText8*)(a)))#define_S8(a)((constTText8*)a)#define_LIT8(name,s)conststaticTLitC8<sizeof(s)>name={sizeof(s)-1,s}#define_L16(a)(TPtrC16((constTText16*)L##a))#define_S16(a)((constTText16*)L##a)#define_LIT16(name,s)conststaticTLitC16<sizeof(L##s)/2>name={sizeof(L##s)/2-1,L##s}首先,我们来看_LIT,这是最有效率也是被使用得最多的一个。这个宏的用法如下:_LIT(KMyLiteralDescriptor,"Thequickbrownfoxjumpsoverthelazydog");后面KMyLiteralDescriptor就可以作为一个常量来使用,例如可以将它写到文件或显示给用户。_LIT宏构建了一个名为KMyLiteralDescriptor的TLitC16对象,其中保存了字符串的值(在这个例子中是Thequickbrownfoxjumpsoverthelazydog),在二进制程序中可以找到这个值,因为它是被写到文件中的。如您所料,_LIT8和_LIT16的用法相似。因为描述符的宽度为16bit,所以,在将C字节类型的字符串转换为描述符能用的数据时,宏将字符串的长度除以2。作为参考,下面给出类TLitC16的定义,其中__TText被定义为宽的,16bit的字符。TLitC8也有类似的定义。template<TIntS>classTLitC16{public:inlineconstTDesC16*operator&()const;inlineoperatorconstTDesC16&()const;inlineconstTDesC16&operator()()const;...//Omittedforclaritypublic:TUintiTypeLength;__TTextiBuf[__Align16(S)];};template<TIntS>inlineconstTDesC16*TLitC16<S>::operator&()const{returnREINTERPRET_CAST(constTDesC16*,this);}template<TIntS>inlineconstTDesC16&TLitC16<S>::operator()()const{return*operator&();}template<TIntS>inlineTLitC16<S>::operatorconstTDesC16&()const{return*operator&();}从上面的定义中可以看到,TLitC16(和TLitC8)并不从TDesC8或TDesC16派生,但是它们与TBufC8或TBufC16具有相同的内存布局。这就使得TLitC16(和TLitC8)可以用在任何可以使用TDesC的地方。您也可以用如下的方法从一个字面量构造一个指针描述符:TPtrC8thePtr(KMyLiteralDescriptor);从字面量构造缓冲区描述符需要一点小技巧。如果您用size()去获得_LIT常量,它会返回相应的TLitC对象的尺寸大小,这个尺寸相当于描述符内容的尺寸加上额外的8个byte(用来存放长度值的4字节和表示结束符的NULL)。如果您想用它来构造基于堆的描述符,必须要将这额外的8个字节考虑进去。//定义一个包含44字符的字面量_LIT8(KExampleLit8,"Thequickbrownfoxjumpedoverthelazydog");TIntsize=sizeof(KExampleLit8);//52bytes(contents+8bytes)TBufC8<(sizeof(KExampleLit8)-8)>theStackBuffer(KExampleLit8);对基于堆的描述符,您可以用描述符实际内容的长度来分配缓冲区,然后将内容拷贝到描述符中。为了得到正确的长度,您可以用公共(public)的成员变量iTypeLength,或者,也可以用更简单的方法,使用()操作符来将字面量转换成一个描述符,然后用这个得到的描述符来得到内容的长度。但最简单的方法是,使用()操作符将对象转换成描述符后,直接调用TDes::AllocL()方法,返回一个HBufC*,代码如下:TIntdescriptorLength=KExampleLit8.iTypeLength;//44bytes//Formastackbufferdescriptoraroundtheliteral//CreateaheapbuffercopyingthecontentsoftheliteralHBufC8*theHeapBuffer=KExampleLit8().AllocL();//对宽字符字面量的操作类似_LIT16(KExampleLit16,"Thequickbrownfoxjumpedoverthelazydog");size=sizeof(KExampleLit16);//96bytes(contentsinbytes+8bytes)descriptorLength=KExampleLit16.iTypeLength;//44bytes(contents)用_L和_LIT生成的字面量,它们的内存布局是有差异的,如下列图所示:现在我们简单地看看_L和_S宏,这两个宏已经过时,但在测试代码中还经常用到。RDebug::Print(_L("Helloworld!"));这个代码的作用相当于:_LIT(KLit,"Helloworld!");RDebug::Print(KLit);从上面的代码可以看到,使用_L的好处在于,您可以直接使用它,而无需在使用之前,在别的地方声明。字符串(〞Helloworld!〞)被作为一个根本的以0结尾的字符串写到二进制文件中,它前面没有长度值(这不同于_LIT产生的字符串)。由于没有长度值,字面量的内存布局不同于描述符,并且当代码运行的时候,_L的第个实例都会产生一个临时的TPtrC,这个TPtrC的指针指向字面量的第一个字节在ROM中的存储位置。只要是在创立该字面量的生存期中使用这个临时的描述符,这都是平安的。然而,创立临时变量要求设置指针、长度和描述符的类型,这对内联的构造方法来说是一个负担,如果代码中有很多这样的字面量,也会使得二进制程序的体积增大。如果仅从存储方式上看,_S宏和_L是相同的,但有一点不同它不产生临时的TPtrC描述符。如果您仅将它作为以0结尾的描述符使用,那么就使用_S宏。到目前为止,我们已经讨论了关于描述符的根本知识,包括如何实例化每一种具体的描述符,如何访问和修改描述符的数据,以及如何置换描述符的内容。现在我们来关注一下操作数据的方法和在使用描述符时一些常见的问题。3.8描述符作参数和返回类型在编写代码的时候,您可能不想被限制于只能使用TBuf,原因是仅仅因为某个特定的库函数要求使用它。同样的道理,作为函数的提供者,您可能对调用者传递进来的参数类型不感兴趣。事实上,您不应该要求调用者传递特定类型的参数,因为您可能在后面要修改函数的实现,您可能要改变描述符的类型,如果您将这样的函数作为编程接口,最后您不得不让您的客户也改变他们的代码。这样的改动是非常不理想的,因为它破坏了代码的兼容性。除非您来掌管描述符(负责描述符的创立和销毁工作),您甚至可以不用知道描述符是基于堆的还是基于栈的。事实上,只要标准类型的描述符(我们前面提到的5种描述符类型之一),就可以在它上面调用适当的方法,客户代码完全可以忽略描述符的内存布局和它在内存中的位置。基于以上的原因,当您定义函数的时候,应当尽量使用抽象的基类作为函数的参数和返回值。为了有效率,描述符参数应当使用引用传递的方式,要么是constTDesC&或者是TDes&。例如,类RFile定义了read()和write()方法IMPORT_CTIntWrite(constTDesC8&aDes);IMPORT_CTIntRead(TDes8&aDes)const;在这两个方法中,输入的描述符被显式地声明为8bit的宽度,这样可以既写入字符串,也可以写入二进制数据。被用来写入到文件中的参数是对一个不可修改的描述符的引用,而在读文件的时候,使用了可修改的描述符的引用。可修改描述符的最大长度决定了可以从文件中读入多少数据,所以不需要再给文件效劳器传递一个表示长度的参数。文件效劳器将会填充满描述符。当文件中的数据不够描述符的最大长度时,文件效劳器会把所有可得的数据写入描述符。调用函数后,描述符的长度反映了写入数据的长度。这样,调用者也无需再另外传递一个参数用来表示返回的数据长度。当写一个函数的时候,如果参数是可修改的描述符,实际上您不必考虑它是否有足够的空间用来存放数据,因为描述符本身有边界检查的机制,如果出现了内存溢出现象,会产生系统异常。当然,您也可能不希望在描述符数据区过短的情况下,描述符的方法会发生系统异常。这时,您应当在文档中说明,如果描述符的长度不够将会如何处理。有时候,一个比较好的方法是,给调用者返回一个长度值,这样,调用者可以采用适当的步骤来分配一个正确长度的描述符。HBufC*CPoem::DoGetLineL(TIntaLineNumber){//Codeomittedforclarity.Allocatesandreturnsaheapbuffer//containingthetextofaLineNumber(leavesifaLineNumberis//outofrange)}voidCPoem::GetLineL(TIntaLineNumber,TDes&aDes){HBufC*line=DoGetLineL(aLineNumber);CleanupStack::PushL(line);//Isthedescriptorlargeenough(4bytesormore)toreturnan//integerrepresentingthelengthofdatarequired?if(aDes.MaxLength()<line->Length()){if(aDes.MaxLength()>=sizeof(TInt)){//Writesthelengthrequired(TPckgisdescribedlater)TPckg<TInt>length(line->Length());aDes.Copy(length);}//Leave&indicatethatthecurrentlengthistooshortUser::Leave(KErrOverflow);//LeavesaredescribedinChapter2}else{aDes.Copy(*line);CleanupStack::PopAndDestroy(line);}}另一个方案是,在函数中分配堆缓冲区,把它返还给调用者,由调用者负责销毁它。3.9常用的方法Ptr()基类TDesC实现了Ptr()方法,用来访问描述符的数据,该方法返回一个指向字符数组首地址的指针。您可以通过这个指针来直接操作字符串数据。代码如下所示:Size()和Length()TDesC实现了Size()andLength()方法,前者返回描述符所占有的字节数,而后者返回的是描述符的字符长度。对8bit的描述符来讲,它们是相等的,而对16bit的描述来说,Size()返回的数值是Length()的两倍。MaxLength()可修改的描述符TDes实现的这个方法返回描述符的最大长度。SetLength()和SetMax()前者用来设置描述符的长度,这个长度值必须是小于描述符的最大长度的,否则会引起系统异常。后者将描述符的当前长度设置成最大值,注意,它不并不能扩展描述符数据区的长度。Zero()和FillZ()前者将描述符的长度设置为0,而后者是用0来来填充描述符的内容置。如果您要用其它字符填充描述符的内容,可用Fill()方法。这个方案类似于C语言中的memset()函数。Copy()TDes实现了一系列的重的Copy()方法,下面是其中的两个:IMPORT_CvoidCopy(constTDesC8&aDes);IMPORT_CvoidCopy(constTDesC16&aDes);这些方法将参数描述符中的数据拷贝到目标描述符中,同时为目标描述符设置新的长度。如可源描述符的长度超过目标描述符的最大长度,将会引发一个系统异常。3.10使用HBufC堆描述符我们已经讨论过描述符的一些特性,现在来关注一下使用描述符时经常容易范的错误。首先,我们将创立和使用堆描述符HBufC。前面提到过,在已有的描述符上调用Alloc()或AllocL()方法,可以产生一个新的HBufC。这里是一个例子:voidCSampleClass::UnnecessaryCodeL(constTDesC&aDes){iHeapBuffer=HBufC::NewL(aDes.Length());TPtrptr(iHeapBuffer->Des());ptr.Copy(aDes);...//以上代码完全可以被下面的代替,下面代码更有效率。iHeapBuffer=aDes.AllocL();}Anothercommonwaytointroducecomplexityoccursintheoppositedirection,thatis,thegenerationofTDesC&fromaheapdescriptor.当从一个堆描述符产生一个TDesC&的时候,也容易范一个错误,这个错误同样为代码增加了复杂性。代码如下所示:constTDesC&CSampleClass::MoreAccidentalComplexity(){return(iHeapBuffer->Des());//以上代码完全可以写成return(*iHeapBuffer);//这样更简洁高效}另外一个比较微妙问题是,当您分配一个HBufC以后,然后在它上面调用Des(),可以返回一个TPtr对象。HBufC*buf=HBufC::NewL(9);TPtrp=buf->Des();可是,假设您回忆一下,可以知道在HBufC中,并没有一个字(word)用来保存最大长度的信息因为HBufC是不可修改的(non-modifiable),它不需要最大长度的信息。然而,,TPtr需要这个最大长度的信息,这时问题来了,您从哪里得到这个最大长度呢?答案在于:当您调用Des()的时候,系统用HBufC的最大
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度学校食堂承包与绿色环保经营协议
- 二零二五年度不动产租赁转售权转让合同
- 2025年度消毒餐具销售渠道拓展代理合同
- 新生儿护理中应避免的误区试题及答案
- 2025年度智能制造公司高管岗位聘用合同
- 二零二五年度加油站合作协议解除通知模板
- 2025年度科技型企业虚拟股份协议书
- 计算机应用程序的开发与优化试题及答案
- 船舶工业事故案例分析
- 创新科技与安全培训
- ART-850A系列数字式厂用变保护测控装置技术说明书
- 精装修工程一户一验记录表
- 红色大气中考百日誓师大会PPT模板
- 哈萨克斯坦共和国有限责任公司和补充责任公司法
- 维语宗教事务条例(2015)
- 红河学院本科生毕业论文模板
- IQC(来料)检测报告模板
- (完整版)电机学第五版课后答案_(汤蕴璆)
- ZY10000╱28╱62掩护式液压支架设计说明书
- 食堂管理流程图(共1页)
- 污水管道工程施工安全事故应急预案
评论
0/150
提交评论