Skip to content

Qt配置文件操作

配置文件的作用是在程序运行时,将一些需要持久化的数据存储到文件中,以便程序下次运行时可以读取这些数据。

常见配置文件类型

  • ini文件:常用的配置文件格式,qt本身也提供了对配置文件的支持
  • toml文件:ini文件的升级版本
  • json文件:不支持写注释
  • yaml文件:json文件的升级版本,支持注释
  • xml文件
  • sqlite文件:数据库文件,不常用

配置文件的设计

设计配置文件的目的是为了让程序更加灵活,为了支持多环境可以为每个环境使用不同的配置文件。但是也有一些配置信息是公用的,这些公用的配置信息可以以全局变量的形式写死在程序中,那些差异化的配置写在不同环境的配置文件中。

INI文件

.ini 文件是Initialization File的缩写,即初始化文件,是Windows的系统配置文件所采用的存储格式。文件扩展名:配置文件名称.ini。比如项目中的配置文件是整个项目共用的,例如:数据库配置.ini

除了windows现在很多其他操作系统下面的应用软件也有 .ini 文件,用来配置应用软件以实现不同用户的要求。一般不用直接编辑这些 .ini 文件,应用程序的图形界面即可操作以实现相同的功能。它可以用来存放软件信息,注册表信息等。

INI语法格式

INI 文件由节、键、值由三个部分组成:

ini

[section] 
参数 
(键=值) 
name=value;

注意:分号表示在分号后面的相关字符等信息,直到该行结尾都全部为注解内容。

ini
[sectionname]
Keynames=values

Qt如何读写INI文件

Qt 使用 QSettings 进行操作读写文件。

QSettings 类提供了一个统一的接口来访问不同存储设备上的应用程序和操作系统设置。

QSettings 可以读写注册表、Windows INI 文件、Unix INI 文件和 Mac OS X plist 文件。

写入配置文件

cpp
#include <QSettings>

int main(int argc, char *argv[])
{
    // 指定配置文件路径和格式
    QSettings *setting = new QSettings("config/config.ini", QSettings::IniFormat);

    // 写入配置文件
    // 使用组名/键名的方式
    setting->setValue("server/ip", "127.0.0.1");
    // 使用开始组名 设置键值对 结束组名的方式
    setting->beginGroup("mysql");
    setting->setValue("ip", "127.0.0.1");
    setting->setValue("port", 3306);
    setting->endGroup();

    // 写入结束删除指针
    delete setting;

    return 0;
}

读取配置文件

cpp
#include <QSettings>
#include <QDebug>

int main(int argc, char *argv[])
{
    QSettings *setting = new QSettings("config/config.ini", QSettings::IniFormat);
    qDebug() << "server ip:" << setting->value("server/ip").toString();
    qDebug() << "mysql ip:" << setting->value("mysql/ip").toString();
    qDebug() << "mysql port:" << setting->value("mysql/port").toString();

    return 0;
}

输出结果:

bash
server ip: "127.0.0.1"
mysql ip: "127.0.0.1"
mysql port: "3306"

更多读取方式可以参考官方文档。

JSON文件

在 Json的两种格式 中介绍了Json的格式以及应用场景。由于这种数据格式与语言无关,下面介绍一下Json在Qt中的使用。

Qt 5.0开始提供了对Json的支持,我们可以直接使用Qt提供的Json类进行数据的组织和解析。相关的类常用的主要有四个,具体如下:

Json类介绍
QJsonDocument它封装了一个完整的JSON文档,并且可以从UTF-8编码的基于文本的表示以及Qt自己的二进制格式读取和写入该文档。
QJsonArrayJSON数组是一个值列表。可以通过从数组中插入和删除QJsonValue来操作该列表。
QJsonObjectJSON对象是键值对的列表,其中键是唯一的字符串,值由QJsonValue表示。
QJsonValue该类封装了JSON支持的数据类型。

1. QJsonValue

在Qt中QJsonValue可以封装的基础数据类型有六种(和Json支持的类型一致),分别为:

  • 布尔类型:QJsonValue::Bool
  • 浮点类型(包括整形): QJsonValue::Double
  • 字符串类型: QJsonValue::String
  • Json数组类型: QJsonValue::Array
  • Json对象类型:QJsonValue::Object
  • 空值类型: QJsonValue::Null

这个类型可以通过QJsonValue的构造函数被封装为一个类对象:

cpp
// Json对象
QJsonValue(const QJsonObject &o);
// Json数组
QJsonValue(const QJsonArray &a);
// 字符串
QJsonValue(const char *s);
QJsonValue(QLatin1String s);
QJsonValue(const QString &s);
// 整形 and 浮点型
QJsonValue(qint64 v);
QJsonValue(int v);
QJsonValue(double v);
// 布尔类型
QJsonValue(bool b);
// 空值类型
QJsonValue(QJsonValue::Type type = Null);

如果我们得到一个QJsonValue对象,如何判断内部封装的到底是什么类型的数据呢?这时候就需要调用相关的判断函数了,具体如下:

cpp
// 是否是Json数组
bool isArray() const;
// 是否是Json对象
bool isObject() const;
// 是否是布尔类型
bool isBool() const;
// 是否是浮点类型(整形也是通过该函数判断)
bool isDouble() const;
// 是否是空值类型
bool isNull() const;
// 是否是字符串类型
bool isString() const;
// 是否是未定义类型(无法识别的类型)
bool isUndefined() const;

通过判断函数得到对象内部数据的实际类型之后,如果有需求就可以再次将其转换为对应的基础数据类型,对应的API函数如下:

cpp
// 转换为Json数组
QJsonArray toArray(const QJsonArray &defaultValue) const;
QJsonArray toArray() const;
// 转换为布尔类型
bool toBool(bool defaultValue = false) const;
// 转换为浮点类型
double toDouble(double defaultValue = 0) const;
// 转换为整形
int toInt(int defaultValue = 0) const;
// 转换为Json对象
QJsonObject toObject(const QJsonObject &defaultValue) const;
QJsonObject toObject() const;
// 转换为字符串类型
QString toString() const;
QString toString(const QString &defaultValue) const;

2. QJsonObject

QJsonObject封装了Json中的对象,在里边可以存储多个键值对,为了方便操作,键值为字符串类型,值为QJsonValue类型。关于这个类的使用类似于C++中的STL类,仔细阅读API文档即可熟练上手使用,下面介绍一些常用API函数:

  • 如何创建空的Json对象

    cpp
    QJsonObject::QJsonObject();	// 构造空对象
  • 将键值对添加到空对象中

    cpp
    iterator QJsonObject::insert(const QString &key, const QJsonValue &value);
  • 获取对象中键值对个数

    cpp
    int QJsonObject::count() const;
    int QJsonObject::size() const;
    int QJsonObject::length() const;
  • 通过key得到value

    cpp
    QJsonValue QJsonObject::value(const QString &key) const;    // utf8
    QJsonValue QJsonObject::value(QLatin1String key) const;	    // 字符串不支持中文
    QJsonValue QJsonObject::operator[](const QString &key) const;
    QJsonValue QJsonObject::operator[](QLatin1String key) const;
  • 删除键值对

    cpp
    void QJsonObject::remove(const QString &key);
    QJsonValue QJsonObject::take(const QString &key);	// 返回key对应的value值
  • 通过key进行查找

    cpp
    iterator QJsonObject::find(const QString &key);
    bool QJsonObject::contains(const QString &key) const;
  • 遍历,方式有三种:

    • 使用相关的迭代器函数

    • 使用 [] 的方式遍历, 类似于遍历数组, []中是键值

    • 先得到对象中所有的键值, 在遍历键值列表, 通过key得到value值

      cpp
      QStringList QJsonObject::keys() const;

3. QJsonArray

QJsonArray封装了Json中的数组,在里边可以存储多个元素,为了方便操作,所有的元素类统一为QJsonValue类型。关于这个类的使用类似于C++中的STL类,仔细阅读API文档即可熟练上手使用,下面介绍一些常用API函数:

  • 创建空的Json数组

    cpp
    QJsonArray::QJsonArray();
  • 添加数据

    cpp
    void QJsonArray::append(const QJsonValue &value);	// 在尾部追加
    void QJsonArray::insert(int i, const QJsonValue &value); // 插入到 i 的位置之前
    iterator QJsonArray::insert(iterator before, const QJsonValue &value);
    void QJsonArray::prepend(const QJsonValue &value); // 添加到数组头部
    void QJsonArray::push_back(const QJsonValue &value); // 添加到尾部
    void QJsonArray::push_front(const QJsonValue &value); // 添加到头部
  • 计算数组元素的个数

    cpp
    int QJsonArray::count() const;
    int QJsonArray::size() const;
  • 从数组中取出某一个元素的值

    cpp
    QJsonValue QJsonArray::at(int i) const;
    QJsonValue QJsonArray::first() const; // 头部元素
    QJsonValue QJsonArray::last() const; // 尾部元素
    QJsonValueRef QJsonArray::operator[](int i);
  • 删除数组中的某一个元素

    cpp
    iterator QJsonArray::erase(iterator it);    // 基于迭代器删除
    void QJsonArray::pop_back();           // 删除尾部
    void QJsonArray::pop_front();          // 删除头部
    void QJsonArray::removeAt(int i);      // 删除i位置的元素
    void QJsonArray::removeFirst();        // 删除头部
    void QJsonArray::removeLast();         // 删除尾部
    QJsonValue QJsonArray::takeAt(int i);  // 删除i位置的原始, 并返回删除的元素的值
  • Josn数组的遍历,常用的方式有两种:

    • 可以使用迭代器进行遍历(和使用迭代器遍历STL容器一样)
    • 可以使用数组的方式遍历

4. QJsonDocument

它封装了一个完整的JSON文档,并且可以从UTF-8编码的基于文本的表示以及Qt自己的二进制格式读取和写入该文档。QJsonObject 和 QJsonArray这两个对象中的数据是不能直接转换为字符串类型的,如果要进行数据传输或者数据的持久化,操作的都是字符串类型而不是 QJsonObject 或者 QJsonArray类型,我们需要通过一个Json文档类进行二者之间的转换。

下面依次介绍一下这两个转换流程应该如何操作:

  • QJsonObject 或者 QJsonArray ===> 字符串

    1. 创建QJsonDocument对象

      cpp
      QJsonDocument::QJsonDocument(const QJsonObject &object);
      QJsonDocument::QJsonDocument(const QJsonArray &array);

      可以看出,通过构造函数就可以将实例化之后的**QJsonObject 或者 QJsonArray **转换为QJsonDocument对象了。

    2. 将文件对象中的数据进行序列化

      cpp
      // 二进制格式的json字符串
      QByteArray QJsonDocument::toBinaryData() const;	 
      // 文本格式
      QByteArray QJsonDocument::toJson(JsonFormat format = Indented) const;

      通过调用toxxx()方法就可以得到文本格式或者二进制格式的Json字符串了。

    3. 使用得到的字符串进行数据传输, 或者磁盘文件持久化

  • 字符串 ===> QJsonObject 或者 QJsonArray

    一般情况下,通过网络通信或者读磁盘文件就会得到一个Json格式的字符串,如果想要得到相关的原始数据就需要对字符串中的数据进行解析,具体解析流程如下:

    1. 将得到的Json格式字符串通过 QJsonDocument 类的静态函数转换为QJsonDocument类对象

      cpp
      [static] QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidation validation = Validate);
      // 参数文件格式的json字符串
      [static] QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error = Q_NULLPTR);
    2. 将文档对象转换为json数组/对象

      cpp
      // 判断文档对象中存储的数据是不是数组
      bool QJsonDocument::isArray() const;
      // 判断文档对象中存储的数据是不是json对象
      bool QJsonDocument::isObject() const
          
      // 文档对象中的数据转换为json对象
      QJsonObject QJsonDocument::object() const;
      // 文档对象中的数据转换为json数组
      QJsonArray QJsonDocument::array() const;
    3. 通过调用QJsonArray , QJsonObject 类提供的 API 读出存储在对象中的数据。

    关于Qt中Json数据对象以及字符串之间的转换的操作流程是固定的,我们在编码过程中只需要按照上述模板处理即可,相关的操作是没有太多的技术含量可言的。

5. 实例

5.1 写文件

cpp
void writeJson()
{
    QJsonObject obj;
    obj.insert("Name", "Ace");
    obj.insert("Sex", "man");
    obj.insert("Age", 20);

    QJsonObject subObj;
    subObj.insert("Father", "Gol·D·Roger");
    subObj.insert("Mother", "Portgas·D·Rouge");
    QJsonArray array;
    array.append("Sabo");
    array.append("Monkey D. Luffy");
    subObj.insert("Brother", array);
    obj.insert("Family", subObj);
    obj.insert("IsAlive", false);
    obj.insert("Comment", "yyds");

    QJsonDocument doc(obj);
    QByteArray json = doc.toJson();

    QFile file("d:\\ace.json");
    file.open(QFile::WriteOnly);
    file.write(json);
    file.close();
}

5.2 读文件

cpp
void MainWindow::readJson()
{
    QFile file("d:\\ace.json");
    file.open(QFile::ReadOnly);
    QByteArray json = file.readAll();
    file.close();

    QJsonDocument doc = QJsonDocument::fromJson(json);
    if(doc.isObject())
    {
        QJsonObject obj = doc.object();
        QStringList keys = obj.keys();
        for(int i=0; i<keys.size(); ++i)
        {
            QString key = keys.at(i);
            QJsonValue value = obj.value(key);
            if(value.isBool())
            {
                qDebug() << key << ":" << value.toBool();
            }
            if(value.isString())
            {
                qDebug() << key << ":" << value.toString();
            }
            if(value.isDouble())
            {
                qDebug() << key << ":" << value.toInt();
            }
            if(value.isObject())
            {
                qDebug()<< key << ":";
                // 直接处理内部键值对, 不再进行类型判断的演示
                QJsonObject subObj = value.toObject();
                QStringList ls = subObj.keys();
                for(int i=0; i<ls.size(); ++i)
                {
                    QJsonValue subVal = subObj.value(ls.at(i));
                    if(subVal.isString())
                    {
                        qDebug() << "   " << ls.at(i) << ":" << subVal.toString();
                    }
                    if(subVal.isArray())
                    {
                        QJsonArray array = subVal.toArray();
                        qDebug() << "   " << ls.at(i) << ":";
                        for(int j=0; j<array.size(); ++j)
                        {
                            // 因为知道数组内部全部为字符串, 不再对元素类型进行判断
                            qDebug() << "       " << array[j].toString();
                        }
                    }
                }
            }
        }
    }
}

一般情况下,对于Json字符串的解析函数都是有针对性的,因为需求不同设计的Json格式就会有所不同,所以不要试图写出一个通用的Json解析函数,这样只会使函数变得臃肿而且不易于维护,每个Json格式对应一个相应的解析函数即可。

上面的例子中为了给大家演示Qt中Json类相关API函数的使用将解析步骤写的复杂了,因为在解析的时候我们是知道Json对象中的所有key值的,可以直接通过key值将对应的value值取出来,因此上面程序中的一些判断和循环其实是可以省去的。

XML文件

可扩展标记语言(Extensible Markup Language, XML) ,标准通用标记语言的子集,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML 是标准通用标记语言可扩展性良好,内容与形式分离,遵循严格的语法要求,保值性良好等优点。

xml文件信息

xml
<?xml version="1.0" encoding="UTF-8"?>
<data>
    <database>
        <ip>127.0.0.1</ip>
        <port>8080</port>
        <databaseversion>QSQLITE</databaseversion>
        <databasename>student.db</databasename>
        <username>root</username>
        <password>123</password>
    </database>
</data>

qreadxml.h文件内容

cpp
#ifndef QREADXML_H
#define QREADXML_H
#include<QString>
#include<QtXml>
#include<QFile>
#include<QIODevice>
#include<QDomNodeList>

#define XMLCONFIG  QReadXml::getInstance()->getXmlConfig()

typedef struct XmlConfig
{
    QString ip;                //IP地址
    QString port;              //端口
    QString dataBaseVersion;   //数据库版本
    QString dataBaseName;      //数据库名称
    QString userName;          //用户名
    QString passWord;          //密码
}XmlConfig;

class QReadXml
{
public:
    static QReadXml * getInstance();
    bool readXML();
    const XmlConfig &getXmlConfig();
private:
    QReadXml();
    XmlConfig xmlConfig;
    static QReadXml * instance;
};


#endif // QREADXML_H

qreadxml.cpp文件内容

cpp
#include "qreadxml.h"

QReadXml *QReadXml::instance = NULL;

QReadXml *QReadXml::getInstance()
{
    if(NULL == instance)
    {
        instance = new QReadXml();
    }

    return instance;
}

QReadXml::QReadXml()
{
    this->readXML();
}

bool QReadXml::readXML()
{
    int countData = 0;
    QDomDocument dom;
    QFile *file=new QFile("config.xml");
    if(file->open(QIODevice::ReadOnly))
    {
        if(!dom.setContent(file))  //将该xml文件导入到dom中
        {
            file->close();
            return false;
        }
    }
    else
    {
        return false;
    }
    file->close();
    QDomElement docElem = dom.documentElement();   //返回根元素
    QDomNode node = docElem.firstChild();          //返回根节点的第一个子节点

    while(!node.isNull())                        //如果节点不为空
    {
        if(node.isElement())                     //如果节点是元素
        {
            QDomElement element=node.toElement();//将其转化为元素
            QDomNodeList list=element.childNodes();//取出该元素的所有子节点放到list中

            //将子节点元素全部取出来
            for(int i = 0; i < list.count(); i ++)
            {
                QDomNode domNode = list.at(i);
                if(domNode.isElement())
                {
                    //取出我们所要的数据
                    switch(i)
                    {
                    case 0:xmlConfig.ip = domNode.toElement().text();break;
                    case 1:xmlConfig.port = domNode.toElement().text();break;
                    case 2:xmlConfig.dataBaseVersion = domNode.toElement().text();break;
                    case 3:xmlConfig.dataBaseName = domNode.toElement().text();break;
                    case 4:xmlConfig.userName = domNode.toElement().text();break;
                    case 5:xmlConfig.passWord = domNode.toElement().text();break;
                    }
                }
            }
        }

        countData++;
        node = node.nextSibling(); //下一个兄弟节点
    }

    return countData > 0 ? true : false;
}

const XmlConfig &QReadXml::getXmlConfig()
{
    return xmlConfig;
}

Qt中的单例模式

在Qt中实现一个单例模式的类是非常简单的,通过以下几步即可完成:

TODO:

正则表达式

Qt中使用QRegExp类来支持正则表达式。

1、元字符规则

要使用正则表达式进行匹配或者验证,必须了解正则表达式的书写规则,其书写规则如下

  • ^标识规则字符串的开始
  • $标识规则字符串的结束
  • []内书写要匹配什么字符,如
    • [A-Z]表示匹配大写A到Z之间的任意一个字符
    • [0-9]表示匹配0到9之间的任意一个数字
    • [A-Z0-9a-z]为组合模式,表示匹配任意一个大小写字母或数字,配合下面的匹配次数即可组成复杂的正则表达式
    • [_\.]则表示只匹配下划线或者..使用了\进行转义
  • +表示匹配次数≥1次
  • *表示匹配任意次数(可以是0次)
  • {m, n}表示至少匹配m次,至多匹配n次

2、正则表达式匹配示例

cpp
// 定义一个匹配输入的邮箱是否正确的正则字符串规则
QRegExp rx("^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$");
// 调用exactMatch匹配传入的字符串是否和规则相匹配
bool res = rx.exactMatch(ui->LineEdit->text());
if(!res){// 匹配成功
    qDebug() << "匹配成功";
}else{
    qDebug() << "匹配失败";
}