MFC 和 ANSI C++ INI 读取/写入类 ( Windows \ Linux )






4.92/5 (47投票s)
Windows 和 Linux 中面向对象的 INI 文件编辑。
- 下载 MFC 版本 (Visual Studio 6.0 项目) - 20.43 KB
- 下载 Ansi C++ 版本 Ansi\Unicode (Visual Studio 6.0 项目) - 16.41 KB
- 下载 MFC 版本 (Visual Studio .NET 2005 项目) - 21.67 KB
- 下载 Ansi C++ 版本 Ansi\Unicode (Visual Studio .NET 2005 项目) - 16.78 KB
- 下载 Ansi C++ 版本 Ansi\Unicode (Code Blocks 项目) - 11.7 KB
- 下载 Ansi C++ 版本 Ansi\Unicode (源码) - 8.71 KB
引言
这是对 2009 年编写的代码的重写。最初 (2005 年),CIniFile
对象的底层数据结构是 MFC 的 CList
对象。在处理巨大的 ini 文件时,这效率极低。有人联系我,询问是否有一个可以在 Ansi C++\STL 中工作的版本。在发布了一个可以在 Ansi C++\STL 编译器下不依赖 MFC 编译的版本后,很明显需要进行另一次修订,以去除缓慢的 (MFC)CList
或 (STL)std::list
。在 2009 年重写后,代码被修改为使用 std::map
,这极大地提高了性能和效率。然而,底层数据存储的效率仍然存在问题。虽然 2009 年版本的速度比 2005 年的原版好得多,但 std::map
键的存储空间有所浪费,因为每个 CIniSection
和 CIniKey
对象分别存储自己的节名和键名。本版本将现有的 std::map<std::string,CIniSection*>
和 std::map<std::string,CIniKey*>
分别替换为 std::set<CIniSection*>
和 std::set<CIniKey*>
。此修改允许节和键名只存储一次,从而大大减少了大型 INI 文件上的内存使用量,同时保持了 O(log(n)) 的效率。在 MFC 项目中使用时,将 CString
传递给 CIniFile
函数时,应将其强制转换为 (LPCTSTR
) 或使用 CString
的 GetString()
成员。CIniFile
类根据您的 MFC 项目是 Unicode 还是多字节编译,将 string
存储为 std::wstring
或 std::string
。由于 CString
性能极差且不是 C++ 标准,因此在 2009 年已移除对 CString
的内部使用。当前使用标准 STL 字符串的 CIniFile
类在 MFC 项目中运行良好。应用户要求,最近添加了使用 std::stream
与 CIniFile
对象的功能。为了进一步提高效率,许多函数调用被修改为通过 const std::string&
或 const std::wstring&
传递,以防止不必要的对 std::basic_string
拷贝构造函数的调用。
CIniClass
可以在 MFC 中使用。但是,在将 CString
类型传递给 CIniFile
函数时,有必要调用 CString::GetSting()
方法或将 CString
强制转换为 (LPCTSTR
)。以下示例将在下面演示
- 在 Linux 下使用 Ansi C++\STL 版本的
CIniFile
- 在 Windows 下使用 Ansi C++\STL 版本的
CIniFile
- 在 MFC 项目中使用 Ansi C++\STL 版本
提供的示例代码
为了帮助理解如何使用代码,提供了多个版本供下载。提供的下载如下
- Ansi C++\STL
CIniFile
(Code Blocks 项目) - Ansi C++\STL
CIniFile
(Visual Studio 6.0 项目) - Ansi C++\STL
CIniFile
在 Windows MFC (Visual Studio 6.0 项目) 中 - Ansi C++\STL
CIniFile
(Visual Studio .NET 2005 项目) - Ansi C++\STL
CIniFile
在 Windows MFC (Visual Studio .NET 2005 项目) 中
如果您不想要整个项目,可以选择仅下载源码版本。请参阅“在 Windows MFC 项目中使用源码”部分。该部分解释了在使用预编译头文件时需要进行的更改。
效率
在运行在带有 2GB RAM 的 Lenovo T60 上的 Ubuntu Linux 10.04 上进行测试时,CIniFile
可以在大约 2 秒内生成一个包含 10000 个节和每个节 100 个键的 ini 文件并将其写入磁盘。将每个节的键数增加到 1000 后,文件生成时间约为 3 秒。文件长达 10,100,000 行,大小约为 227MB。将数据从文件读回内存也在此范围内。在 MFC 项目中进行了相同的测试。结果不那么令人印象深刻,前一个测试大约需要 3 秒,后一个测试大约需要 30 秒。对于两个测试,节和键都以如下格式写入
[Section0]
Key0=KeyValue
Key1=KeyValue
Key2=KeyValue
...
[Section1]
Key0=KeyValue
Key1=KeyValue
Key2=KeyValue
...
预处理器定义
以下值适用于 Ansi C++\STL CIniFile 库
注意:CIniFile
类目前支持 (STL) std::wstring
或 std::string
。
CIniFile
的 typedef
取决于是否定义了 _UNICODE
。如果您的项目中定义了 _UNICODE
,则 CIniFile typedef
是 CIniFileW
;如果未定义 _UNICODE
,则 CIniFile typedef
是 CIniFileA
对象。CIniFileW
使用 std::wstring
来支持宽字符。请参阅以下预处理器指令
// Additional defines
#ifdef _UNICODE
#define INI_TOKEN_A INI_TOKEN_UNICODE
#define INI_TOKEN_B INI_TOKEN_UNICODE
#define INI_EMPTY INI_EMPTY_UNICODE
typedef CIniFileW CIniFile;
typedef CIniSectionW CIniSection;
typedef CIniKeyW CIniKey;
typedef PCINIW PCINI;
typedef PCINIKEYW PCINIKEY;
typedef PCINISECW PCINISEC;
typedef KeyIndexW KeyIndex;
typedef SecIndexW SecIndex;
#else
#define INI_TOKEN_A INI_TOKEN_ANSI
#define INI_TOKEN_B INI_TOKEN_ANSI
#define INI_EMPTY INI_EMPTY_ANSI
typedef CIniFileA CIniFile;
typedef CIniSectionA CIniSection;
typedef CIniKeyA CIniKey;
typedef PCINIA PCINI;
typedef PCINIKEYA PCINIKEY;
typedef PCINISECA PCINISEC;
typedef KeyIndexA KeyIndex;
typedef SecIndexA SecIndex;
#endif
Visual Studio 下的 CIniFile
库期望在编译时定义 _WIN32
。由于 Windows 不支持 strcasecmp
或 wcscasecmp
,因此定义 _WIN32
会将函数切换为相应的 Windows 版本。这些函数由 std::set
作为自定义比较器使用。
// The following is defined in the CIniFileA class
struct ci_less_a
{
bool operator() (const CIniSectionA* s1, const CIniSectionA* s2) const
{
#ifndef _WIN32
return strcasecmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
#else
return _stricmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
#endif
}
};
// The following is defined in the CIniSectionA class
struct ci_less_a
{
bool operator() (const CIniKeyA* s1, const CIniKeyA* s2) const
{
#ifndef _WIN32
return strcasecmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
#else
return _stricmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
#endif
}
};
// The following is defined in the CIniFileW class
struct ci_less_w
{
bool operator() (const CIniSectionW* s1, const CIniSectionW* s2) const
{
#ifndef _WIN32
return strcasecmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
#else
return _stricmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
#endif
}
};
// The following is defined in the CIniSectionW class
struct ci_less_w
{
bool operator() (const CIniKeyW* s1, const CIniKeyW* s2) const
{
#ifndef _WIN32
return strcasecmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
#else
return _stricmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
#endif
}
};
重要的定义
_TRACE_CINIFILE
- 如果定义,则启用到标准输出的调用跟踪_UNICODE
- 如果定义,则CIniFile
将被定义为CIniFileW
而不是CIniFileA
_FORCE_UNIX_LINEFEED
- 如果定义,当定义了_WIN32
(WINDOWS) 时,默认行尾 CRLF 将被覆盖为 CR_FORCE_WINDOWS_LINEFEED
- 如果定义,当未定义_WIN32
(*NIX) 时,默认行尾 CR 将被覆盖为 CRLF
解析和行为
目前 CIniFile
被设计为读取大多数 ini 文件。Ini 节应以 "[" 开始并以 "]" 结束。 "[" 和 "]" 之间的空白将被修剪。例如,下面定义的节将被解释为 "SECTION
",而不是 " SECTION
"。
[ SECTION ]
...
...
...
Ini 键\值对应有一个键值,在键值右侧用 "=" 分隔。键值也会被修剪以去除空白。例如,下面定义的键将被解释为 "MyKeyValue=SomeData
"。结果键将是 "MyKeyValue
",值将是 "SomeData
"。
[ SECTION ]
MyKeyValue =SomeData
...
...
然而,Ini 键值不会被修剪,空白会被保留。例如,下面定义的键将被解释为 "MyIniKey= SomeDataWithSpaces
"。结果键将是 "MyKeyValue
",值将是 " SomeDataWithSpaces
"。
[ SECTION ]
MyKeyValue = SomeDataWithSpaces
...
...
函数和返回值
以下示例将使用 std::string
作为函数参数。使用 CIniFileA
或 CIniFileW
将分别导致使用 std::string
或 std::wstring
。CIniFile
的 typedef
基于预处理器指令 _UNICODE
。CIniSection
和 CIniKey
对象的构造函数和析构函数是 private
的。CIniFile
类负责创建 CIniSection
对象。CIniSection
对象负责创建 CIniKey
对象。这种封装可以防止有人删除内部管理的指针。每个子类都有一个指向父对象的指针。CIniKey
有一个指向其父 CIniSection
的指针。CIniSection
有一个指向其关联的 CIniObject
的指针。
// Methods of CIniFile
// Used to save the data back to the file or your choice. Returns true if saved
bool CIniFile::Save( const std::string& fileName );
// Used to save the ini data to an output stream
bool CIniFile::Save( std::ostream& output );
// Loads the data in the ini file into the IniFile object,
// Returns true if loaded, false if failed to open file
// If bMerge is passed true, ini data will be merged into the existing ini object.
// Merger overwrite similar section key values
bool CIniFile::Load( const std::string& fileName , bool bMerge = false );
// Loads data from an input stream which contains ini data.
// If bMerge is passed true, ini data will be merged into the
// existing ini object. Merger overwrite similar section key values
void Load( std::istream& input , bool bMerge = false );
// Returns a const ref to the internal section index
// ( can be used to get number of sections or enumerate them )
// See section on "Using CIniFile::GetSections and CIniSection::GetKeys"
const SecIndex& GetSections() const;
// Methods of CIniSection
// Adds a key to the CIniSection object,
// returns a CIniKey pointer to the new or existing object
CIniKey* AddKey( std::string sKeyName );
// Removes a single key by pointer
void RemoveKey( CIniKey* pKey );
// Removes a single key by string
void RemoveKey( std::string sKey );
// Removes all the keys in the section
void RemoveAllKeys( );
// Returns a CIniKey pointer to the key by name, NULL if it was not found
CIniKey* GetKey( std::string sKeyName ) const;
// Returns a const ref to the internal key index
// ( can be used to get number of keys or enumerate them )
// See section on "Using CIniFile::GetSections and CIniSection::GetKeys"
const KeyIndex& GetKeys() const;
// Returns a KeyValue at a certain section
std::string GetKeyValue( std::string sKey ) const;
// Sets a KeyValuePair at a certain section
void SetKeyValue( std::string sKey, std::string sValue );
// Sets the section name, returns true on success, fails if the section
// name sSectionName already exists
bool SetSectionName( std::string sSectionName );
// Returns the section name [sectionname]
std::string GetSectionName() const;
// Methods of CIniKey
// Sets the string "value" of key=value
void SetValue( std::string sValue );
// Returns the string "value" of the key=value
std::string GetValue() const;
// Sets the key name, returns true on success,
// fails if the key name sKeyName already exists in the parent section
bool SetKeyName( std::string sKeyName );
// Returns the string "key" of key=value
std::string GetKeyName() const;
// Operator overloads for ostream
std::ostream& operator<<(std::ostream& output, CIniFile& obj);
// Operator overloads for istream
std::istream& operator>>(std::istream& input, CIniFile& obj);
// Used to manipulate CIniFile input behavior. Allows >> to merge rather than overwrite.
std::istream& operator>>(std::istream& input, CIniMerge merger)
使用 CIniFile::GetSections 和 CIniSection::GetKeys
以下代码将演示如何使用 GetSection
和 GetKeys
函数。
// The code will print the [SECTIONS] and Key=Value for the ini file
CIniFile ini;
ini.Load("/tmp/inifile.ini");
// ini.GetSections().size() will return the number of ini sections in the file.
// Print all sections and keys using the following code
for( SecIndex::const_iterator itr = ini.GetSections().begin() ;
itr != ini.GetSections().end() ; ++itr )
{
// (*itr)->GetSectionName() returns the name of the section
// (*itr)->GetKeys().size() will return the number of keys associated with this section
std::cout << "[" << (*itr)->GetSectionName() << "]" << std::endl;
for( KeyIndex::const_iterator kitr = (*itr)->GetKeys().begin() ;
kitr != (*itr)->GetKeys().end() ; kitr++ )
{
// (*kitr)->->GetKeyName() returns is the name of the key
// (*kitr)->->GetValue() returns is the value of the key
std::cout << (*kitr)->->GetKeyName() << "=" << (*kitr)->->GetValue() << std::endl;
}
}
使用 CIniMerge Manipulator 和 C++ 流
以下代码将演示如何使用 CIniMerge
与 >>
运算符。
stringstream ss1;
ss1 << "[SOMESECTION]" << CIniFile::LF;
ss1 << "key1=value" << CIniFile::LF;
ss1 << "key2=value" << CIniFile::LF;
ss1 << "key3=value" << CIniFile::LF;
stringstream ss2;
ss2 << "[SOMESECTION2]" << CIniFile::LF;
ss2 << "key1=value" << CIniFile::LF;
ss2 << "key2=value" << CIniFile::LF;
ss2 << "key3=value" << CIniFile::LF;
CIniFile ini;
// Load ini using Load and stream
ini.Load( ss1 );
// Append to ini using Load and stream
// The resulting ini will be both sections
ini.Load( ss2 , true );
// Load ini using >>
// Note: The full contents of the existing ini file object
// are dumped and the ss1 data is imported.
ss1 >> ini;
// Append to ini using >>
// The resulting ini will be both sections
ss2 >> CIniMerge(ini);
// Saving ini to stream using Save()
// The following will print the entire ini to standard out.
ini.Save( std::cout );
// Saving ini to stream using <<
// The following will print the entire ini to standard out.
std::cout << ini
Linux - 使用示例
#include "inifile.h"
#include <iostream>
int main()
{
// Note if _UNICODE would be defined CIniFile would require wide strings.
// It is 100% valid to also use either CIniFileW or CIniFileA explicitly
CIniFile ini;
// Loading an existing INI file
ini.Load("/tmp/test.ini");
// Adding Sections
ini.AddSection("Test1");
// Adding sections and keys
ini.AddSection("Test2")->AddKey("Test2Key");
// Adding sections, keys, and values
ini.AddSection("Test3")->AddKey("Test3Key")->SetValue("Test3KeyValue");
// Getting an existing section of the ini
CIniSection* pSection = ini.GetSection("Test3");
if( pSection )
{
// Do stuff with that section
pSection->SetSectionName("Test3NewSectionName");
// Getting an existing key of the ini section
CIniKey* pKey = pSection->GetKey("Test3Key");
if( pKey )
{
pKey->SetKeyName("Test3KeyNewName");
}
}
// Getting key value using the ini function v.s getting the objects
std::cout << "KeyValue: " << ini.GetKeyValue
( "Test3NeWSECtionName" , "Test3KeyNewName" ) << std::endl;
ini.Save("/tmp/testout.ini");
return 0;
}
Windows (非 MFC VS6 或 VS 2005.NET) - 使用示例
#include "inifile.h"
#include "tchar.h"
#include <iostream>
int _tmain()
{
// Note if _UNICODE would be defined CIniFile would require
// wide strings (Note the _T(x) macro).
// It is 100% valid to also use either CIniFileW or CIniFileA explicitly
CIniFile ini;
ini.Load(_T("C:\\temp\\testout.ini"));
// Adding Sections
ini.AddSection(_T("Test1"));
// Adding sections and keys
ini.AddSection(_T("Test2"))->AddKey(_T("Test2Key"));
// Adding sections, keys, and values
ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->SetValue(_T("Test3KeyValue"));
// Getting an existing section of the ini
CIniSection* pSection = ini.GetSection(_T("Test3"));
if( pSection )
{
// Do stuff with that section
pSection->SetSectionName(_T("Test3NewSectionName"));
// Getting an existing key of the ini section
CIniKey* pKey = pSection->GetKey(_T("Test3Key"));
if( pKey )
{
pKey->SetKeyName(_T("Test3KeyNewName"));
}
}
// Getting key value using the ini function v.s getting the objects
std::cout << _T("KeyValue: ") << ini.GetKeyValue
( _T("Test3NeWSECtionName") , _T("Test3KeyNewName") ) << std::endl;
ini.Save(_T("C:\\temp\\testout.ini"));
return 0;
}
Windows (MFC) Visual Studio .NET 2005 - 使用示例
#include "stdafx.h"
#include "inifile_mfc_vs.h"
#include "inifile.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
// Using CString
CString fileToSave = _T("C:\\temp\\testout.ini");
// Note if _UNICODE would be defined CIniFile would require
// wide strings (Note the _T(x) macro).
// It is 100% valid to also use either CIniFileW or CIniFileA explicitly
// Works Unicode or Multibyte
CIniFile ini;
// Load an existing INI
ini.Load(_T("C:\\temp\\testout.ini"));
// Adding Sections
ini.AddSection(_T("Test1"));
// Adding sections and keys
ini.AddSection(_T("Test2"))->AddKey(_T("Test2Key"));
// Adding sections, keys, and values
ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->
SetValue(_T("Test3KeyValue"));
// Getting an existing section of the ini
CIniSection* pSection = ini.GetSection(_T("Test3"));
if( pSection )
{
// Do stuff with that section
pSection->SetSectionName(_T("Test3NewSectionName"));
// Getting an existing key of the ini section
CIniKey* pKey = pSection->GetKey(_T("Test3Key"));
if( pKey )
{
pKey->SetKeyName(_T("Test3KeyNewName"));
}
}
// Getting key value using the ini function v.s getting the objects
_tprintf(_T("KeyValue: %s\n"),
ini.GetKeyValue( _T("Test3NeWSECtionName") , _T("Test3KeyNewName") ) );
// Note you must call GetString() to make the conversion
ini.Save(fileToSave.GetString());
}
return nRetCode;
}
Windows (MFC) Visual Studio 6.0 - 使用示例
#include "stdafx.h"
#include "inifile_mb_wide_mfc_vc6.h"
#include "inifile.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
// Using CString
CString fileToSave = "C:\\temp\\testout.ini";
CIniFile ini;
// Adding Sections
ini.AddSection("Test1");
// Adding sections and keys
ini.AddSection("Test2")->AddKey("Test2Key");
// Adding sections, keys, and values
ini.AddSection("Test3")->AddKey("Test3Key")->SetValue("Test3KeyValue");
// Getting an existing section of the ini
CIniSection* pSection = ini.GetSection("Test3");
if( pSection )
{
// Do stuff with that section
pSection->SetSectionName("Test3NewSectionName");
// Getting an existing key of the ini section
CIniKey* pKey = pSection->GetKey("Test3Key");
if( pKey )
{
pKey->SetKeyName("Test3KeyNewName");
}
}
// Getting key value using the ini function v.s getting the objects
std::cout << "KeyValue: " << ini.GetKeyValue
( "Test3NeWSECtionName" , "Test3KeyNewName" ) << std::endl;
// Note to use CString in VC6 you must cast to LPCTSTR
// since GetString() is not available
ini.Save((LPCTSTR)fileToSave);
}
return nRetCode;
}
在 Windows MFC 项目中使用源码
要在使用预编译头文件的 MFC 项目中使用 Ansi C++\STL 版本的代码,必须将预编译头文件作为 cinifile.cpp 的第一行添加。请参阅下面的示例
--------------- cinifile.cpp ---------------
#include "stdafx.h"
#include "inifile.h"
...
--------------- end of file ----------------
未来的增强
也许支持多键值,如另一位 CodeProject 用户在下面的注释中所述。
历史
- 12\01\2005 - 初始 MFC 发布
- 01\12\2006 - 移植到 Ansi C++ 非 MFC
- 06\16\2009 - 添加了对不同行尾类型的支持,解决了读取不同类型行尾的问题
- 06\17\2009 - 添加了对宽字符的支持
- 06\21\2009 - 重写以使用
std::map
- 07\02\2009 - 移除了 MFC 版本,因为 Ansi 版本可以在 MFC 中工作 (提供下载示例)
- 07\03\2009 - 添加了对 VS6 的支持
- 07\03\2009 - 修复了
SecMapA
\SecMapW
的问题。命名不针对编码,可能导致问题 - 07\03\2009 - 修复了
GetKeys
和GetSections
函数,使其返回 const ref 而不是数据副本 - 07\14\2009 - 修复了
Load()
在键值上的空格保留 - 07\26\2009 - 修复了错误的定义
MSC_VER
,应为_MSC_VER
- 09\21\2009 - 修复了删除所有节和键的问题,将
empty()
替换为clear()
- 09\22\2009 - 添加了重载的
Load()
和Save()
以读写stream
s - 09\23\2009 - 添加了用于
stream
s 的<<
和>>
运算符 - 09\24\2009 - 在
Load()
中添加了合并选项 - 09\25\2009 - 添加了
CIniMerge
用于<<
和>>
- 09\27\2009 - 将
CIniMerge
移至CIniFile
,修复了 VC6CIniFile::CR
的问题 - 12\28\2010 - 使用
std::set
而不是std::map
减少了键存储冗余 - 12\29\2010 - 减少了传值方法数量以减少深拷贝
std::string
到 conststd::string&
- 05\07\2011 - 将
MSC_VER
修复为 _MSC_VER
- 05\07\2011 - 修复了 OTHER 文件解析检测问题
- 05\06\2021 - 许多人联系我,现迁移到 MIT 许可证