C++学习:一个通用ini配置文件操作类

本文介绍了一种基于C++的解决方案,用于在Windows平台上读取、修改、删除INI配置文件中的配置项,包括字符串和整型值,并实现了删除section和key的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在windows平台下,简单的程序可以通过ini文件实现简单的配置,简单适用。在win32 sdk还提供了相应的api来读取修改ini文件。ini配置文件格式为:

[section]

key=string

...

读取与修改string的api为ReadPrivateProfilesString和WritePrivateProfilesString。不过美中不足的是,win32 api并没有提供删除section或key的功能,也不支持linux平台。因此我们得自己来实现。

对于ini配置文件,格式比较简单,也就是字符串分析,为了简单,只要分析行字符串即可。但分析时需要关注的功能点:忽略大小写功能,section和key前后的无用的空格功能,注释功能。读写删除字符串、整型等。因此类的设计如下:

class IniFile
{
public:
    explicit IniFile(const string &fileName);
    ~IniFile(void);
    bool ReadFile(void);
    string ReadString( const string §ion, const string &key, const string &value );
    int    ReadInt( const string §ion, const string &key, int value );
    bool WriteString( const string §ion, const string &key, const string &value );
    bool WriteInt( const string §ion, const string &key, int value );
    bool RemoveSection( const string §ion );
    bool RemoveKey( const string §ion, const string &key );
    bool WriteFile(void);
private:
    static string Trim( const string &str );
    static string LTrim( const string &str );
    static string RTrim( const string &str );
private:
    string m_fileName;
    vector<string> m_vctLine;
    bool m_modifyFlag;
};

IniFile类的设计,对外提供的接口基本都是C++的基本类型。首先我们来看构造和析构函数。

IniFile::IniFile(const string &fileName)
    :m_fileName(fileName),
     m_modifyFlag(false)
{
	ReadFile();
}

IniFile::~IniFile(void)
{
	WriteFile();
}

bool IniFile::ReadFile( void )
{
	ifstream in(m_fileName.c_str());
	if( false == in.is_open() )
        	return false;
    
	string line;
	while( getline(in,line) )
	{
		m_vctLine.push_back(line);
	}
        m_modifyFlag = false;
	return true;
}

bool IniFile::WriteFile( void )
{
	//check if is need to save
    	if( false == m_modifyFlag )
    	{
        	return true;
    	}
    	ofstream out(m_fileName.c_str());
    	for( size_t i = 0; i < m_vctLine.size(); ++i )
    	{
		out<<m_vctLine[i]<<endl;
    	}
    	m_modifyFlag = false;
    	return true;
}

ReadFile和WriteFile函数用来读配置文件和保存配置文件。也比较简单。配置文件保存到内存,就是设计字符串数组。IniFile类主要的函数是ReadString和WriteString,下面主要讲述这两个函数实现。ReadString函数,参数就是section、key、value,这里的value是缺省值(参考win api接口设计)。先比较section,然后再是key和value。分析section的关键是“[]”,key=value格式也比较好分析,分析到section就终止key的分析,再回过头来分析下一个section。这里的分析,也可以用递归,这样遇到下个section就不要回退一步。一起先看一下函数代码:

string IniFile::ReadString( const string §ion, const string &key, const string &defval )
{
	for( size_t i = 0;i < m_vctLine.size(); ++i )
	{
		string section_line = LTrim(m_vctLine[i]);
		size_t sec_begin_pos = section_line.find('[');
		if( sec_begin_pos == string::npos || sec_begin_pos != 0 )
		{
			continue;
		}
		size_t sec_end_pos = section_line.find( ']', sec_begin_pos );
		if( sec_end_pos == string::npos )
		{
			continue;
		}

		if( ci_string(section.c_str()) != Trim(section_line.substr(sec_begin_pos+1, sec_end_pos-sec_begin_pos-1) ).c_str() )
		{
			continue;
		}

		//find key
		for( ++i; i < m_vctLine.size(); ++i )
		{
			string key_line = LTrim(m_vctLine[i]);
			size_t sec_pos = key_line.find('[');
			if( sec_pos != string::npos && sec_pos == 0 )
			{
				--i;  //reback a step,find again
				break;//the line is section line
			}

			if( key_line.find('#') != string::npos )
			{
				continue;//this is comment line
			}
			size_t equal_pos = key_line.find( '=' );
			if( equal_pos == string::npos )
			{
				continue;
			}
			if( ci_string(key.c_str()) != RTrim(key_line.substr( 0, equal_pos ) ).c_str() )
			{
				continue;
			}

			size_t comment_pos = key_line.find( "#", equal_pos + 1 );
			if( comment_pos != string::npos )
			{
				return Trim(key_line.substr( equal_pos + 1, comment_pos - equal_pos - 1 ));
			}

			return Trim(key_line.substr( equal_pos + 1 ));
		}
	}

	return defval;
}
ReadString函数中,key=value后面还可能有注释,这里注释是以"#"开头,其他形式注释暂时不支持。另外读写字符串,还要区分有引号和无引号两种形式(代码中未体现,各位读者自己增加代码)。再来看看WriteString函数,WriteString函数和ReadString差不多,只是一个读一个写,如果找不到,就要新建key=value,或者新建section和key=value。具体代码如下:

bool IniFile::WriteString( const string §ion, const string &key, const string &value )
{
	for( size_t i = 0;i < m_vctLine.size(); ++i )
	{
		string section_line = LTrim(m_vctLine[i]);
		size_t sec_begin_pos = section_line.find('[');
		if( sec_begin_pos == string::npos || sec_begin_pos != 0 )
		{
			continue;
		}
		size_t sec_end_pos = section_line.find( ']', sec_begin_pos );
		if( sec_end_pos == string::npos )
		{
			continue;
		}
		if( ci_string(section.c_str()) != RTrim(section_line.substr(sec_begin_pos+1, sec_end_pos-sec_begin_pos-1)).c_str() )
		{
			continue;
		}

		//find key
		for( ++i; i < m_vctLine.size(); ++i )
		{
			string key_line = LTrim(m_vctLine[i]);
			size_t sec_pos = key_line.find('[');
			if( sec_pos != string::npos && sec_pos == 0 )
			{
				--i;  //reback a step,find again
				break;//the line is section line
			}

			if( key_line.find('#') != string::npos )
			{
				continue;//this is comment line
			}
			size_t equal_pos = key_line.find( '=' );
			if( equal_pos == string::npos )
			{
				continue;
			}
			if( ci_string(key.c_str()) != RTrim(key_line.substr( 0, equal_pos )).c_str() )
			{
				continue;
			}

			size_t comment_pos = key_line.find( "#", equal_pos + 1 );
			string new_line = key_line.substr( 0, equal_pos + 1 ) + value;
			if( comment_pos != string::npos )
			{
				new_line += key_line.substr( comment_pos );
			}
			key_line = new_line;
            		m_modifyFlag = true;
			return true;
		}

		//add a new key
		m_vctLine.insert( m_vctLine.begin() + i, key + "=" + value ); 
		m_modifyFlag = true;
        	return true;
	}

	//add a new section and a new key
    	m_vctLine.insert( m_vctLine.end(), "" );
	m_vctLine.insert( m_vctLine.end(), "[" + section + "]" );
	m_vctLine.insert( m_vctLine.end(), key + "=" + value );
	m_modifyFlag = true;
    	return true;
}
RemoveSection函数,就是删除一个section,包括下面所有的key,代码如下:

bool IniFile::RemoveSection( const string §ion )
{
	for( size_t i = 0;i < m_vctLine.size(); ++i )
	{
		string section_line = LTrim(m_vctLine[i]);
		size_t sec_begin_pos = section_line.find('[');
		if( sec_begin_pos == string::npos || sec_begin_pos != 0 )
		{
			continue;
		}
		size_t sec_end_pos = section_line.find( ']', sec_begin_pos );
		if( sec_end_pos == string::npos )
		{
			continue;
		}
		if( ci_string(section.c_str()) != RTrim(section_line.substr(sec_begin_pos+1, sec_end_pos-sec_begin_pos-1)).c_str() )
		{
			continue;
		}

		//
		size_t del_begin = i;
		for( ++i ; i < m_vctLine.size(); ++i )
		{
			string next_section = LTrim(m_vctLine[i]);
			size_t next_pos = next_section.find('[');
			if( next_pos == string::npos || next_pos != 0 )
			{
				continue;
			}

			break;
		}
		m_vctLine.erase( m_vctLine.begin() + del_begin, m_vctLine.begin()+i );
		return true;
	}
	return false;
}
RemoveKey函数就是删除一个key,这个也比较容易看。

bool IniFile::RemoveKey( const string §ion, const string &key )
{
	for( size_t i = 0;i < m_vctLine.size(); ++i )
	{
		string §ion_line = m_vctLine[i];
		size_t sec_begin_pos = section_line.find('[');
		if( sec_begin_pos == string::npos || sec_begin_pos != 0 )
		{
			continue;
		}
		size_t sec_end_pos = section_line.find( ']', sec_begin_pos );
		if( sec_end_pos == string::npos )
		{
			continue;
		}
		if( ci_string(section.c_str()) != Trim(section_line.substr(sec_begin_pos+1, sec_end_pos-sec_begin_pos-1)).c_str() )
		{
			continue;
		}

		//find key
		for( ++i ; i < m_vctLine.size(); ++i )
		{
			string key_line = m_vctLine[i];
			key_line = Trim(key_line);
			if( key_line.find('#') == 0 )
			{
				continue;
			}

			size_t key_pos = key_line.find('=');
			if( key_pos == string::npos )
			{
				continue;
			}

			if( ci_string(key.c_str()) == Trim(key_line.substr(0, key_pos)).c_str() )
			{
				m_vctLine.erase( m_vctLine.begin() + i );
				return true;
			}
		}
	}
	return false;
}

ReadInt和WriteInt就更简单了,就通过ReadString和WriteString来实现了。直接贴上代码。

int IniFile::ReadInt( const string §ion, const string &key, int value )
{
	string str = ReadString( section, key, "" );
	if( "" == str )
	{
		return value;
	}

	istringstream in( str.c_str() );
	int ret = 0;
	in>>ret;
	return ret;
}


bool IniFile::WriteInt( const string §ion, const string &key, int value )
{
	ostringstream out;
	out<<value;
	return WriteString( section, key, out.str() );
}

其他几个辅助删除前向空格和后向空格的函数,也一并贴出来。

string IniFile::LTrim( const string &str )
{
	size_t pos = 0;
	while( pos != str.size() )
	{
		if( ' ' == str[pos] )
		{
			++pos;
		}
		else
		{
			break;
		}
	}
	
	return str.substr(pos);
}

string IniFile::RTrim( const string &str )
{
	size_t pos = str.size() - 1;
	while( pos >= 0 )
	{
		if(' ' == str[pos])
		{
			--pos;
		}
		else
		{
			break;
		}
	}
	
	return str.substr( 0, pos + 1 );
}

string IniFile::Trim(const string &str)
{
	return LTrim( RTrim(str) );
}
不区分大小写的string类型,实现如下:

/************************************************************************/
/* 字符串的迭代器定义                                                         */
/************************************************************************/
struct ci_char_traits : public char_traits<char>
{
	static bool eq(char c1, char c2)
	{ 
		return toupper(c1) == toupper(c2); 
	}
	static bool ne(char c1, char c2)
	{ 
		return toupper(c1) != toupper(c2); 
	}
	static bool lt(char c1, char c2)
	{ 
		return toupper(c1) < toupper(c2); 
	}
	static bool compare(const char* s1, const char* s2, size_t n)
	{ 
#ifdef WIN32
		return memicmp(s1,s2,n) != 0;  //实现不区分大小写的串比较
#else
		//linux不支持memicmp,自定义版本
		char *tmps1 = new char[n];
		char *tmps2 = new char[n];
		for( size_t i = 0; i < n; ++i )
		{
			tmps1[i] = toupper(s1[i]);
			tmps2[i] = toupper(s2[i]);
		}
		return memcmp(tmps1, tmps2, n) != 0;
#endif
	}
	static const char* find(const char*s, int n, char a) 
	{
		while (n-- > 0  &&  toupper(*s) != toupper(a) ) 
			++s;
		return s;
	}
};
使用代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
	IniFile ini("./test.ini");
	ini.WriteString("system", "ip", "127.0.0.1");
	cout<<ini.ReadString("system", "ip", "0.0.0.0")<<endl;
	cout<<ini.ReadInt("system", "port", 5060 )<<endl;

	return 0;
}
大功告成,各位读者自己拷贝下代码试试。
















评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值