C++學習:一個通用ini配置檔案操作類
阿新 • • 發佈:2019-01-02
在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;
}
大功告成,各位讀者自己拷貝下程式碼試試。