解析 XML 这事儿,说难不难,但真要在 C++ 里用 TinyXML2 稳稳当当地跑起来,很多人一上来就踩坑。不是加载完文件直接拿指针解引用崩了,就是查节点时死活找不到想要的数据,搞到最后连中文都乱码。其实核心就几个关键点:加载必须检查返回值,节点操作前必须判空,取属性或文本时不能默认它一定存在,多线程下各自用各自的实例别共享。把这些记牢了,绝大多数崩溃都能避免。

很多人以为调完 LoadFile() 就能直接拿 FirstChildElement() 开干,但加载失败时它照样返回 nullptr,接着解引用就是段错误。所以第一步必须把返回值校验焊死。
怎么加载 XML 文件并确认它真能用
别调完 LoadFile() 就直接 FirstChildElement()——加载失败时它照样返回 nullptr,接着解引用就段错误。
LoadFile()返回tinyxml2::XMLError,只有等于tinyxml2::XML_SUCCESS才算成功- 失败时立刻用
doc.ErrorStr()和doc.ErrorID()看具体原因,比如"Failed to open file"(路径错)、"XML parse error at line 12"(格式非法) - 文件必须是 UTF-8 编码(无 BOM),Windows 上用记事本另存为“UTF-8”而非“UTF-8-BOM”,否则中文节点名(如
<用户>)会查不到 - 从字符串解析用
Parse(),传入的const char*必须以\0结尾,且不能是临时std::string::c_str()(生命周期短)
怎么安全地查找和遍历子节点
FirstChildElement("tag") 不是“找一个叫 tag 的子节点”,而是“从第一个子节点开始,往后找第一个类型为 ELEMENT 且名字匹配的节点”——中间夹着注释、空白文本、CDATA,它都会跳过。
- 永远把结果赋给指针再判空:
auto* elem = root->FirstChildElement("config"); if (!elem) return; - 多个同名子节点(如
)必须循环:for (auto* item = root->FirstChildElement("item"); item; item = item->NextSiblingElement("item")) { ... } - 别依赖
FirstChildElement("xxx")非空就认为结构存在——它只找第一个,找不到就返回nullptr,不会报错也不会继续找 - 若 XML 中有换行缩进,
FirstChild()可能拿到的是XMLText节点(含空白),不是XMLElement;需要跳过非 ELEMENT 节点,可用IterateChildren()或手动判断n->ToElement()
怎么取属性和文本内容才不崩
Attribute() 和 GetText() 都可能返回 nullptr,直接传给 printf 或 std::string 构造函数就是未定义行为。
Attribute("name")返回const char*,为空时是nullptr,建议写成:std::string name = elem->Attribute("name") ? elem->Attribute("name") : "";GetText()只取直接子文本(不含子元素),若节点有子元素(如),它返回Tom nullptr;此时得用FirstChild()->ToText()->Value()- 数值转换别硬转:
QueryIntAttribute("id", &id)失败时返回非XML_SUCCESS,必须检查:if (elem->QueryIntAttribute("id", &id) != tinyxml2::XML_SUCCESS) id = 1; QueryIntAttribute()对" 42"、"42.0"、"0x2A"全部失败;想兼容,先用Attribute()拿原始字符串,再用std::stoi等手动解析
保存 XML 后中文乱码怎么办
不是 TinyXML2 出了问题,是 Windows 记事本默认用 ANSI 解析纯 UTF-8 字节流——它没 BOM,就认不出来。
Sa veFile()输出纯 UTF-8,无 BOM,这是标准行为,不是 bug- VS Code、Notepad++ 能正常显示;若业务上必须让记事本打开不乱码,得自己加 BOM:用二进制方式打开文件,先写入
0xEF 0xBB 0xBF,再调Sa veFile()到同一句柄(需绕过 API,重写XMLPrinter或用fwrite手动拼接) - 不要试图在
XMLDocument内部改编码设置——它不提供编码参数,所有文本都按 UTF-8 处理 - 多线程下每个线程必须用独立的
XMLDocument实例,它内部有缓存和节点链表,非线程安全
最常被忽略的其实是两件事:一是 LoadFile() 后不看返回码,二是把 GetText() 当作“一定有值”来用。这两处一漏,程序跑两天突然 crash,查半天才发现 XML 文件少了一行空格。