Ini-файл на основе std::map

2010-06-15 papirosnik Программирование

В среде программирования C++ Builder и Delphi есть удобный класс: TIniFile. С его помощью можно практически за десять секунд организовать сохранения каких-либо данных во внешнем файле (например, настройки программы в файл с расширением *.cfg). Как его использовать — описывать смысла нет. Уйма общедоступной информации. Но в практике программирования в Microsoft Visual Studio 2010 (C++) я столкнулся с необходимостью иметь доступ к таким файлам — а готового решения нет. Зато в стандартной библиотеке с++ (STL — Standard Templates Library — Библиотека стандартных шаблонов) есть замечательная возможность  использовать ассоциативные массивы.

А они как нельзя лучше подходят для наших целей. В двух словах, что это такое. Ассоциативный массив -массив, индексами которого являются не числа, а любое произвольное значение. Если в обычном массиве Вы пишите например так: а[1] = 100; a[2] = 400; a[3] = 1200, то в ассоциативном массиве допустим любой тип индекса (и значения конечно же): a["first"]="one hundred", a["second"] = "four hundred", a["third"] = "one thousand two hundred"  и т.п. В этом примере в качестве индекса используется строка и значением является строка.

Как же объявить такой массив? Подключаем заголовный файл ассоциативного массива: #include <map>.  И в нужном месте объявляем переменную массива с нужными типами, например: для нашего кода выше: std::map<std::string, std::string> a; Для того, чтобы не писать каждый раз std:: можно объявить в начале кода,  что мы будем использовать пространство имён std по умолчанию: using namespace std. Использование ассоциативного массива предельно просто и интуитивно понятно. a["first"] = "one" разместит в массиве строку "one" по адресу "first". Если же по этому адресу существовала какакя-то строка, то она затрётся. Для более быстрого наполнения существуют методы insert c передачей пары адрес-значение. Эту пару можно сделать стандартным средством той же STL — make_pair. Но детальное описание ассоциативных массивов выходит за рамки данной статьи. Считаю нужным только подчеркнуть, что в этом массиве могут храниться данные любых типов (в том числе и определённых Вами классов) и индексом может выступать также любой тип (главное, чтобы для него была определена операция сравнения "<").

Относительно нашей задачи (реализация доступа к ini-файлу)  предпринимаем следующие действия. Объявляем один ассоциативный массив с индексом типа строка (это для названия секций), а вот значениями этого массива будут выступать обратно ассоциативные массивы, но уже с парой "строка-строка", то есть "ключ-=значение". То есть более высокоуровневый массив использует ключи-названия секций для своих значений-массивов типа "Параметр=значение." Ещё один момент. Мы хотим, чтобы наши ключи были регистронезависимыми, то есть "Name=Вася" было эквивалентно "NAME=Вася" или даже "nAmE=Вася" . Для этого определим структуру оператором (), который реализует нужную нам функциональность:

1
2
3
4
5
6
7
8
struct less_insensitive
{
	bool operator()(const string& _Left, const string& _Right) const
	{
		int i = _stricmp(_Left.c_str(),_Right.c_str());
		return (i<0);
	}
};

Далее предопределим тип нашей пары "ключ=значение", то есть введём тип элемента массива вместе сего индексом: typedef map<string,string,less_insensitive> TItems;. Здесь в качестве третьего параметра шаблона указана наша структура, производящая сравнение без учёта регистра. Более детальное описание таких приёмов можно найти у Страуструпа или в других источниках по STL. Теперь предопределяем тип для наших секций: typedef map<string,TItems*,less_insensitive> TSections;. Использование таких предопределений не вносит каких-либо преимуществ в выполняемый код программы, но позволяет получить более краткий и удобочитаемый синтаксис. Все действия по реализации работы с ini-файлом сводятся к реализации чтения-записи ассоциативного массива в файл и реализация различных функция доступа к его элементам, которые интерпретируют строку в значение нужного типа (int, bool и т.п.). Ниже представлен возможный вариант реализации такого подхода:

Файл inifile.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef __INIFILE_H__
#define __INIFILE_H__
#include <map>
#include <string>
using std::map;
using std::string;
 
struct less_insensitive
{
	bool operator()(const string& _Left, const string& _Right) const
	{
		int i = _stricmp(_Left.c_str(),_Right.c_str());
		return (i<0);
	}
};
 
class CIniFile
{
private:
	typedef map Tmss;
	struct _ssect	{		Tmss _items;	};
	typedef map TSections;
	TSections _sections;
	LPCSTR	_filename;
protected:
	bool	NeedRead, NeedWrite;
	_ssect* GetSection(LPCSTR Name);
	string GetStrValue(LPCSTR SectionName, LPCSTR ParamName);
	virtual void ReadFile();
public:
	CIniFile(LPCSTR FileName);
	~CIniFile();
	virtual void WriteFile();
	const	LPCSTR FileName() const {return _filename;};
	int	ReadInteger(const LPCSTR SectionName, const LPCSTR ParamName, const int DefValue);
	void	WriteInteger(const LPCSTR SectionName, const LPCSTR ParamName, const int Value);
	dword	ReadDword(const LPCSTR SectionName, const LPCSTR ParamName, const dword DefValue);
	void	WriteDword(const LPCSTR SectionName, const LPCSTR ParamName, const dword Value);
	bool      ReadBool(const LPCSTR SectionName, const LPCSTR ParamName, const bool DefValue);
	void      WriteBool(const LPCSTR SectionName, const LPCSTR ParamName, const bool Value);
};
#endif

Файл inifile.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "inifile.h"
CIniFile::CIniFile(LPCSTR FileName): _filename(FileName)
{
	NeedRead = true;
	NeedWrite = false;
}
 
CIniFile::~CIniFile()
{
	if (NeedWrite) WriteFile();
	TSections::iterator ib = _sections.begin(), ie = _sections.end();
	while (ib!=ie)
	{
		DAN(ib->second);
		++ib;
	}
}
 
void CIniFile::ReadFile()
{
	std::ifstream file(_filename);
	if (!file) return;
	_sections.clear();
	string rw;
	_ssect* sect = 0;
	while (!std::getline(file,rw).eof())
	{
		if (&#39;[&#39;==rw[0] && &#39;]&#39;==rw[rw.length()-1])
		{
			sect = new _ssect();
			_sections.insert(TSections::value_type(rw.substr(1,rw.length()-2),sect));
		} else
		{
			Tmss::size_type se = rw.find("=");
			if (se!=string::npos)
			{
				string ss1(rw.substr(0,se));
				++se;
				string ss2(rw.substr(se,rw.length()-se));
				sect->_items.insert(Tmss::value_type(ss1,ss2));
			}
		}
	}
	NeedRead = false;
}
 
void CIniFile::WriteFile()
{
	std::ofstream file(_filename);
	if (!file) return;
	TSections::iterator ib = _sections.begin(), ie = _sections.end();
	for (; ib!=ie; ib++)
	{
		file <<"["<first<<"]n"; 		_ssect* sect = ib->second;
		Tmss::iterator i1 = sect->_items.begin(),	i2= sect->_items.end();
		for (;i1!=i2;i1++)	file <first <<"=" << i1->second <<"n";
	}
	NeedWrite = false;
}
 
CIniFile::_ssect* CIniFile::GetSection(LPCSTR Name)
{
	string s(Name);
	for (dword i = 0; i_items.find(ParamName);
	return (ib == ss->_items.end())?"":ib->second;
}
 
int CIniFile::ReadInteger(const LPCSTR SectionName,const LPCSTR ParamName,const int DefValue)
{
	string s = GetStrValue(SectionName,ParamName);
	return (""==s)?DefValue:atoi(s.c_str());
}
 
void CIniFile::WriteInteger(const LPCSTR SectionName,const LPCSTR ParamName,const int Value)
{
	char str[64];
	_itoa_s(Value,&str[0],64,10);
	GetSection(SectionName)->_items.insert(Tmss::value_type(ParamName,str));
	NeedWrite = true;
}
 
dword CIniFile::ReadDword(const LPCSTR SectionName, const LPCSTR ParamName, const dword DefValue)
{
	string s = GetStrValue(SectionName,ParamName);
	dword val = StrToHex(s);
	return (""==s)?DefValue:val;
}
 
void CIniFile::WriteDword(const LPCSTR SectionName, const LPCSTR ParamName, const dword Value)
{
	char str[64];
	sprintf_s(str,64,"0x%X",Value);
	GetSection(SectionName)->_items.insert(Tmss::value_type(ParamName,str));
	NeedWrite = true;
}
 
bool CIniFile::ReadBool(const LPCSTR SectionName, const LPCSTR ParamName, const bool DefValue)
{
	string s = GetStrValue(SectionName,ParamName);
	if (""==s) return DefValue;
	for (dword i = 0; i_items.insert(Tmss::value_type(ParamName,s));
 
}
 
void CIniFile::WriteBool(const LPCSTR SectionName, const LPCSTR ParamName, const bool Value)
{
	string s(Value?"TRUE":"FALSE");
	GetSection(SectionName)->_items.insert(Tmss::value_type(ParamName,s));
}

Благодаря такому подходу теперь  можно быстро и удобно сохранять в файл какие-то конфигурации, пользовательские настройки  и тому подобные вещи, для которых служит ini-файл или Реестр Windows. Доступ к параметрам осуществляется по имени, что существенно упрощает жизнь программисту. А благодаря тому, что этот механим реализован посредством ассоциативных массивов map из STL, код работает быстро и качественно. Если какие-либо моменты в этой статье остались за пределами Вашего понимания или я недостаточно их осветил — спрашивайте, всегда рад помочь.

ini-файл, map, STL,


Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Powered by WordPress. Designed by elogi.