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的格式进行配准项描述。具体可见下列示例代码

port=8080     #设置端口
host=http://www.baidu.com    #设置服务器路径
default_savename=down.zip    #设置默认保存文件的命令
cmd=wget [URL] -o [SAVENAME]
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即可,在个人项目中直接导入即可。

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
//-------------三个辅助函数-------------
//用于支持配置文件中的动态字符串 如 "wget {URL} -o down.zip"
std::string string_replace(std::string source, std::string find, std::string replace)
{
	std::string::size_type pos = 0;
	std::string::size_type a = find.size();
	std::string::size_type b = replace.size();
	while ((pos = source.find(find, pos)) != std::string::npos)
	{
		//source.replace(pos, a, replace);
		source.erase(pos, a);
		source.insert(pos, replace);
		pos += b;
	}
	return source;
}
//用于支持配置文件中的字符串转数字
template <class T>
void convertFromString(T& value, const std::string& s) {
	std::stringstream ss(s);
	ss.precision(s.length());
	ss >> value;
}
//用于安全读取配置文件(只用config.find(key)->second写错了key会导致报错)
std::string read_config(std::map<std::string, std::string> config, std::string key) {
	auto it = config.find(key);
	if (it == config.end()) {
		return "";
	}
	else {
		return it->second;
	}
}

//-------------四个主要函数-------------
//用于去除字符串多余的空格
std::string trim(std::string text)
{
	if (!text.empty())
	{
		text.erase(0, text.find_first_not_of(" \n\r\t"));//去除字符串头部的空格
		text.erase(text.find_last_not_of(" \n\r\t") + 1);//去除字符串尾部的空格
	}
	return text;
}
//用于支持将多行的配置文件分割开来
void Stringsplit(std::string str, const char split, std::vector<std::string>& strList)
{
	std::istringstream iss(str);	// 输入流
	std::string token;			// 接收缓冲区
	while (getline(iss, token, split))	// 以split为分隔符
	{
		strList.push_back(token);
	}
}
//用于支持将 key=value 格式的配置文件分割开来(只分割一次)
void Stringsplit2(std::string str, const char split, std::vector<std::string>& strList)
{
	//string str = "key=value1 value2 #notes";
	size_t pos = str.find(split); // 3
	if (pos>0&&pos<str.length()) {//用于分割key=value
		string p = str.substr(0, pos); // 123
		string q = str.substr(pos + 1); // 456,789
		strList.push_back(p);
		strList.push_back(q);
	}
	else {//用于不带# 注释时的分割
		strList.push_back(str);
	}
}
//解析配置文件,并添加默认配置
std::map<std::string, std::string> get_configs(std::string fname) {
	std::string strdata;
	try {
		std::ifstream in(fname, std::ios::in);
		std::istreambuf_iterator<char> beg(in), end;
		strdata = std::string(beg, end);
		in.close();
		if (strdata.length() < 10) {
			std::cout << fname << " context is not correct! " << std::endl;
		}
	}
	catch (...) {
		std::cout <<"Read " << fname << " error! " << std::endl;
	}

	std::vector<std::string> strList;
	Stringsplit(strdata, '\n', strList);
	std::map<std::string, std::string> maps;
	for (int i = 0;i < strList.size();i++) {
		std::vector<std::string> tmp1,tmp2;
		Stringsplit2(strList[i], '#', tmp1);//用于清除注释  注释存储在trim(tmp1[1])中
		Stringsplit2(tmp1[0], '=', tmp2);//把字符串分为两节
		maps.insert({ trim(tmp2[0]),trim(tmp2[1]) });//去除字符串多余的空格(包含 \n\r\t)
	}

	//添加默认配置
	//如果配置文件中的key都是正常设置了的,那么下面的insert代码都不会生效
	maps.insert({ "port","80" });
	maps.insert({ "host", "http://www.bing.com" });
	maps.insert({ "default_savename", "default_savename.zip" });
	maps.insert({ "cmd", "wget [URL] -o [SAVENAME]" });
	maps.insert({ "max_time", "1000" });
	return maps;
}

4、调用示例

#include <curl_tool.hpp>

extern std::map<std::string, std::string> config = get_configs("config.txt");
int main(){
    std::string key="host";
    std::string value=config.find(key)->second;
    std::string value=read_config(config,key);
}

猜你喜欢

转载自blog.csdn.net/a486259/article/details/125516871