Symbian開發教程

Symbian系統已經提供了一套已經定義好的內置的數據類型。爲了保證你的代碼是編譯器無關的,應當使用下面symbian系統提供的數據類型,而不要使用原生數據類型(native types,這裏指標準C中的int ,char等)。

1. 基本類型

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才被定義爲long long類型,真正使用64位的內置數據類型。

? TReal32 和 TReal64 (TReal相當於TReal64)

    這兩個數據類型相當於單精度和雙精度的浮點數,由於浮點數的運算要比整數慢,所以一般應儘量避免使用浮點數的運算。

? TTextX (其中X = 8 或 16)

    分別對應窄或寬的字符(注:所謂窄字符通常ASCII碼字符,而寬字符是指unicode字符集的字符 )

? TAny*

TAny* 意爲指向任意內容的指針,在這種意義上講,TAny相當於void, TAny* 相當於TAny*。但是,在某些場合下,void標示‘空’,如:

void  hello(void);

這時,不要將它改寫爲:  TAny hello(TAny);

? TBool 

標示布爾類型。Symbian系統提供了兩個常量:ETrue (=1) 和 EFalse (=0),分別表示真和假。

注意:在Symbian系統中,TBool被定義爲int, 而ETrue和EFalse被定義爲enum,所以,如果一個函數的返回值爲TBool,不要用如下的代碼來比較函數的返回值:

TBool isLarger(TInt a, TInt b)

{

return (a>b)?ETrue:EFalse; 

}

if(isLarger(4,3)==ETrue){...}    //錯誤,編譯不過。

if(isLarger(4,3)){...}   //正確

2類和對象

2.1 Symbian系統中的命名習慣:

在Symbian系統中編寫代碼時,應當遵守種樣幾個規則:成員變量的命名以小寫字母i開頭,方法的參數以小寫字母a開頭,例如:

class Pernon

{

public:

 TInt iAge;

 void SetAge(TInt  aAge){iAge = aAge};

}

在symbian系統中存在幾種不同類型的類(class),不同類型的類,其特性也各不相同。

有的在堆(heap)上創建,有的在棧(stack) 上創建,特別的是,類的實例(instance)的清除方式也不盡相同(下面,爲了方便我們把類的類別稱爲型別)。型別(class type)可以體現 這些不同的特點。每個型別都有一套定義好的關於如何創建和清除實例的規則。爲了容易區分型別,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派生來的(直接或間接)。

//.h file

class CStudent:public CBase 

{

 public:

 CStudent(){

  RDebug::Print(_L("i am a student"));

 };

 ~CStudent()

 {

  RDebug::Print(_L("please, don't kill me!"));

 }

 void SampleFunction(){};

 private:

 TInt iCode;

 TInt iScore;

};

CBase有兩個特點:首先,它有一個虛的析構方法,這樣,可以通過CBase指針來刪除它的子類。代碼如下所示:

CBase *pStu = new CStudent();

delete pStu;

結果:   i am a student

 please, don't kill me!         

其次, CBase類和它的子類,重載了new操作符,這使得當它在堆上創建的時候,自動初始化爲0,也就是說,當它一開始被創建出來的時候,所有的成員變量都被 初始化爲0,所以您不必在構造方法中去做這件事情。但是,在棧上創建對象時,情況並非這樣, 因爲這時沒有用到new操作。這將潛在地導致堆上創建的對象 和棧上創建的對象的行爲不一致。因此,C類的對象一定要在堆上創建。

很明顯,當一個堆上的C類對象不再被需要時,我們需要 消耗它。 一個C類的對象可能以兩種方式存在:其它類的指針成員變量或是一個局部的針指變量。在前一種情況下,我們可以在類的析構方法中調用delete 來刪除它;後一種情況要複雜一些,在調用任何有潛在的異常(leave)的代碼之前,要把這個指針放到清除棧(cleanup stack)中,否則有可 能發生內存泄露。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類是一個堆上的自動變量(相對於成員變量),您一但要保證資源被釋放,而且,變量本身也要被釋放。

R類的成員變量 通常都很簡單,所以一般不需要深拷貝(bitwise copy)。R類的拷貝可能會引起混亂(想象一下:如果兩個對象同時在一個資源句柄上調用 Close()方法,或兩個對象都沒有釋放資源,會發生什麼情況?)如果,您想阻止任何對R類的拷貝,您應當聲明(但不定義)一個私有的構造方法和賦值操 作。

? M 類

當提到多繼承的時候,它意味着從一個主要的類派生,同時也混雜基它類的功能。前綴M是單詞Mixin的首字母。Symbian系統不贊成多繼承的做法,因爲這個引入額外的複雜性,M類是一個抽象類,它的作用相當於java中的接口(interface)。在Symbian系統中,M 類常被用來定義回調接口或者是觀察者(observer)類。M類也可以被其它類繼承。下面我們給出兩個例子。

class MAnimal

{

 public:

 virtual void EatL() =0;

};

class MDomesticAnimal : public MAnimal

{

 public:

 virtual void NameL() =0;

};

class CCat : public CBase, public MDomesticAnimal

{

 public:

 virtual void EatL(){}; // 從MAnimal, 經過MDomesticAnimal繼承

 virtual void NameL(){}; // 從 MDomesticAnimal繼承

 // Other functions omitted for clarity

};

上面的例子演示了一個從CBase類和一個M類派生的具體類。而類MDomesticAnimal又是從MAnimal派生的。象接口一樣,由於不能被實例化,M類只能有虛(virtual)函數,不能有成員變量和構造方法。但它可以有析構方法, 條件是,實現它的具體類必須是從CBase派生的。在定義完類以後,然後可以用使用它。代碼如下:

CCat    *cat1 =   new CCat;

delete  cat1;   //正確

然下面的代碼卻是錯誤的。

MAnimal *cat2 = new CCat;

delete  cat1;   //錯誤

當用M類的指針引用一個對象的時候,如果用delete刪除這個指針,則這個M類必須提供一個虛擬的析構方法,否則會出現系統異常(panic code 42)。將MAnimal的代碼改寫,則上面代碼沒有問題。

class MAnimal

{

 public:

 virtual void EatL() =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 = "Hello world";

其中的"Hello world"就是字面量。在Symbian系統中,對它們的處理是很不一樣的,這點我們在後面再介紹。

有了這樣的一些認識, 現在我們可以來看看有哪些描述符類型。在Symbian系統中描述符類型有兩大種類:不可修改(non-modifiable)的描述符和可修改(modifiable)的描述符。

3.1不可修改(non-modifiable)的描述符

  

在Symbian 系統中,所有的描述符都繼承自TDesC,在前面我們已經討論了類名前綴T所代表的意義,在這裏,我們更關心類名的後綴C所代表的意義,這個C是單詞 Constant的首字符,表示這個類是不可更改的。這個類提供了一些用來返回字符串的長度和操作數據的方法。Length()方法返回了描述符的長度, 因爲,每個描述符對象在內存中的佈局都是同樣的,用4個字節來表示它所包含的數據的長度(實際上,只用了32個bit中的28個bit,剩餘的4bit留 作它用,所以描述符能表示的最大的長度爲228  字節,256 MB,不過這已經足夠了)。所以,Length()方法沒有被它的子類重寫,它對所有子類都有效。但是,根據實現子類的方法的不同,子類訪問數據的方式也不一樣,Symbian系統不要求它的子類通過虛函數的方式來實現自己的訪問數據的方法。 不 用虛函數重寫的原因是因爲,虛函數會給每個被派生的描述符對象增加4節字的額外負擔,c++用這4個字節來存放指向虛函數表的指針。我們前面說過,在設計 描述符的時候要讓它儘可能高效,額外的字節開銷被認爲是不理想的。存放長度的4個字節中,28bit用來表示長度,剩下的4bit用來表示描述符的類型。 目前,symbian系統中有5種派生的描述符類型,4bit限制了描述符的種類最多只能有16種,但這已經足夠了。子類可以通過調用基類TDesC的 Ptr()方法來訪問描述符的數據,Ptr()方法檢查這4個bit,確定描述符的類型並返回它的數據在內存中的地址。當然,這要求TDesC基類清楚它的子類的內存佈局,並在Ptr()方法中使用硬編碼的方法。後面,爲了表述上的方便,我們也把這種不可修改的描述符也稱爲常量描述符(constant descriptor)

總結:不可修改的描述符類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 指針描述符(pointer descriptor)

指針描述符可分爲兩種:TPtrC 和TPtr(我們前面說過,每種類型的描述符,按照字符寬度,都可以分爲三個版本,例如:窄字符版本TPtrC8,寬字窄版本TPtrC16和中立的版本TPtrC,所以嚴格來講,有六種指針描述符)。指 針描述符所持有的字符串是跟描述符本身分開來存放的,它可以被存儲在ROM中,堆中或棧中。由於保存數據的內存既不爲描述符所擁有,也不通過它來管理。所 以,如果要該描述符是在堆上分配的,那麼應通過堆描述符(HBufC,下面將要講解)來操作內存的分配和銷燬;如果指針描述符所指向的字符串是在棧上分配 的,那這個內存必須是已經在棧上分配好的。通常情況下,指針描述符是基於棧的,但有時候,它們也可以在堆上使用,例如:作爲一個CBase派生類的成員變 量的時候。在不可修改的描述符(TPtrC)中,指向數據的指針存放在長度的後面,因此,指針描述符的總長度爲2個字(word);在可修改的指針描述符 中,它存放在最大長度的後面,因此,總長度爲3個字。下圖比較了TPtr和TPtrC內存佈局.

  

? TPtrC

TPtrC相當於C語言中的const char*。被它指向的數據可以被訪問但不能被修改:也就是說,描述符中的數據是常量。所有的從基類TDesC中繼承的操作都是可訪問的。TPtrC定義了一系列的構造方法,使得它能從其它的描述符、指向內存的指針或以0結尾的C語言字符串構造。

// 字面量描述符將在後面介紹

_LIT(KLiteralDes, "Sixty zippers were quickly picked from the woven

jute bag");

TPtrC pangramPtr(KLiteralDes); // 從字面量描述符構造

TPtrC copyPtr(pangramPtr); // 從其它的描述符構造

TBufC<100> constBuffer(KLiteralDes); // 常量緩存區描述符,後面介紹

TPtrC ptr(constBuffer); // Constructed from a TBufC

// TText8 is a single (8-bit) character, equivalent to unsigned char

const TText8* cString = (TText8*)"Waltz, bad nymph, for quick jigs

vex";   

// 從以0結尾的字符串構造

TPtrC8 anotherPtr(cString);

TUint8* memoryLocation; // Pointer into memory initialized elsewhere

TInt length; // Length of memory to be represented

...

TPtrC8 memPtr(memoryLocation,length); // 從一個指針構造。

這個指針本身可以改變成指向其他的字符串數據(通過Set()方法)。如果您想指明,不能改變您的TPtrC所指向的數據,那麼您可以將TPtrC聲明爲const,這樣,當您試圖用Set()方法更改TPtrC所指向的數據時,編譯器會產生警告。

// 字面量描述符

_LIT(KLiteralDes1, "Sixty zippers were quickly picked from the woven jute

bag");

_LIT(KLiteralDes2, "Waltz, bad nymph, for quick jigs vex");

TPtrC alpha(KLiteralDes1);

TPtrC beta(KLiteralDes2);

alpha.Set(KLiteralDes2); // alpha points to the data in KLiteralDes2

beta.Set(KLiteralDes1); // beta points to the data in KLiteralDes1

const TPtrC gamma(beta); // Points to the data in beta, KLiteralDes1

gamma.Set(alpha); // Generates a warning, but points to alpha

? TPtr

TPtr 是可修改的指針描述符,它可用來訪問和修改字符串或二進制數據。TDesC 和TDes所提供的所有的操作都適用於TPtr。這個類定義了一些構造方法,使得它能從指向內存的指針構造,並設置適當的長度值和最大長度值。

編譯器也會產生隱式的構造方法和拷貝構造方法,因爲它們沒有被聲明爲保護的或私有的。一個TPtr對象可以從其它的可修改描述符構造,例如:通過在不可修改的描述符上調用Des()方法,這個方法返回一個如下所示的TPtr對象:

_LIT(KLiteralDes1, "Jackdaws love my big sphinx of quartz");

TBufC<60> buf(KLiteralDes1); // TBufC are described later

TPtr ptr(buf.Des()); // Copy construction; can modify the data in buf

TInt length = ptr.Length(); // Length = 37

TInt maxLength = ptr.MaxLength(); // Maximum length = 60, as for buf

TUint8* memoryLocation; // Valid pointer into memory

...

TInt len = 12; // Length of data to be represented

TInt maxLen = 32; // Maximum length to be represented

// Construct a pointer descriptor from a pointer into memory

TPtr8 memPtr(memoryLocation, maxLen); // length = 0, max length = 32

TPtr8 memPtr2(memoryLocation, len, maxLen); // length = 12, max = 32

另外,TPtr提供了賦值運算符=(),用來拷貝數據到指針所指向的內存(數據源可以是可修改、不可修改的指針描述符,或以0結尾的字符串)。如果要拷貝的數據的長度超過了描述符的最大長度,會引發一個系統異常。像TPtrC一樣,TPtr也定義了一個Set()方法,用來改變描述符所指向的數據。

_LIT(KLiteralDes1, "Jackdaws love my big sphinx of quartz");

TBufC<60> buf(KLiteralDes1); // TBufC are described later

TPtr ptr(buf.Des()); // Points to the contents of buf

TUint16* memoryLocation; // Valid pointer into memory

...

TInt maxLen = 40; // Maximum length to be represented

TPtr memPtr(memoryLocation, maxLen); // length = 12, max length = 40

// Copy and replace

memPtr = ptr; // memPtr data is KLiteralDes1 (37 bytes), maxLength = 40

_LIT(KLiteralDes2, "The quick brown fox jumps over the lazy dog");

TBufC<100> buf2(KLiteralDes2); // TBufC are described later

TPtr ptr2(buf2.Des()); // Points to the data in buf

// Replace what ptr points to

ptr.Set(ptr2); // ptr points to contents of buf2, max length = 100

memPtr = ptr2; // Attempt to update memPtr which panics because the

// contents of ptr2 (43 bytes) exceeds max length of memPtr (40 bytes)

您一定不要混淆了Set()方法和=()賦值操作。前者將描述符的指針重置,使它指向新的數據區域,而後者將數據拷貝到描述符中,一般來說,這會更改描述符的長度,但不會更改它的最大長度值。

3.5 基於棧(stack-based)的緩衝區描述符

基於緩衝區的描述符也可以分爲可修改的TBuf和不可修改TBufC的兩種類型。對這種描述符來講,字符串數據本身就是描述符的一部分。下圖給出了描述符的內存佈局:

這兩種描述符通常用來存儲定長的或相對較小的字符串,常用來存放長度小於256個字符的文件名。類似於C語言中的char[],但是,它們具有檢查內存溢出的功能。

? TBufC<n>

TBufC<n>是不可修改的緩衝區類型,它主要用來存放字符串常量或是二進制數據。該類從TBufCBase類派生,尖括號<>內的數字表示分配給該描述符的數據區的大小。它定義了一些構造方法,允許從其它的描述符或以0結尾的字符串構造。也允許創建一個空的描述符,然後再填充。

由於該描述符的數據是不可修改的,它的整個內容可以被置換(通過該類的所定義的賦值操作),用來置換的數據可以是其它的不可修改的描述符或是0結尾的字符串,但是,無論是何種情況,新數據的長度都不能超過長度n(也就是創建該類的時候指定的模板參數)。

_LIT(KPalindrome, "Satan, oscillate my metallic sonatas");

TBufC<50> buf1(KPalindrome); // Constructed from literal descriptor

TBufC<50> buf2(buf1); // Constructed from buf1

// Constructed from a NULL-terminated C string

TBufC<30> buf3((TText*)"Never odd or even");

TBufC<50> buf4; // Constructed empty, length = 0

// Copy and replace

buf4 = buf1; // buf4 contains data copied from buf1, length modified

buf1 = buf3; // buf1 contains data copied from buf3, length modified

buf3 = buf2; // Panic! Max length of buf3 is insufficient for buf2 data

該描述符中的數 據可以被整體置換,但不能被直接修改,但有時候我們的確需要修改緩存區中的數據,該怎麼辦呢?系統提供了另一種途徑來修改數據。該類定義了Des()方 法,它爲緩存區中的數據返回一個可修改的指針描述符(TPtr)。我們可以通過這個指針描述符間接地修改緩衝區中的數據。當數據通過指針描述符被修改以 後,指針描述符和緩衝區描述符中的iLength的值會跟着改變,但要記住,緩存區描述符的長度值只可能減小,而是不可能增大的,因爲,描述符類是不提供 內存管理管理功能的。

_LIT8(KPalindrome, "Satan, oscillate my metallic sonatas");

TBufC8<40> buf(KPalindrome); // Constructed from literal descriptor

TPtr8 ptr(buf.Des()); // data is the string in buf, max length = 40

// Illustrates the use of ptr to copy and replace contents of buf

ptr = (TText8*)"Do Geese see God?";

ASSERT(ptr.Length()==buf.Length());

_LIT8(KPalindrome2, "Are we not drawn onward, we few, drawn onward to

new era?");

ptr = KPalindrome2; // Panic! KPalindrome2 exceeds max length of ptr(=40)

? TBuf<n>

這也是一個模板類,它是一個可修改的緩衝區描述符類,後面的<n>表示緩衝區大小。TBuf從TBufBase類派生,而TBufBase是從TDes派生的,因此,它繼承了TDes和TDesC類所有的方法。像TBufC<n>一樣,TBuf<n>也 定義了一系列的構造方法和賦值操作。對所有的描述符類型來講,內存管理是您的責任,儘管這個緩衝區中的數據是可修改的,但它的長度不能超過在構造方法中所 給定的最大值(n)。假如緩衝區的內容需要擴展,那麼您必須決定是在編譯的時候就給定一個足夠大的值,或是在運行的時候動態分配內存。但無論哪種情況,都 要確保數據長度不要超過緩存區的最大長度。

如果需要使用動態分配的內存,您可以使用基於堆的描述符,這個我們在後面要講到。要是您覺得管理內存分配的任務太過繁重,您也可以選擇使用動態數組。不過,您應當記住,使用動態數組的額外開銷是很高的。

_LIT(KPalindrome, "Satan, oscillate my metallic sonatas");

TBuf<40> buf1(KPalindrome); // Constructed from literal descriptor

TBuf<40> buf2(buf1); // Constructed from constant buffer descriptor

TBuf8<40> buf3((TText8*)"Do Geese see God?"); // from C string

TBuf<40> buf4; // Constructed empty, length = 0, maximum length = 40

// Illustrate copy and replace

buf4 = buf2; // buf2 copied into buf4, updating length and max length

buf3 = (TText8*)"Murder for a jar of red rum"; // updated from C string

3.6 基於堆的(Heap-Based)緩衝區描述符

當您要使用非常長的字符串時,有另外一種選擇:基於堆的描述符。它能擁有比它的創建者更長的生存期。當您在編譯的時候還不能確定緩衝區長度的時候,堆描述符也是很有用的,這時,它的作用相當於C語言中的malloc。

? HBufC

也許您已經發現,HBufC的類名以“H”開頭,這不符合Symbian系統中慣用的命名習慣。這的確是一個特例,“H”表示這個類一般是在堆(Heap)上分配的。HBufC定 義了靜態的NewL()方法,用來在堆上創建一個緩存區。正如您所見到,HBufC中的字母“C”表示這個表述符是不可修改的。對該類的操作幾乎和 TBufC<n>一樣:該類提供了一套賦值操作,允許整個緩衝區中的內容被替換掉;同樣,新內容的長度不能超過緩存區的大小,否則會引起系統 異常;通過調用Des()方法,可以返回一個可修改的指針描述符(TPtr),可以通過這個指針描述符來更改緩衝區中的內容。

_LIT(KPalindrome, "Do Geese see God?");

TBufC<20> stackBuf(KPalindrome);

// Allocates an empty heap descriptor of max length 20

HBufC* heapBuf = HBufC::NewLC(20);

TInt length = heapBuf->Length();// Current length = 0

TPtr ptr(heapBuf->Des()); // Modification of the heap descriptor

ptr = stackBuf; // Copies stackBuf contents into heapBuf

length = heapBuf->Length(); // length = 17

HBufC* heapBuf2 = stackBuf.AllocLC(); // From stack buffer

length = heapBuf2->Length(); // length = 17

_LIT(KPalindrome2, "Palindrome");

*heapBuf2 = KPalindrome2; // Copy and replace data in heapBuf2

length = heapBuf2->Length(); // length = 10

CleanupStack::PopAndDestroy(2, heapBuf);

記住,堆描述符可以按您的要求的尺寸動態分配內存,但它不會自動按您的期望更改緩衝區的大小。在修改緩存區 的內容之前,您要確保緩存區的內存是足夠的。爲了幫您簡化這些操作,HBufC提供的一套ReAllocL()方法,它可以用來擴展堆的緩存區(這個操作有可能會使緩衝區從一個內存區域搬到另一個區域)。

如果您在HBufC上調用Des()方法來獲取了TPtr, 在經過重新分配內存後,TPtr中的成員變量iPtr有可能變成無效的。因此,爲了確保安全,在重新分配內存後,應該再次調用Des()來創建一個新的TPtr對象。

注:出於性能上的考慮,Symbian系統並沒有提供可修改的堆描述符HBuf。

總結:Symbian系統中總共有5種類型的描述符,TPtrC,PTtr,TBufC<n>,TBuf<n>和HBufC。下面的圖示表明了它們的繼承關係。

 

3.7字面量描述符(Literal Descriptors)

下面我們來看看字面量描述符,它相當於C語言中的static char[]。字面量描述符是通過一系列的宏來創建的,這些宏可在頭文件e32def.H中找到

#define _L8(a) (TPtrC8((const TText8 *)(a)))

#define _S8(a) ((const TText8 *)a)

#define _LIT8(name,s) const static TLitC8<sizeof(s)>

name ={sizeof(s)-1,s}

#define _L16(a) (TPtrC16((const TText16 *)L ## a))

#define _S16(a) ((const TText16 *)L ## a)

#define _LIT16(name,s) const static TLitC16<sizeof(L##s)/2>

name ={sizeof(L##s)/2-1,L##s}

首先,我們來看_LIT,這是最有效率也是被使用得最多的一個。這個宏的用法如下:

_LIT(KMyLiteralDescriptor, "The quick brown fox jumps over the lazy dog");

後面KMyLiteralDescriptor就可以作爲一個常量來使用,例如可以將它寫到文件或顯示給用戶。_LIT 宏構建了一個名爲KMyLiteralDescriptor的TLitC16對象,其中保存了字符串的值(在這個例子中是The quick brown fox jumps over the lazy dog),在二進制程序中可以找到這個值,因爲它是被寫到文件中的。如您所料,_LIT8和_LIT16的用法相似。因爲描述符的寬度爲16bit,所以,在將C字節類型的字符串轉換爲描述符能用的數據時,宏將字符串的長度除以2。

作爲參考,下面給出類TLitC16的定義,其中__TText被定義爲寬的,16bit的字符。TLitC8

也有類似的定義。

template <TInt S>

class TLitC16

{

public:

inline const TDesC16* operator&() const;

inline operator const TDesC16&() const;

inline const TDesC16& operator()() const;

... // Omitted for clarity

public:

TUint iTypeLength;

__TText iBuf[__Align16(S)];

};

template <TInt S>

inline const TDesC16* TLitC16<S>::operator&() const

{return REINTERPRET_CAST(const TDesC16*,this);}

template <TInt S>

inline const TDesC16& TLitC16<S>::operator()() const

{return *operator&();}

template <TInt S>

inline TLitC16<S>::operator const TDesC16&() const

{return *operator&();}

從上面的定義中可以看到, TLitC16 (和TLitC8) 並不從TDesC8 或 TDesC16派生,但是它們與TBufC8 或TBufC16具有相同的內存佈局。這就使得TLitC16 (和TLitC8)可以用在任何可以使用TDesC的地方。您也可以用如下的方法從一個字面量構造一個指針描述符:

TPtrC8 thePtr(KMyLiteralDescriptor);

從字面量構造緩衝區描述符需要一點小技巧。如果您用size()去獲得_LIT常量,它會返回相應的TLitC對象的尺寸大小,這個尺寸相當於描述符內容的尺寸加上額外的8個byte(用來存放長度值的4字節和表示結束符的NULL)。如果您想用它來構造基於堆的描述符,必須要將這額外的8個字節考慮進去。

// 定義一個包含44字符的字面量

_LIT8(KExampleLit8, "The quick brown fox jumped over the lazy dog");

TInt size = sizeof(KExampleLit8); // 52 bytes (contents + 8 bytes)

TBufC8<(sizeof(KExampleLit8)-8)> theStackBuffer(KExampleLit8);

對基於堆的描述符,您可以用描述符實際內容的長度來分配緩衝區,然後將內容拷貝到描述符中。爲了得到正確的長度,您可以用公共(public)的成員變量iTypeLength,或者,也可以用更簡單的方法,使用()操作符來將字面量轉換成一個描述符,然後用這個得到的描述符來得到內容的長度。但最簡單的方法是,使用()操作符將對象轉換成描述符後,直接調用TDes::AllocL()方法,返回一個HBufC*,代碼如下:

TInt descriptorLength = KExampleLit8.iTypeLength; // 44 bytes

// Form a stack buffer descriptor around the literal

// Create a heap buffer copying the contents of the literal

HBufC8* theHeapBuffer = KExampleLit8().AllocL();

// 對寬字符字面量的操作類似

_LIT16(KExampleLit16, "The quick brown fox jumped over the lazy dog");

size = sizeof(KExampleLit16);// 96 bytes (contents in bytes + 8 bytes)

descriptorLength = KExampleLit16.iTypeLength; // 44 bytes (contents)

用_L 和 _LIT生成的字面量,它們的內存佈局是有差異的,如下圖所示:

 

現在我們簡單地看看 _L 和 _S 宏, 這兩個宏已經過時, 但在測試代碼中還經常用到。

RDebug::Print(_L("Hello world!"));

這個代碼的作用相當於:

_LIT(KLit,"Hello world!");

RDebug::Print(KLit);

從上面的代碼可以看到,使用_L的好處在於,您可以直接使用它,而無需在使用之前,在別的地方聲明。字符串(”Hello world!”)被作爲一個基本的以0結尾的字符串寫到二進制文件中,它前面沒有長度值(這不同於_LIT產生的字符串)。由於沒有長度值,字 面量的內存佈局不同於描述符,並且當代碼運行的時候,_L的第個實例都會產生一個臨時的TPtrC,這個TPtrC的指針指向字面量的第一個字節在ROM 中的存儲位置。只要是在創建該字面量的生存期中使用這個臨時的描述符,這都是安全的。然而,創建臨時變量要求設置指針、長度和描述符的類型,這對內聯的構 造方法來說是一個負擔,如果代碼中有很多這樣的字面量,也會使得二進制程序的體積增大。如果僅從存儲方式上看,_S 宏和_L是相同的, 但有一點不同------它不產生臨時的TPtrC描述符。如果您僅將它作爲以0結尾的描述符使用,那麼就使用_S宏。

到目前爲止,我們已經討論了關於描述符的基本知識,包括如何實例化每一種具體的描述符,如何訪問和修改描述符的數據,以及如何置換描述符的內容。現在我們來關注一下操作數據的方法和在使用描述符時一些常見的問題。

3.8描述符作參數和返回類型

在編寫代碼的時 候,您可能不想被限制於只能使用TBuf,原因是僅僅因爲某個特定的庫函數要求使用它。同樣的道理,作爲函數的提供者,您可能對調用者傳遞進來的參數類型 不感興趣。事實上,您不應該要求調用者傳遞特定類型的參數,因爲您可能在後面要修改函數的實現,您可能要改變描述符的類型,如果您將這樣的函數作爲編程接 口,最後您不得不讓您的客戶也改變他們的代碼。這樣的改動是非常不理想的,因爲它破壞了代碼的兼容性。

除非您來掌管描述符(負責描述符的創建和銷燬工作), 您甚至可以不用知道描述符是基於堆的還是基於棧的。事實上,只要標準類型的描述符(我們前面提到的5種描述符類型之一),就可以在它上面調用適當的方法, 客戶代碼完全可以忽略描述符的內存佈局和它在內存中的位置。基於以上的原因,當您定義函數的時候,應當儘量使用抽象的基類作爲函數的參數和返回值。爲了有 效率,描述符參數應當使用引用傳遞的方式,要麼是const TDesC&或者是TDes&。

例如,類RFile定義了read()和write()方法

IMPORT_C TInt Write(const TDesC8& aDes);

IMPORT_C TInt Read(TDes8& aDes) const;

在這兩個方法中,輸入的描述符被顯式地聲明爲8bit的寬度,這樣可以既寫入字符串,也可以寫入二進制數據。被 用來寫入到文件中的參數是對一個不可修改的描述符的引用,而在讀文件的時候,使用了可修改的描述符的引用。可修改描述符的最大長度決定了可以從文件中讀入 多少數據,所以不需要再給文件服務器傳遞一個表示長度的參數。文件服務器將會填充滿描述符。當文件中的數據不夠描述符的最大長度時,文件服務器會把所有可 得的數據寫入描述符。調用函數後,描述符的長度反映了寫入數據的長度。這樣,調用者也無需再另外傳遞一個參數用來表示返回的數據長度。

當寫一個函數的時候, 如果參數是可修改的描述符,實際上您不必考慮它是否有足夠的空間用來存放數據,因爲描述符本身有邊界檢查的機制,如果出現了內存溢出現象,會產生系統異常。

當然,您也可能不希望在描述符數據區過短的情況下,描述符的方法會發生系統異常。這時,您應當在文檔中說明,如果描述符的長度不夠將會如何處理。有時候,一個比較好的方法是,給調用者返回一個長度值,這樣,調用者可以採用適當的步驟來分配一個正確長度的描述符。

HBufC* CPoem::DoGetLineL(TInt aLineNumber)

{// Code omitted for clarity. Allocates and returns a heap buffer

// containing the text of aLineNumber (leaves if aLineNumber is

// out of range)

}

void CPoem::GetLineL(TInt aLineNumber, TDes& aDes)

{

HBufC* line = DoGetLineL(aLineNumber);

CleanupStack::PushL(line);

// Is the descriptor large enough (4 bytes or more) to return an

// integer representing the length of data required?

if (aDes.MaxLength() < line->Length())

{

if (aDes.MaxLength() >= sizeof(TInt))

{// Writes the length required (TPckg is described later)

TPckg<TInt> length(line->Length());

aDes.Copy(length);

}

// Leave & indicate that the current length is too short

User::Leave(KErrOverflow); // Leaves are described in Chapter 2

}

else

{

aDes.Copy(*line);

CleanupStack::PopAndDestroy(line);

}

}

另一個方案是,在函數中分配堆緩衝區,把它返還給調用者,由調用者負責銷燬它。

3.9常用的方法

? Ptr()

基類TDesC 實現了Ptr()方法,用來訪問描述符的數據,該方法返回一個指向字符數組首地址的指針。您可以通過這個指針來直接操作字符串數據。 代碼如下所示:

? Size() 和 Length()

TDesC 實現了 Size() and Length() 方法, 前者返回描述符所佔有的字節數,而後者返回的是描述符的字符長度。對8bit的描述符來講,它們是相等的,而對16bit的描述來說,Size() 返回的數值是 Length() 的兩倍。

? MaxLength() 

可修改的描述符TDes實現的這個方法返回描述符的最大長度。

? SetLength()和SetMax()

前者用來設置描述符的長度,這個長度值必須是小於描述符的最大長度的,否則會引起系統異常。後者將描述符的當前長度設置成最大值,注意,它不並不能擴展描述符數據區的長度。

? Zero()和FillZ()

前者將描述符的長度設置爲0,而後者是用0來來填充描述符的內容置。如果您要用其它字符填充描述符的內容,可用Fill()方法。這個方案類似於C語言中的memset()函數。

? Copy()

TDes 實現了一系列的重的Copy() 方法, 下面是其中的兩個:

IMPORT_C void Copy(const TDesC8 &aDes);

IMPORT_C void Copy(const TDesC16 &aDes);

這些方法將參數描述符中的數據拷貝到目標描述符中,同時爲目標描述符設置新的長度。如可源描述符的長度超過目標描述符的最大長度,將會引發一個系統異常。

3.10 使用HBufC 堆描述符

我們已經討論過描述符的一些特性,現在來關注一下使用描述符時經常容易範的錯誤。

 首先,我們將創建和使用堆描述符HBufC。前面提到過,在已有的描述符上調用Alloc()或AllocL()方法,可以產生一個新的HBufC。這裏是一個例子:

void CSampleClass::UnnecessaryCodeL(const TDesC& aDes)

{

iHeapBuffer = HBufC::NewL(aDes.Length());

TPtr ptr(iHeapBuffer->Des());

ptr.Copy(aDes);

...

// 以上代碼完全可以被下面的代替,下面代碼更有效率。

iHeapBuffer = aDes.AllocL();

}

Another common way to introduce complexity occurs in the opposite

direction, that is, the generation of TDesC& from a heap descriptor. 

當從一個堆描述符產生一個TDesC&的時候,也容易範一個錯誤,這個錯誤同樣爲代碼增加了複雜性。代碼如下所示:

const TDesC& CSampleClass::MoreAccidentalComplexity()

{

return (iHeapBuffer->Des());

// 以上代碼完全可以寫成

return (*iHeapBuffer);  //這樣更簡潔高效

}

另外一個比較微妙問題是,當您分配一個HBufC以後,然後在它上面調用Des(),可以返回一個TPtr對象。

HBufC* buf = HBufC::NewL(9);

TPtr p = buf->Des();

可是,假如您回憶一下,可以知道在HBufC中,並沒有一個字(word)用來保存最大長度的信息------因爲HBufC是不可修改的(non-modifiable),它不需要最大長度的信息。然而,,TPtr需要這個最大長度的信息,這時問題來了,您從哪裏得到這個最大長度呢?答案在於:當您調用Des()的時候,系統用HBufC的最大長度來設置TPtr的最大長度(iMaxLength)。

在這個例子中,buf的最大長度是多少呢?它是9嗎?答案是不一定。堆描述符的最大長度有可能並不是您所期望的值(在這個例子中爲9)。這是由於您並沒有指定一個字對齊(word-aligned)的最大長度,所以最後的實際的長度可能比您所指定的長度要大一些(但我們不能確定這個值到底是多少)。

_LIT(KPanic, "TestPointer");

const TInt KBufferLength = 9;

void TestPointer()

{ // Create a buffer with length KBufferLength = 9 bytes

HBufC8* myBuffer = HBufC8::NewMaxL(KBufferLength);

TPtr8 myPtr(myBuffer->Des());

TInt len = myPtr.Length();    //len的值爲0

TInt maxLen = myPtr.MaxLength();  //得到一個比KBufferLength稍大數,並不固定

myPtr.SetLength(KBufferLength); //或myPtr.SetMax();否則下面的語句不起作用

myPtr.Fill(’?’); // 用’?’填充描述符

char* ptr = (char*)myPtr.Ptr();//確保已經調用了SetLength()或SetMax()方法。

ptr[0] = 'x'; 

 

HBufC8* buf = HBufC8::NewLC(9);

TPtr8 ptr(buf->Des());

TInt maxLength = ptr.MaxLength(); // maxLength比9大,但不一定等於12(字的邊界)

3.11 TFileName的濫用

對TFileName 對象的濫用是一個潛在的危險。TFileName是在文件 e32std.H中定義的:

const TInt KMaxFileName=0x100; // = 256 (decimal)

typedef TBuf<KMaxFileName> TFileName;

由於每個寬字符相當於兩個字節(前面說過,默認情況下,TBuf是16bit寬度的), 所以,無論文件名的長度爲多少,每次您在棧上創建一個TFileName 對象的時候都消耗了524 個字節 (2 × 256 描述符數據字節 + 描述符本身的12字節)。在Symbian OS系統中,標準的棧空間的大小爲8K字節,不必要地使用有限的資源是非常浪費的,所以儘量不要使用基於棧的TFileName 對象,也不要用值傳遞的方式使用它們,應當使用引用傳遞的方式。您可以在堆上使用它們,比如,您可以把它們作爲C類(從CBase派生的類)的成員變量。如果,您不需要使用完整的路徑,你也可以用HBufC來存放文件名,儘量少用資源總是好的。

您最好不要使用TParse 類(在f32file.H中定義)。因爲這個類保存了一個描述符的拷貝,在拷貝中包含了要解析的文件名,拷貝會用掉寶貴的棧空間。您應當考慮使用TParsePtr 和 TParsePtrC 類;它們提供了同樣的功能,但它們不拷貝文件名,而僅僅保存對文件名的引用。

4有用的輔助類

在討論了這些普遍的問題之後,我們在這一章的結尾來看看兩個常用的輔助類。

4.1 TLex(TLex8,TLex16)類

像描述符一樣,TLex也有8bit和16bit兩種版本,分別是TLex8 and TLex16,一般情況下,你應當使用TLex,而無需指定特定的版本。該類實現了一般目的詞法分析,和語法成分解析以及從字符串到數字轉換的功能。

4.2 Pckg類

另外一套有用的輔助類分別是:TPckgBuf和TPckg以及TPckgC,它們是分別派生自TBuf<n>, TPtr 和 TPtrC的模板類,在文件e32std.H中可以找到它們的定義。

打包類(package class)能很方便地將扁平的(flat)數據對象存儲到描述符中,這在跨線程或跨進程的數據共享的時候很有用。 實際上,能將一個T類對象完整地打包到描述符中,這樣很容易以一種類型安全的方式在線程間共享它。

有兩種Package指針類:TPckg,TPckgC,它們分別對應於可修改和不可修改的類型,都持有一個指向被包裝對象的指針。

class TSample

{

 public:

 void SampleFunction();

 void ConstantSampleFunction() const;

 private:

 TInt iSampleData;

};

TSample theSample;

TPckg<TSample> packagePtr(theSample);

TPckgC<TSample> packagePtrC(theSample);

在這個例子中,TPckg<TSample>表示這是一個模板類,模板參數爲TSample,packagePtr(theSample)定義了一個對象,它持有一個指向theSample的針指;可以在

包對象上調用()方法,返回被包裝對象的引用。代碼如下:

packagePtr().SampleFunction(); //合法

packagePtrC().SampleFunction();//編譯錯誤!只能調用const方法

packagePtrC().ConstantSampleFunction();//合法

TPckgBuf類創建並存儲一個新的被包裝類型的實例(注意,是窗建新的實例而不是保存指針) ,TPckgBuf自己管理這個拷貝的對象;在TPckgBuf對象上調用()方法,可以返回對拷貝的引用,然後可以在這個引用上調用其它的方法。這個TPckgBuf對象所擁有的拷貝可以被修改。代碼如下:

TPckgBuf<TSample> packageBuf(theSample);

packageBuf().SampleFunction();

由於TPckgBuf擁有原始數據的拷貝,所以,如果在上面調用了可修改成員變量的方法,那麼被修改的只是拷貝的數據,而原來的數據不受影響(這類似於函數調用時的值傳遞方式)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章