四时宝库

程序员的知识宝库

C/C++编程笔记:超级简便的 Qt 读写 INI 配置方案

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

使用方法在文章开头已经写了,就不再赘述了,忘记的小伙伴们可以在文章开头查看。


总结

我的砖就抛到这里,希望对你们有用。
接下来的路就请各位小伙伴们自己走了!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接