使用TinyXml庫值得注意的幾個地方

來源:互聯網
上載者:User

      這兩天仔細看了下TinyXml的原始碼, 完美地搞清楚了一些網友和我自己的很多疑問. 鑒於TinyXml的實用性, 而且現在不少人在使用, 就決定在此做點有意義的事情 ---- 列出使用TinyXml庫值得注意的幾個地方.

     關於TinyXml庫的介紹網上有很多資料, 大家可以試著搜下, 這裡我就不多說了, TinyXml很小巧, 但它提供了非常豐富的介面, 特別適用於存取程式的資料, 如果你使用它, 相信你會感覺到它的靈活的.

     TinyXml: http://download.csdn.net/detail/hoyt00/3904805

 

 

     以下幾點值得注意哦:

 

1.new出指標而不見delete.

     如果你看了TinyXml的文檔或者一些網友寫的例子程式, 你會發現,
其中new出了很多個物件而不見一個delete, 這個問題我當時也感覺特別難以接受, 網上也有各種各樣的說法, 有說"nwe出這麼多, 記憶體不泄漏才怪", 有說"TinyXml的指標有自銷毀功能"等等, 這些說法都是錯誤的, 只要你正確的使用TinyXml, 無論你new多少(當然在記憶體的有效使用範圍之內)都不會有記憶體流失, 而且TinyXml的指標根本沒有自銷毀功能, 可以想像一下, 在C++中一個沒有經過任何類的封裝的指向堆記憶體的指標變數怎麼可能在它自身的生命結束時管得了它所指向的對象? 那它到底是怎麼回事?
     我們都知道(這裡我假設你已經看過TinyXml文檔), TinyXml是利用DOM(文件物件模型)來呈現XML文檔的, 在它眼中XML文檔就是一棵樹, TiXmlDocument對象就是這棵樹的根結點, 在一個完整的文檔中, 除了它, 其餘結點必須都是它的後代, 所以TinyXml用了一個很巧妙的方法來析構每一個結點所對應的對象
---- 每個結點的析構任務都委託給了它的父親, 這樣只要保證父親被正確析構, 或者調用了父親的Clear函數, 它的所有後代都會被正確的析構, 所以對整個文檔來說只要TiXmlDocument對象被正確析構, 那就萬無一失了, 這棵樹會先從葉子銷毀, 一直到樹根. 像這種資料結構, 為了保證樹的完整性而使用堆記憶體, 和由它自己管理記憶體也是理所當然的, 由此便可得到以下幾個結論:
     (1)TiXmlDocument對象最好在棧上建立, 如果在堆上建立了, 那你必須得自己銷毀它, 千萬不要像別的對象一樣new出了就不管了.
     (2)除了TiXmlDocument對象, 樹中的別的結點對象, 必須是堆上建立的, 千萬不要把棧上對象的地址連結(LinkEndChild)到樹中, 因為棧上對象是不能用delete銷毀的, 當然TinyXml也有對棧上對象插入的方法, 以下會說到.
     (3)除了文檔結點, new出的所有結點對象必須被連結到一個父親結點上, 才可以不用管對象的delete, 而且必須不能管, 不然有可能整棵樹會被破壞而得不到正確的遍曆和析構, 如果new出的對象從來沒連結到某棵樹上, 而且將來也不打算連結, 無論有沒有別的結點連結到它身上, 你都必須手動delete它.
     (4)不要嘗試連結已經被別的結點連結過的指標, 很顯然, 這會造成無法估量的麻煩, 不用多說, 大家都懂的.

 

2.Insert vs. Link.

     當然不是所有人都喜歡new, 可能他們更喜歡像std::vector那樣插入元素的副本而不是交給它一個指標, TinyXml也提供了同樣的方式插入結點 ---- Insert函數(InsertEndChild,
InsertBeforeChild, InsertAfterChild), 因為這些函數插入的是結點的副本(包括所有子結點) ,所以可以傳給它棧上或堆上的對象, 但是要注意, 如果傳入的是堆上對象, 那還必須手動delete它, 而不像LinkEndChild那樣連結了就不能管了, 因為樹析構的時候析構的是它的副本, 而不是它. 我更傾向於使用Link + 堆上對象, 而不Insert + 棧上對象, 因為這樣可以省去棄置站台的運行成本, 除非要插入到具體的位置, 而不是End, 當然也可以修改原始碼使Link函數也有這種功能.

 

3.元素的屬性不是普通結點.

     與文字物件(TiXmlText)不同,
屬性對象(TiXmlAttribute)在文檔樹中不是以普通結點形式存在的, 也不從TiXmlNode類派生, 而是被元素結點的資料成員 ---- 一個TiXmlAttributeSet對象所管理, 這個TiXmlAttributeSet對象持有一個環狀的TiXmlAttribute對象鏈表(具體可以參看原始碼), 所以TiXmlElement對象通過調用Child系列函數是無法得到屬性的, 相反這件事是通過Attribute系列函數完成的. 訪問器(從TiXmlVisitor派生的使用者實現的類對象, 一種附加的回調機制,
具體參看原始碼)沒有針對TiXmlAttribute對象的介面, 而必須在對TiXmlElement對象的實現中處理屬性, 因此TiXmlAttribute類也沒有Accept虛函數.

 

4.盡量不要在xml中使用中文.

     TinyXml只認識UTF-8和ISO
8859-1編碼, 而不知GB2312為何物, 但事實上你以GB2312在文檔中寫入中文, 之後可以正確讀取, 而且文檔在記事本中開啟也能顯示正確的中文, 其實這是種巧合, 並不是TinyXml支援GB2312了.
     這個問題需要解釋下,
我已經仔細分析過了, TinyXml的函數有char*型別參數, 而沒有wchar_t*型別參數, 所以直接在程式中向文檔寫入中文必然是GB2312方式(這裡是以VC編譯器為例的), 這時char*只是指向一塊記憶體塊, 跟void*一樣, 這塊記憶體只有用GB2312才能正確解釋為中文, 因為TinyXml是被設計跨平台的, 所以不要指望它會調用WideCharToMultiByte和MultiByteToWideChar來幫你做轉換, 而以GB2312寫入的中文在讀取時這些中文字元的碼值是不變的, 也就是你在準備寫入文檔時是碼值是多少,
讀取到程式裡的值就是多少, 而把這個值當作GB2312編碼時就是原來寫文檔時的中文字元了, 當把寫入的文檔在記事本開啟時, 由於沒有utf-8標記位元組0xEF 0xBB 0xBF(TinyXml預設不寫入這三個位元組, 稍後再說怎麼讓它寫入), 所以記事本把xml檔案當作GB2312編碼開啟, 就陰差陽錯地把原本錯誤的位元組以正確的中文字元顯示了, 但終究這篇xml文檔是utf-8編碼的, 那些中文字元本應顯示為亂碼的, 就這樣錯誤的寫入, 錯誤的讀取, 記事本錯誤的判斷, 都加到一起就離奇地沒有錯誤了.
     但錯誤還是錯誤,
有一個辦法, 就是在文檔頭部加上utf-8標記位元組0xEF 0xBB 0xBF, 這樣記事本就能正確判斷文檔編碼, 正確地以utf-8開啟, 正確地把中文顯示為亂碼.
     基於這幾種錯誤疊加的現象,
如果你產生的xml文檔只用在你自己特定的程式中 而不用在其它軟體(比如有時要用別的文本處理軟體開啟查看內容), 那麼在文檔中存取中文是完全沒有問題的, 但在別的地方以utf-8開啟時中文就是亂碼.
     保證所有地方都正確的方法是在寫入時和讀取時用WideCharToMultiByte和MultiByteToWideChar把GB2312編碼的中文字串轉換為UTF-8編碼的中文字串,
如此, 所有軟體都能正確的讀取UTF-8編碼的中文字元(為了讓記事本正確的判斷為UTF-8, 可以加上utf-8標記位元組, 雖然它不是標準, 但普遍使用).當然還是那句話 ---- 盡量不使用中文和其它非英文字元, 除非迫不得已.

 

 

另外我在看原始碼時,
有幾個地方也發表下自己的想法:

 1.關於explicit關鍵字

     tinystr.h第85行 
TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0)

     (TIXML_EXPLICIT宏的內容是explicit)
explicit關鍵字的作用是使單參數的建構函式不用作隱式轉換, 但這個建構函式有兩個參數, 很顯然, 它根本就沒有發生隱式轉換的可能, 也完全沒有必要使用explicit關鍵字.

2.關於成員函數const和非const

     TinyXml的類庫中有許多類的函數有const和非const版本,
為什麼非const版本的函數不把函數體重新寫一遍而是用const_cast把const函數的const性質轉換掉再調用它呢? 其實函數體也就返回資料成員的那一兩句而已.

3.關於預設實參

     有些類的重載成員函數們,
一個函數的參數表和另一個函數的參數表的前面部分完全一致, 為什麼不使用預設實參而是參數較少的函數版本調用參數較多的函數版本呢?

 

4.關於Test項目中的亂碼

     這也是很多人都疑惑的一個問題
---- TinyXml庫內建Test項目竟然編譯不過, 這個問題的原因很簡單, 開啟xmltest.cpp檔案, 定位到第1141行, 你會發現, 該行和下面幾行有亂碼, 這又是怎麼回事?

     其實這幾行是作者為測試ISO
8859-1編碼(一種單位元組編碼, 編碼範圍使用了8位的所有取值, 也就是0到0xFF)文檔而寫, 意圖把ISO 8859-1字串寫入文檔對象, 再從文檔對象讀取, 只有用ISO 8859-1編碼才能正確解釋.

     但是編譯器以GB2312開啟cpp檔案時遇到這幾個字元就發生了奇怪的事情
---- 源碼變亂碼了. 本來亂碼沒什麼的, 大不了我輸出到xml檔案也亂碼就是了, 但問題是那幾個字元不僅使它本身亂, 而且它後面的一個asc2字元也有50%的幾率跟著遭殃, 因為我們都知道用GB2312解碼字串時如果第一個位元組大於0x7F, 一般情況下會把它和接下來的一個位元組當作一個整體字元, 當然這個字元可能顯示, 也可能不顯示, 無論怎樣都不是正確的, 所以你會看到那些字串中有用於xml元素的"<"符號而沒有">"符號, 有的字串甚至都沒反引號, 這時不要簡單的加上反引號, 雖然文法正確了, 能編譯,
但xml文法還沒糾正, 調試時會導致文檔對象裡的宣告失敗而退出. 另外我還查看了這幾個亂碼字元的二進位值:

第1141行 "<?"
二進位值 22 3C E4 3E 22 0A
GB2312 "<(\xE4\x3E)"
ISO 8859-1 "<ä>"

 

第1142行 "C鰊t鋘t咪鰉鬧?
二進位值 22 43 F6 6E 74 E4 6E 74 DF E4 F6 FC C4 D6 DC 22 0A
GB2312 "C(\xF6\x6E)t(\xE4\x6E)t(\xDF\xE4)(\xF6\xFC)(\xC4\xD6)(\xDC\x22)
ISO 8859-1                                                "CöntäntßäöüÄÖÜ"

 

第1143行 "</?";
二進位值 22 3C 2F E4 3E 22 3B 0A
GB2312 "</(\xE4\x3E)";
ISO 8859-1       "</ä>";

其中二進位值省略了行首的空白字元, GB2312的括弧中兩個位元組表示被當作的一個整體,ISO 8859-1表示原本用ISO
8859-1編碼時顯示的字串. 可以看出以ISO 8859-1編碼的文本是完全符合C++文法和xml文法的, 大家在產生項目時將這塊代碼注釋掉就能通過編譯和調試了.

     還有剛才提到TinyXml儲存文檔時預設不寫入utf-8標記位元組BOM,
如果想讓它儲存時寫入這三個位元組, 可以修改TiXmlDocument類添加一個成員:

public:    void SetMicrosoftBOM(bool _useBOM) {useMicrosoftBOM = _useBOM;}

當然也可以修改別的成員函數, 比如給SaveFile函數添加一個bool參數_useBOM等等, 看個人愛好了.

     關於TinyXml的使用就先寫到這了, 如果有不對的地方, 希望大家指出, 如果有不明白的地方也可以聯絡我!

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.