序
INI 文件作为最常用的配置文件之一,经常出现在各种程序中。
在效率第一的现在,如何能方便简洁并高效的搞定这些数据读写?
本文将会分享一份关于 Qt 的方案。(有兴趣的小伙伴可以在此基础知识轻松扩展)
- 定义 INI 配置文件方式如下:
// datIni.h 文件
#include "inier.h" // 本文将要实现的
// 加载定义 INI 文件开始
INI_BEGIN("./dat.ini") // 操作的文件路径
// user 段
INI_PARAM(user, QString, Name, "Ann") // 依次是:段名,数据类型,数据名称,默认值
INI_PARAM(user, QString, Power, "123456")
INI_PARAM(user, int, Authority, 1)
// test 段
INI_PARAM(test, int, Num, 15)
INI_PARAM(test, double, Tem, 86.3)
INI_PARAM(test, bool, IsOk, true)
... // 添加各种需要的数据
// 加载定义 INI 文件结束
INI_END
- 使用方式如下
#include <QApplication>
#include "logger.h" // 见《工欲善其事系列之一》
#include "datIni.h" // 上面定义的头文件
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// get 对应的参数,并打印
logDebug(ini.getName(), ini.getPower(), ini.getAuthority(), ini.getNum(), ini.getTem(), ini.getIsOk());
// set 参数
ini.setName("Judi");
ini.setPower("admin");
ini.setTem(63.5);
ini.setIsOk(false);
// get 对应的参数,并打印
logDebug(ini.getName(), ini.getPower(), ini.getAuthority(), ini.getNum(), ini.getTem(), ini.getIsOk());
return 0;
}
- 生成的数据文件
[user]
Name=Judi
Power=admin[test]
Tem=63.5
IsOk=false
Qt 自带 INI 文件读写类使用方法
Qt中自带的读写类是 QSettings. 读写 INI 文件方式如下:
- 读 INI 文件
#include <QCoreApplication>
#include <QSettings>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//QSettings构造函数的第一个参数是ini文件的路径,第二个参数表示针对ini文件,第三个参数可以缺省
QSettings ini("./dat.ini", QSettings::IniFormat);
// 第二个参数是缺省值,如果文件还没创建或者不存在该参数值是,则用缺省值
qDebug() << ini.value("user/Name", "Judi").toString();
qDebug() << ini.value("user/Power", "admin").toString();
qDebug() << ini.value("user/Authority", "2").toInt();
return 0;
}
- 写 INI 文件
#include <QCoreApplication>
#include <QSettings>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//QSettings构造函数的第一个参数是ini文件的路径,第二个参数表示针对ini文件,第三个参数可以缺省
QSettings ini("./dat.ini", QSettings::IniFormat);
ini.setValue("user/Name", "Judi");
ini.setValue("user/Power", "admin");
ini.setValue("user/Authority", "2");
return 0;
}
然而在非常简单的项目里面,像这样写,确实很方便!
但是如果设置的参数很多,不同的位置都要进行读取参数,这样做就会很容易出错。
毕竟如果一个字符串打错了,编译不会报错,那样就很难发现bug!
所以一般会对操作配置文件进行封装。
常规封装操作 INI 文件方案
常见的操作 INI 类经常会这样:
#include <QSettings>
class CIni
{
public:
CIni()
{
m_setter = new QSettings("./dat.ini", QSettings::IniFormat);
m_a = m_setter->value("user/a", 0).toInt();
m_b = m_setter->value("user/b", 0).toFloat();
...
}
~CIni() {delete m_setter;}
// user
int a() const { return m_a; }
void setA(int a)
{
if (m_a == a) return;
m_a = a;
m_setter->setValue("user/a", a);
}
float b() const { return m_b; }
void setB(float b)
{
if (m_b == b) return;
m_b = b;
m_setter->setValue("user/b", b);
}
...
private:
QSettings *m_setter;
int m_a;
float m_b;
...
};
主要会出现如下问题:
1、这种方案虽然没啥问题,可是等数据一多起来,添加也不是很方便友好;
2、而且“键”出现了两次,还不在一起,代码过多也有可能导致前后不一致的问题。
所以要尽量避免bug的产生。
如何做出优化
其实定义变量 m_a、m_b ... 就是为了初始化读取文件,然后返回值;再就是在设置的时间进行比对;
可以通过集合数据对象进行管理,可以避免敲很多对象代码。
class CIni
{
public:
CIni()
{
m_setter = new QSettings("./dat.ini", QSettings::IniFormat);
QStringList keys = m_setter->allKeys();
foreach (QString key, keys) m_mapVals.insert(key, m_setter->value(key));
}
~CIni() {delete m_setter;}
// user
int a() const { m_mapVals.value("user/a", 0).toInt(); }
void setA(int a)
{
int tem = this->a();
if (tem == a) return;
m_mapVals.insert("user/a", a);
m_setter->setValue("user/a", a);
}
float b() const { m_mapVals.value("user/b", 0).toFloat(); }
void setB(float b)
{
float tem = this->b();
if (tem == b) return;
m_mapVals.insert("user/b", b);
m_setter->setValue("user/b", b);
}
...
private:
QSettings *m_setter;
QMap<QString, QVariant> m_mapVals;
};
这样可以减少成员定义和构造函数不停添加代码。
减少代码添加位置,只在一个地方添加。
进一步优化
现在只有一个位置存在变化,那就是该轮到 宏 登场了!
可以做如下修改:
#define INI_PARAM(Section, Type, Name, DefaultVal)\
Type get##Name() const { return m_mapVals.value(#Section"/"#Name, DefaultVal).value<Type>(); }\
void set##Name(const Type &Name) {\
Type val = get##Name();\
if (val == Name) return;\
val = Name;\
m_mapVals.insert(#Section"/"#Name, Name);\
setValue(#Section"/"#Name, Name);\
}
...
INI_PARAM(user, int, a, 0)
INI_PARAM(user, float, b, 0)
这样就不需要我们频繁的敲重复性代码了!
当然,对于头和尾也可以用一个宏简单的替换了。
而且对于配置文件来说,做一个简单的单实例封装,也更方便管理。
- 单实例的简单实现如下
#define Singleton(ClassName) \
static ClassName &instance() {\
static ClassName *m_instance = 0;\
if (m_instance == 0)\
m_instance = new ClassName;\
return *m_instance;\
}
最后的效果
按照所想的,则就是最开始需要实现的 "inier.h" 文件了;
具体实现如下:
#ifndef INIER_H
#define INIER_H
#include "defs.h" // 单实例的宏定义在里面
#include <QSettings>
#include <QString>
#define INI_BEGIN(FileName) \
class Inier {\
public:\
Singleton(Inier)\
private:\
Inier() {\
m_setter = new QSettings(FileName, QSettings::IniFormat);\
QStringList keys = m_setter->allKeys();\
foreach (QString key, keys) m_mapVals.insert(key, m_setter->value(key));\
}\
~Inier() {delete m_setter;}\
public:
#define INI_PARAM(Section, Type, Name, DefaultVal)\
Type get##Name() const { return m_mapVals.value(#Section"/"#Name, DefaultVal).value<Type>(); }\
void set##Name(const Type &Name) {\
Type val = get##Name();\
if (val == Name) return;\
val = Name;\
m_mapVals.insert(#Section"/"#Name, Name);\
setValue(#Section"/"#Name, Name);\
}
#define INI_END \
void setValue(const QString &name, const QVariant &val) { m_setter->setValue(name, val); m_setter->sync(); }\
QVariant value(const QString &name, const QVariant &defaultVal) { return m_setter->value(name, defaultVal); }\
private:\
QSettings *m_setter;\
QMap<QString, QVariant> m_mapVals;\
};
#define ini Inier::instance()
#endif // INIER_H
使用方法在文章开头已经写了,就不再赘述了,忘记的小伙伴们可以在文章开头查看。
总结
我的砖就抛到这里,希望对你们有用。
接下来的路就请各位小伙伴们自己走了!