• C++下基于std标准库实现配置文件的读取


    在一般应用程序中,配置文件的格式有json、yaml、xml、ini等格式。这些格式对变量类型支持较为丰富,如int、string、double、array等。但使用这些格式的配置文件通常需要导入一些其他的外部库,如json、yaml、xml等格式;ini格式是windows系统内置的,但是放到liunx平台上就不支持;这些需要库依赖。采用使用的配置文件(json、yaml、xml、ini)支持域嵌套,但这在中小型程序中是非必须的,在以string做为key的配置文件中完全可以使用前缀(如 section.key来表示)。且,通过后处理,配置文件中以string表示的value可以转化为int、double、array等格式。也就是说,完全可以通过读取文本实现配置文件的加载调用。

    同时,在网上的开源代码中也有许多c++脚本实现了key=value格式配置文件的读取。为此,博主针对于C++配置文件的读取,以std::map的方式进行了实现,代码保存为config.hpp,通过include后在项目中即可用。

    本实现支持注释,支持默认配置,支持配置文件中多余的空格(使用trim函数过滤空格)。未实现std::map配置文件转txt(需要对std::map进行遍历),后续如果有需要会进行实现,也欢迎各位在评论区贡献实现代码。在https://c.zhizuobiao.com/c-18120300137/中以链表的形式实现了配置文件的读写,尽管不支持默认配置、注释与多余空格,但还是值得可以参考一下。


    1、配置文件的格式

    大部分配置文件的格式都是通过换行符来分割不同的配置项,通过#描述注释,通过key=value的格式进行配准项描述。具体可见下列示例代码

    1. port=8080 #设置端口
    2. host=http://www.baidu.com #设置服务器路径
    3. default_savename=down.zip #设置默认保存文件的命令
    4. cmd=wget [URL] -o [SAVENAME]
    5. max_time=100 #设置cmd最大执行时间

    2、基本思路

    加载配置文件分四步实现,1、将配置文件读取为string;2、利用换行符分割多个配置项;3、利用#分割注释与配置项内容;4利用=分割key与value

    为了实现这四步博主实现了四个主要函数。
    get_configs函数:用于加载配置文件,调用其他函数处理string,生成std::map
    Stringsplit函数:用于步骤2,将多行的配置文件分割开
    Stringsplit2函数:用于步骤3与步骤4,分割#与=
    trim函数:用于步骤1的get_configs函数中,去除key与value中多余的空格

    此外还实现了三个辅助函数
    string_replace函数:用于实现字符串模板中的替换
    convertFromString函数:用于实现字符串类型转换,支持将string转换为int、flaot、double
    read_config函数:用于安全读取配置项,简化读取代码。防止读取到未知配置项时报错

    3、全部代码

    代码保存为config.hpp即可,在个人项目中直接导入即可。

    1. #include <iostream>
    2. #include <fstream>
    3. #include <sstream>
    4. #include <vector>
    5. #include <map>
    6. //-------------三个辅助函数-------------
    7. //用于支持配置文件中的动态字符串 如 "wget {URL} -o down.zip"
    8. std::string string_replace(std::string source, std::string find, std::string replace)
    9. {
    10. std::string::size_type pos = 0;
    11. std::string::size_type a = find.size();
    12. std::string::size_type b = replace.size();
    13. while ((pos = source.find(find, pos)) != std::string::npos)
    14. {
    15. //source.replace(pos, a, replace);
    16. source.erase(pos, a);
    17. source.insert(pos, replace);
    18. pos += b;
    19. }
    20. return source;
    21. }
    22. //用于支持配置文件中的字符串转数字
    23. template <class T>
    24. void convertFromString(T& value, const std::string& s) {
    25. std::stringstream ss(s);
    26. ss.precision(s.length());
    27. ss >> value;
    28. }
    29. //用于安全读取配置文件(只用config.find(key)->second写错了key会导致报错)
    30. std::string read_config(std::map<std::string, std::string> config, std::string key) {
    31. auto it = config.find(key);
    32. if (it == config.end()) {
    33. return "";
    34. }
    35. else {
    36. return it->second;
    37. }
    38. }
    39. //-------------四个主要函数-------------
    40. //用于去除字符串多余的空格
    41. std::string trim(std::string text)
    42. {
    43. if (!text.empty())
    44. {
    45. text.erase(0, text.find_first_not_of(" \n\r\t"));//去除字符串头部的空格
    46. text.erase(text.find_last_not_of(" \n\r\t") + 1);//去除字符串尾部的空格
    47. }
    48. return text;
    49. }
    50. //用于支持将多行的配置文件分割开来
    51. void Stringsplit(std::string str, const char split, std::vector<std::string>& strList)
    52. {
    53. std::istringstream iss(str); // 输入流
    54. std::string token; // 接收缓冲区
    55. while (getline(iss, token, split)) // 以split为分隔符
    56. {
    57. strList.push_back(token);
    58. }
    59. }
    60. //用于支持将 key=value 格式的配置文件分割开来(只分割一次)
    61. void Stringsplit2(std::string str, const char split, std::vector<std::string>& strList)
    62. {
    63. //string str = "key=value1 value2 #notes";
    64. size_t pos = str.find(split); // 3
    65. if (pos>0&&pos<str.length()) {//用于分割key=value
    66. string p = str.substr(0, pos); // 123
    67. string q = str.substr(pos + 1); // 456,789
    68. strList.push_back(p);
    69. strList.push_back(q);
    70. }
    71. else {//用于不带# 注释时的分割
    72. strList.push_back(str);
    73. }
    74. }
    75. //解析配置文件,并添加默认配置
    76. std::map<std::string, std::string> get_configs(std::string fname) {
    77. std::string strdata;
    78. try {
    79. std::ifstream in(fname, std::ios::in);
    80. std::istreambuf_iterator<char> beg(in), end;
    81. strdata = std::string(beg, end);
    82. in.close();
    83. if (strdata.length() < 10) {
    84. std::cout << fname << " context is not correct! " << std::endl;
    85. }
    86. }
    87. catch (...) {
    88. std::cout <<"Read " << fname << " error! " << std::endl;
    89. }
    90. std::vector<std::string> strList;
    91. Stringsplit(strdata, '\n', strList);
    92. std::map<std::string, std::string> maps;
    93. for (int i = 0;i < strList.size();i++) {
    94. std::vector<std::string> tmp1,tmp2;
    95. Stringsplit2(strList[i], '#', tmp1);//用于清除注释 注释存储在trim(tmp1[1])中
    96. Stringsplit2(tmp1[0], '=', tmp2);//把字符串分为两节
    97. maps.insert({ trim(tmp2[0]),trim(tmp2[1]) });//去除字符串多余的空格(包含 \n\r\t)
    98. }
    99. //添加默认配置
    100. //如果配置文件中的key都是正常设置了的,那么下面的insert代码都不会生效
    101. maps.insert({ "port","80" });
    102. maps.insert({ "host", "http://www.bing.com" });
    103. maps.insert({ "default_savename", "default_savename.zip" });
    104. maps.insert({ "cmd", "wget [URL] -o [SAVENAME]" });
    105. maps.insert({ "max_time", "1000" });
    106. return maps;
    107. }

    4、调用示例

    1. #include <curl_tool.hpp>
    2. extern std::map<std::string, std::string> config = get_configs("config.txt");
    3. int main(){
    4. std::string key="host";
    5. std::string value=config.find(key)->second;
    6. std::string value=read_config(config,key);
    7. }

  • 相关阅读:
    分享一种非隔离控制三象限双向可控硅的产品级电路
    详解单例模式+代码实现
    基于Android的电子书阅读器的设计与实现
    科技图表AE
    springboot2.3整合mybatis-plus,实现xxljob执行器基于线程池的异步多线程任务
    Linux命令记载
    4. react路由
    SpringBoot相关
    人工智能时代,数据分析如何帮助预测业务未来?
    [Kogel.Subscribe.Mssql]SQL Server增量订阅,数据库变更监听
  • 原文地址:https://blog.csdn.net/a486259/article/details/125516871