最近要用到XML文件,网络上也有很多处理XML文件的方法,但最终还是选择了TinyXML,因为它的小巧。在网络了查找了很多相关的资料,记录下来。
TinyXML文件的基础使用比较简单,网络上也有很多相关的例子和教程,在这里给出链接(个人认为比较好的),大家可以参考(参考中也附有参考)。
TinyXML文件的处理也有一些值得注意的地方,在这里也记录下来:
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.尽量不要在文档中使用中文.
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标记字节, 虽然它不是标准, 但普遍使用).当然还是那句话 ---- 尽量不使用中文和其它非英文字符, 除非迫不得已.