纯C++实现ini文件操作

📅 2026/7/7 3:20:22
纯C++实现ini文件操作
纯C实现ini文件操作(可以自从此处下载 https://download.csdn.net/download/huangjiazhi_/93079858头文件#ifndef INIRW_H #define INIRW_H #include string #include map #include vector /** * brief INI配置文件读写类 * 特性 * 1. 行首 # 为整行注释空行、注释保存时原样保留 * 2. 支持读取 string / int / double / bool * 3. 支持修改、新增分段与键值保存不破坏原有注释排版 * 4. 提供格式合法性校验函数输出详细错误行号与原因 * 5. 纯标准C仅依赖STL无第三方库跨平台 */ /// 存储文件原始单行数据区分注释行与配置行 struct IniLine { bool isComment; /// true空行/#注释行false[Section]或keyvalue配置行 std::string rawText; /// 该行原始完整文本 }; class IniRW { public: /** * brief 构造函数自动加载指定INI文件 * param filePath INI文件路径 */ explicit IniRW(const std::string filePath); /// 析构默认 ~IniRW() default; /** * brief 从磁盘重新加载INI文件清空原有缓存 * return 加载成功返回true文件不存在/打开失败返回false */ bool load(); /** * brief 将内存中修改后的配置写回原文件保留全部原始注释与空行 * return 写入成功true文件无法打开返回false */ bool save(); /** * brief 校验当前加载的INI文件格式是否合法 * 校验规则 * 1. [分段]必须成对括号段名不可为空 * 2. 配置行必须包含分隔符键名不可为空 * 3. 所有keyvalue必须归属在某个[Section]下禁止裸键值 * 4. 纯注释/空文件视为合法 * return 格式合法true存在语法错误false */ bool checkValid() const; /** * brief 获取checkValid校验失败的错误描述 * return 错误字符串包含出错行号原因校验通过返回空字符串 */ std::string getErrorMsg() const; /** * brief 判断指定分段是否存在 * param sec 分段名称 * return 存在true不存在false */ bool hasSection(const std::string sec) const; /** * brief 判断指定分段下某个键是否存在 * param sec 分段名 * param key 键名 * return 存在true不存在false */ bool hasKey(const std::string sec, const std::string key) const; // 读取接口 /** * brief 读取字符串类型配置 * param sec 分段名 * param key 键名 * param def 不存在分段/键时返回的默认值 * return 配置值字符串 */ std::string getString(const std::string sec, const std::string key, const std::string def ); /** * brief 读取整型配置 * param sec 分段名 * param key 键名 * param def 默认值 * return 解析后整数解析失败/不存在返回def */ int getInt(const std::string sec, const std::string key, int def 0); /** * brief 读取浮点型配置 * param sec 分段名 * param key 键名 * param def 默认值 * return 解析后浮点数解析失败/不存在返回def */ double getDouble(const std::string sec, const std::string key, double def 0.0); /** * brief 读取布尔配置 * 识别真值1 / true / yes假值0 / false / no * param sec 分段名 * param key 键名 * param def 默认值 * return 布尔结果无法识别返回def */ bool getBool(const std::string sec, const std::string key, bool def false); // 修改/写入接口 /** * brief 设置字符串键值分段不存在自动创建仅更新已有key行不会追加新行 * param sec 目标分段 * param key 键名 * param val 待写入字符串值 */ void setString(const std::string sec, const std::string key, const std::string val); /** * brief 设置整型键值 */ void setInt(const std::string sec, const std::string key, int val); /** * brief 设置浮点键值 */ void setDouble(const std::string sec, const std::string key, double val); /** * brief 设置布尔键值文件中存储true/false文本 */ void setBool(const std::string sec, const std::string key, bool val); /** * brief 【新增接口】在已有分段末尾追加新键值对 * param sec 目标分段名 * param key 新增键名 * param val 键对应字符串值 * 说明setString仅更新原有行该接口会在对应分段尾部插入全新keyvalue行 * 若分段不存在则自动新建分段存放该键值 */ void appendKeyInSection(const std::string sec, const std::string key, const std::string val); private: /// 单分段内键值对存储 using SectionKV std::mapstd::string, std::string; std::mapstd::string, SectionKV m_data; /// 解析后的键值缓存 std::vectorIniLine m_lines; /// 文件原始全部行用于保存还原注释 std::string m_filePath; /// INI文件路径 mutable std::string m_errMsg; /// 校验错误信息mutable允许const函数修改 /** * brief 去除字符串首尾空白字符空格/制表符/换行 * param str 原始字符串 * return 修剪后字符串 */ static std::string trim(const std::string str); /** * brief 清空所有缓存数据、错误信息 */ void clearAll(); /** * brief 查找指定分段在原始行数组中的起始下标与结束下标 * param sec 分段名 * param outStart 输出分段[xxx]所在行下标 * return 分段下一个其他分段的下标无后续分段返回m_lines.size() */ size_t findSectionLineRange(const std::string sec, size_t outStart) const; }; #endif源文件#include IniRW.h #include fstream #include sstream #include algorithm #include cctype #include iomanip // 去除字符串首尾空白 std::string IniRW::trim(const std::string str) { if (str.empty()) return ; // 跳过开头空白 auto start str.begin(); while (start ! str.end() std::isspace(static_castunsigned char(*start))) start; // 跳过末尾空白 auto end str.end(); do --end; while (std::distance(start, end) 0 std::isspace(static_castunsigned char(*end))); return std::string(start, end 1); } // 清空所有缓存与错误信息 void IniRW::clearAll() { m_data.clear(); m_lines.clear(); m_errMsg.clear(); } // 构造自动加载文件 IniRW::IniRW(const std::string filePath) : m_filePath(filePath) { load(); } // 从磁盘加载并解析INI bool IniRW::load() { clearAll(); std::ifstream fin(m_filePath); if (!fin.is_open()) return false; std::string curSec; std::string lineBuf; while (std::getline(fin, lineBuf)) { IniLine lineItem; lineItem.rawText lineBuf; std::string trimLine trim(lineBuf); // 空行 / 行首#注释 标记为注释行 if (trimLine.empty() || trimLine.front() #) { lineItem.isComment true; m_lines.push_back(lineItem); continue; } // 配置/分段行 lineItem.isComment false; m_lines.push_back(lineItem); // 解析 [Section] if (trimLine.front() [ trimLine.back() ]) { curSec trim(trimLine.substr(1, trimLine.size() - 2)); continue; } // 解析 keyvalue size_t eqPos trimLine.find(); if (eqPos std::string::npos) continue; std::string key trim(trimLine.substr(0, eqPos)); std::string val trim(trimLine.substr(eqPos 1)); // 存在当前分段才存入缓存 if (!curSec.empty()) { m_data[curSec][key] val; } } fin.close(); return true; } // 查找分段起始与结束行下标 size_t IniRW::findSectionLineRange(const std::string sec, size_t outStart) const { outStart m_lines.size(); size_t secEndIdx m_lines.size(); bool targetSecFound false; std::string tempSecName; for (size_t i 0; i m_lines.size(); i) { const IniLine item m_lines[i]; if (item.isComment) continue; std::string s trim(item.rawText); if (s.front() [ s.back() ]) { tempSecName trim(s.substr(1, s.size() - 2)); // 已经找到目标分段遇到下一个分段则终止 if (targetSecFound) { secEndIdx i; break; } // 匹配目标分段记录起始行 if (tempSecName sec) { targetSecFound true; outStart i; } } } return secEndIdx; } // 保存内存配置回文件保留注释 bool IniRW::save() { std::ofstream fout(m_filePath, std::ios::trunc); if (!fout.is_open()) return false; std::vectorIniLine newLineList m_lines; // 遍历所有分段更新原有key、新增缺失分段 for (const auto secPair : m_data) { const std::string secName secPair.first; const SectionKV kvMap secPair.second; size_t secStart newLineList.size(); size_t secEnd findSectionLineRange(secName, secStart); // 场景1分段已存在更新分段内所有键值行 if (secStart ! newLineList.size()) { for (size_t i secStart 1; i secEnd; i) { IniLine lineItem newLineList[i]; if (lineItem.isComment) continue; std::string s trim(lineItem.rawText); size_t eq s.find(); if (eq std::string::npos) continue; std::string k trim(s.substr(0, eq)); // 缓存中存在该key则覆盖文本 if (kvMap.find(k) ! kvMap.end()) { lineItem.rawText k kvMap.at(k); } } // 新增代码追加分段内不存在的key std::mapstd::string, bool existKeys; // 收集当前分段原有所有key for (size_t i secStart 1; i secEnd; i) { const IniLine lineItem newLineList[i]; if (lineItem.isComment) continue; std::string s trim(lineItem.rawText); size_t eq s.find(); if (eq std::string::npos) continue; std::string k trim(s.substr(0, eq)); existKeys[k] true; } // 将缓存中不存在的key插入到分段末尾 for (const auto kv : kvMap) { const std::string k kv.first; const std::string v kv.second; if (!existKeys.count(k)) { IniLine newKvLine; newKvLine.isComment false; newKvLine.rawText k v; newLineList.insert(newLineList.begin() secEnd, newKvLine); secEnd 1; } } // } // 场景2不存在该分段追加全新[Section]与所有键值 else { IniLine secLine; secLine.isComment false; secLine.rawText [ secName ]; newLineList.push_back(secLine); for (const auto kv : kvMap) { IniLine kvLine; kvLine.isComment false; kvLine.rawText kv.first kv.second; newLineList.push_back(kvLine); } } } // 逐行写入文件 for (const auto item : newLineList) { fout item.rawText \n; } fout.close(); return true; } // 校验INI格式合法性 bool IniRW::checkValid() const { m_errMsg.clear(); std::string currentSection; for (size_t lineIdx 0; lineIdx m_lines.size(); lineIdx) { const IniLine item m_lines[lineIdx]; std::string trimLine trim(item.rawText); // 注释、空行无需校验 if (item.isComment) continue; // 处理分段行 [xxx] if (trimLine.front() [) { // 缺少右中括号 if (trimLine.back() ! ]) { m_errMsg 第 std::to_string(lineIdx 1) 行错误分段标记[无对应]; return false; } // 段名不能为空 std::string secName trim(trimLine.substr(1, trimLine.size() - 2)); if (secName.empty()) { m_errMsg 第 std::to_string(lineIdx 1) 行错误分段名称不能为空 []; return false; } currentSection secName; continue; } // 普通配置行必须存在等号 size_t eqPos trimLine.find(); if (eqPos std::string::npos) { m_errMsg 第 std::to_string(lineIdx 1) 行错误无效配置行缺少分隔符; return false; } // 键名不能为空 std::string key trim(trimLine.substr(0, eqPos)); if (key.empty()) { m_errMsg 第 std::to_string(lineIdx 1) 行错误键名不能为空; return false; } // 键值行不能在任何分段之外 if (currentSection.empty()) { m_errMsg 第 std::to_string(lineIdx 1) 行错误键值无归属分段必须写在[Section]下方; return false; } } // 全部校验通过 return true; } // 获取校验错误信息 std::string IniRW::getErrorMsg() const { return m_errMsg; } // 判断分段是否存在 bool IniRW::hasSection(const std::string sec) const { return m_data.find(sec) ! m_data.end(); } // 判断分段下键是否存在 bool IniRW::hasKey(const std::string sec, const std::string key) const { auto secIt m_data.find(sec); if (secIt m_data.end()) return false; return secIt-second.find(key) ! secIt-second.end(); } // 读取字符串 std::string IniRW::getString(const std::string sec, const std::string key, const std::string def) { auto secIt m_data.find(sec); if (secIt m_data.end()) return def; auto keyIt secIt-second.find(key); if (keyIt secIt-second.end()) return def; return keyIt-second; } // 读取int int IniRW::getInt(const std::string sec, const std::string key, int def) { std::string valStr getString(sec, key, ); if (valStr.empty()) return def; std::istringstream iss(valStr); int res; if (iss res) return res; return def; } // 读取double double IniRW::getDouble(const std::string sec, const std::string key, double def) { std::string valStr getString(sec, key, ); if (valStr.empty()) return def; std::istringstream iss(valStr); double res; if (iss res) return res; return def; } // 读取bool bool IniRW::getBool(const std::string sec, const std::string key, bool def) { std::string valStr getString(sec, key, ); std::string lowerStr; lowerStr.resize(valStr.size()); std::transform(valStr.begin(), valStr.end(), lowerStr.begin(), ::tolower); if (lowerStr 1 || lowerStr true || lowerStr yes) return true; if (lowerStr 0 || lowerStr false || lowerStr no) return false; return def; } // 设置字符串 void IniRW::setString(const std::string sec, const std::string key, const std::string val) { m_data[sec][key] val; } // 设置int void IniRW::setInt(const std::string sec, const std::string key, int val) { std::ostringstream oss; oss val; m_data[sec][key] oss.str(); } // 设置double void IniRW::setDouble(const std::string sec, const std::string key, double val) { std::ostringstream oss; oss std::fixed val; m_data[sec][key] oss.str(); } // 设置bool void IniRW::setBool(const std::string sec, const std::string key, bool val) { m_data[sec][key] val ? true : false; } // 新增接口实现分段尾部追加键值 void IniRW::appendKeyInSection(const std::string sec, const std::string key, const std::string val) { m_data[sec][key] val; } // 主函数#include mainwindow.h #include QApplication #include inirw.h #include iostream #include QTextCodec // Qt编码头文件 #include QTextCodec #include QMessageBox #include QScreen #include QRect #include QDebug #include windows.h int main(int argc, char *argv[]) { // 1. 获取 UTF-8 编码器 QTextCodec *utf8Codec QTextCodec::codecForName(UTF-8); if (utf8Codec) { // ① 关键设置 locale 编码为 UTF-8影响文件 I/O、系统接口、QString 与 const char* 转换等 QTextCodec::setCodecForLocale(utf8Codec); // ② 兼容旧代码设置 C 字符串const char*默认编码Qt5 已废弃但部分场景仍需 //QTextCodec::setCodecForCStrings(utf8Codec); // ③ 兼容 tr() 翻译设置 tr() 函数的字符串编码Qt5 已废弃若用 tr 需设置 //QTextCodec::setCodecForTr(utf8Codec); } // 2. 可选设置 Windows 控制台编码为 UTF-8避免控制台输出乱码 SetConsoleOutputCP(CP_UTF8); // 控制台输出用 UTF-8 SetConsoleCP(CP_UTF8); // 控制台输入用 UTF-8 // // 解决MinGW控制台cout中文乱码核心代码 QTextCodec *codec QTextCodec::codecForName(UTF-8); QTextCodec::setCodecForLocale(codec); QApplication a(argc, argv); MainWindow w; w.show(); // 初始化INI对象自动加载config.ini IniRW ini(config.ini); // 1. 合法性校验 if (!ini.checkValid()) { qDebug() (QString)INI文件格式校验失败 ini.getErrorMsg().c_str(); return -1; } qDebug() INI文件格式校验通过 \n\n; // 2. 读取各类配置 std::string appName ini.getString(Global, AppName, DefaultApp); int port ini.getInt(Net, Port, 8080); double ratio ini.getDouble(Logic, Rate, 1.0); bool logSwitch ini.getBool(Log, Enable, false); qDebug() 【读取原始配置】\n; qDebug() (QString)AppName: appName.c_str() ; qDebug() (QString)Port: port \n; qDebug() (QString)Rate: ratio \n; qDebug() (QString)LogEnable: (logSwitch ? true : false) \n\n; // 3. 修改已有键值 ini.setString(Global, AppName, IniDemoClient); ini.setInt(Net, Port, 9999); ini.setDouble(Logic, Rate, 3.1415926); ini.setBool(Log, Enable, true); // 4. 【新增功能测试】在已有分段末尾追加全新键值 ini.appendKeyInSection(Global, new, 1024); ini.setString(Global, Describe, 主要修改了xxx); // 4. 新增分段与键 ini.setString(NewSection, Msg, Hello INI RW); ini.setInt(NewSection, NumTest, 666); ini.setString(Global, Describe, 主要修改了xxx); // 5. 保存修改到文件原有#注释全部保留 if (ini.save()) { qDebug() 配置修改完成保存文件成功\n\n; } else { qDebug() 保存INI文件失败; return -2; } // 6. 重新加载验证修改结果 ini.load(); if (ini.checkValid()) { qDebug() 【重新加载后读取修改值】\n; qDebug() 新AppName: ini.getString(Global, AppName).c_str() \n; qDebug() 新端口: ini.getInt(Net, Port) \n; qDebug() 新增字段Msg: ini.getString(NewSection, Msg).c_str() \n; } return a.exec(); }