qt基础知识


基于窗口的应用程序

每次打开qt创建程序的时候,会有QMainWindow、QWidget、QDialog三种窗口类供我们选择,他们的特点和继承关系如下所示:
不同窗口类的继承关系和特点

qt的内存管理机制

qt对于内存回收提供了一种对象树的机制,而QObject就是以对象树的形式组织起来的,所有继承QObject的类同时也会继承这种对象树关系。当创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构,我们以创建的Qwidget窗口为例:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

这段代码的意思就是:用parent参数(不传递的时候默认是nullptr)来指定Widget类对象的父对象,用初始化列表的方式,调用基类QWidget的构造函数并把参数parent传给它进行初始化,同时对成员变量ui进行初始化(ui对象对应着我们的界面文件)。这样我们在创建Wigdet对象后,其可以被对象树所管理

需要声明的有两点:
1.所有继承自 QObject 的对象,只要它们被正确地设置了父对象,都会被对象树管理。这包括栈上分配的对象以及通过 new 运算符在堆上分配的对象。
2.Qt中大部分类是继承自QObject的,比如所有的 QWidget 派生类(如 QPushButton, QLabel 等),大多数 Qt 核心模块类(如 QTimer, QThread, QFile, QApplication 等),但是Qt 的基本数据结构类(如 QString, QList, QVector 等)、自定义类都不继承于它,对于这些对象需要我们自己管理,或者借助智能指针来高效管理内存

关于更详细的解读可以参考StackOverflow的提问

qt信号槽机制

1.介绍
主要用于实例对象间的通信,某个事件发生以后(比如按键按下),它就发出一个信号(类似于广播),如果希望有对象(槽)进行事件的响应,就用QObject类的connect函数把他们连接起来,实现一个事件的注册。该机制同时支持信号连接信号,当前一个信号发出时,后一个信号也随之发射

回调:通常是指将一个函数作为参数传递给另一个函数,并在适当的时候由后者调用前者。回调函数通常用于处理事件或响应异步操作

connect函数一般定义在构造函数中,在使用它之前应先在头文件中创建好发送信号的对象和拥有槽函数的对象,并在构造函数中对他们初始化

connect(const QObject *sender, &QObject::signal, const QObject *receiver, &QObject::method);
参数:
  - sender:   发出信号的对象
  - signal:   属于sender对象, 信号是一个函数, 这个参数的类型是函数
              指针, 信号函数地址
  - receiver: 信号接收者
  - method:   属于receiver对象, 当检测到sender发出了signal信号, 
              receiver对象调用method方法,信号发出之后的处理动作

2.标准信号槽的使用
qt中的类提供了一些信号和槽函数,只需要用connect连接他们,就可以在事件发生的时候执行,比如通过按钮实现窗口的关闭

connect(ui->closeButton, &QPushButton::clicked, this, &Widget::close);

3.自定义信号槽的使用
定义自己的信号和槽要满足以下的条件:

  • 编写新的类从QObject类或者是QObject子类(比如QWidget)进行派生
  • 在类的头文件定义中加入 Q_OBJECT 宏
class MyMainWindow : public QWidget
{
    Q_OBJECT
    ......
}

自定义信号:

  • 信号是类的成员函数,且返回值必须是void类型
  • 信号函数只需要在头文件中声明,不需要定义
  • 声明时要使用signals关键字,类似于public
  • 参数可随意指定,实参最终会传递给槽函数
  • 用emit来发射(调用)信号

自定义槽:

  • 返回值必须是 void 类型
  • 槽函数需要指定多少个参数, 需要看连接的信号的参数个数,可以小于等于,但是不能多于信号的参数个数
  • 槽函数的参数是用来接收信号传递的数据的,信号传递的数据就是信号的参数,因此参数类型要对应
  • 槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式
  • 用关键字slots来声明

示例

    connect(m_cat, &Cat::hungry, m_me, &me::food);
    connect(ui->closeButton, &QPushButton::clicked, this, &Widget::close);

qt样式表

qt的样式表又叫做qss,是qt程序界面中用来设置控件的背景图片、大小、字体颜色、字体类型、按钮状态变化等属性,实现UI界面的美化。

ui的设置一般在窗口的构造函数中实现

1.qss语法

  • 选择器 {property:value}
    花括号里面就是属性和属性的设置值,eg:QPushButton {color:red}
    也可以对多个选择器进行设置,用","来分割它们 ,eg:QPushButton,QLineEdit{color:red}
    或者对一个选择器设置多个属性:eg:QPushButton{color:red;background-color:white;}

2.样式表设置方法

  • 通过样式表设置函数setStyleSheet
    通过这个函数来设置控件的外观,比如在下面的代码中设置了按钮按下时的显示样式和不按时的显示样式
//当字符粘合的时候用一个\来表示一行
    ui->pushButton->setStyleSheet(QString("QPushButton:pressed { \
                                          background-color:rgb(200,200,200);\
                                          border-style:insert;\
                                          color:rgb(0,255,0);\
                                          }\
                                          QPushButton{color:rgb(255,0,0)}"));
  • 在ui界面右键控件,选择改变样式表

3.常见样式
QSS常见样式

qt定时器

定时器是QT中很有用的一个工具,比如我想实现视觉上的图片动态切换,或者一些延时的处理,常用API如下所示:

  • 成员函数/槽函数
// 构造函数
// 最好指定一下父对象, 创建的堆内存可以自动析构,如果是在当前窗口中进行,直接使用this指针指定即可
QTimer::QTimer(QObject *parent = nullptr);

//启动或重新启动定时器,超时间隔为msec毫秒
[slot] void QTimer::start(int msec)

//设置定时器的精度
参数: 
    - Qt::PreciseTimer -> 精确的精度, 毫秒级
    - Qt::CoarseTimer  -> 粗糙的精度, 和1毫秒的误差在5%的范围内, 默认精度
    - Qt::VeryCoarseTimer -> 非常粗糙的精度, 精度在1秒左右
void QTimer::setTimerType(Qt::TimerType atype);

// 如果定时器正在运行,返回true; 否则返回false。
bool QTimer::isActive() const;

// 判断定时器是否只触发一次
bool QTimer::isSingleShot() const;
// 设置定时器是否只触发一次, 参数为true定时器只触发一次, 为false定时器重复触发, 默认为false
void QTimer::setSingleShot(bool singleShot);
  • 信号
    QTimer类中的信号只有一个,当定时器超时的时候,该信号就会被发出,用connect去关联一个槽函数来处理超时事件
[signal] void QTimer::timeout();
  • 静态成员函数
    不需要创建对象就能用,让信号在msec毫秒后发射一次信号, 并且只发射一次
参数:
	- msec:     在msec毫秒后发射信号
	- receiver: 接收信号的对象地址
	- method:   槽函数地址

[static] void QTimer::singleShot(
        int msec, const QObject *receiver, 
        PointerToMemberFunction method);

测试一下上述API,ui界面设置两个按钮,第一个按钮按下显示当前时间,定时器超时间隔设置为1s,也就是每隔1s发送一次超时信号,进行时间的显示,再按一下停止定时器。第二个按钮按下只发送一次信号,显示一次当前时间

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->beginBtn->setStyleSheet(QString("QPushButton{color:rgb(0,0,255)}"));
    ui->curTimeLabel->setStyleSheet(QString("QLabel{background-color:rgb(0,255,0)};\
                                                    font-size:16px"));
    QTimer *timer = new QTimer(this);

    //当前窗口对象去执行匿名函数
    connect(ui->beginBtn, &QPushButton::clicked, this, [=]() {
        if (timer->isActive()) {
            timer->stop();
            ui->beginBtn->setText("开始");
        } else {
            ui->beginBtn->setText("关闭");
            //每隔一秒刷新一次
            timer->start(1000);
        }
    });
    connect(timer, &QTimer::timeout, this, [=]() {
        QTime tm = QTime::currentTime();
        QString tmstr = tm.toString("hh:mm:ss");
        ui->curTimeLabel->setText(tmstr);
    });

    connect(ui->onceShowBtn, &QPushButton::clicked, this, [=]() {
        QTimer::singleShot(1000, this, [=]() {
            QTime tm = QTime::currentTime();
            QString tmstr = tm.toString("hh:mm:ss");
            ui->onceShowLabel->setText(tmstr);
        });
    });
}

qt事件

qt编写的程序大多都是基于窗口的,对窗口的各种操作会产生各种各样的事件。一些事件在对用户操作做出响应时发出,比如按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件;另一些事件则是由系统自动发出,如计时器事件。窗口可以是嵌套的,当事件发生以后就要由对应的子窗口去处理,我们需要了解的一个问题就是:当一个事件发生以后,是怎么通过qt框架发送到对应的窗口里去的

事件路由:
事件派发(发往对应的窗口)->事件过滤->事件分发->事件处理
Qt窗口中对于产生的一系列事件都有默认的处理动作,也就是上述的流程通常由qt框架来实现,但是我们可以对事件处理函数进行重写,这些事件处理器函数通常可以在帮助文档的protected functions中进行查看

重写事件处理器函数:

  • 创建一个继承自窗口类的子类
  • 子类重写父类的虚函数
  • 重写的事件处理器函数需要在头文件的类中声明为protected,并且用override表示自己要进行重写操作

自定义控件的实现:

  • 继承自QWidget或其他控件类
  • 根据需求重载 paintEvent、mousePressEvent、keyPressEvent 等事件处理函数,以实现自定义的行为和外观
  • 添加自定义属性和方法
  • 将已有的QWidget实例提升为Qt控件

qt数据库操作

qt中对于数据库操作的类主要有以下几个
在这里插入图片描述
1.QSqlDatabase常用方法

  • 添加数据库连接
- type:数据库驱动类型,如 "QSQLITE", "QMYSQL", "QPSQL" 等
- connectionName:连接名称,可以用于标识多个数据库连接
QSqlDatabase db = QSqlDatabase::addDatabase(const QString &type, const QString &connectionName = QLatin1String(QSqlDatabase::defaultConnection));
  • 关闭数据库连接
- connectionName:要移除的连接名称。移除后应确保没有任何活动的 QSqlQuery 或 QSqlTableModel 使用该连接。
QSqlDatabase::removeDatabase(const QString &connectionName);
  • 检查数据库连接是否有效
bool isValid() const;
  • 打开和关闭数据库
bool open();//打开数据库连接
bool open(const QString &user, const QString &password);//需要用户名和密码的数据库
void close();
  • 设置数据库属性
void setDatabaseName(const QString &name);//设置数据库名字,对于QSQLITE驱动程序,如果指定的数据库名不存在,那么它将创建文件
void setUserName(const QString &name);//设置用户名
void setPassword(const QString &password);//设置密码
void setHostName(const QString &host);//设置主机名
void setPort(int port);//设置端口
QString connectionName() const;//获取连接名

2.QSqlQuery类常用方法

  • 构造函数
QSqlQuery(const QSqlDatabase &db);//创建一个绑定到指定数据库连接的查询对象
  • 执行sql语句
bool exec(const QString &query);执行指定的sql语句
  • 检索结果集
bool next();//移动到下一条行记录
QVariant value(int index) const;//根据列索引获取当前记录的值,列索引是列的序号,比如第一列是0,第二列索引为1.....
QVariant value(const QString &name) const;//根据列名获取当前记录的值,列名是我们定义的字符串标识
  • 获取查询信息
    获取这些查询信息的时候
QString lastQuery() const;//获取最后执行的查询字符串
QSqlError lastError() const;//获取最后一个错误信息,返回的是一个QSqlError类型对象,继续调用databasetext可获取错误信息
QSqlRecord record() const;//获取结果集的当前行记录

3.代码示例:

	//删除旧表
    QFile::remove("test.db");

    //指定数据库类型
    QSqlDatabase myDb = QSqlDatabase::addDatabase("QSQLITE");
    //创建并打开
    myDb.setDatabaseName("test.db");
    if (!myDb.open()) {
        qDebug() << myDb.lastError().databaseText();
        return -1;
    }
    QString sql
        = "create table if not exists PlayersTable(序号 int primary key,名字 text,位置 text)";
    //创建一个操作数据库的对象
    QSqlQuery query(myDb);
    if (!query.exec(sql)) {
        qDebug() << query.lastError().databaseText();
        return -1;
    }
    sql = "insert into PlayersTable values(1,'舍甫琴科','中锋')";
    if (!query.exec(sql)) {
        qDebug() << query.lastError().databaseText();
        return -1;
    }
    sql = "select * from PlayersTable";
    if (!query.exec(sql)) {
        qDebug() << query.lastError().databaseText();
        return -1;
    }
    while (query.next()) {
        int id = query.record().value(0).toInt();
        QString name = query.record().value(1).toString();
        QString pos = query.record().value(2).toString();
        qDebug() << "id:" << id << "name:" << name << "pos:" << pos;
    }

qt数据模型与视图

Qt 的数据模型和视图框架用于管理和显示复杂的数据集,它将数据和用户界面分离,使得数据可以独立于其显示方式进行管理。其中模型是应用对象,用来进行数据的存放和管理,视图是用户界面,负责把模型中的数据呈现给用户

  • 模型
    Qt的所有模型类都是基于QAbstractItemModel虚类,这个定义了接口,可以供视图和代理来访问数据。Qt也提供了一些现成的模型来处理数据项:
QStringListModel用来存储一个简单的QString项目列表;
QStandardItemModel管理复杂的树型结构数据项,每一个数据项可以包含任意的数据;
QFileSystemModel提供了本地文件系统中文件和目录的信息;
QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel用来访问数据库
  • 视图
    Qt提供了几种不同类型的视图,这些类都基于QAbstractItemView抽象基类,这些类可以直接使用,还可以被子类化来提供定制的视图
QListView将数据项显示为一个列表
QTableView将模型中的数据显示在一个表格中
QTreeView将模型的数据项显示在具有层次的列表中。

代码示例:
用QSqlQueryModel模型类封装查询到的sql结果集,用tableview来显示,用combox下拉框显示不同位置选项,用户可点击查看不同位置的球员

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    tableModel = new QSqlQueryModel(this);
    comboxModel = new QSqlQueryModel(this);

    myDb = QSqlDatabase::addDatabase("QSQLITE");
    myDb.setDatabaseName("test.db");
    if (!myDb.open()) {
        qDebug() << "打开失败";
        return;
    }

    QSqlQuery query(myDb);
    QString sql = "select * from PlayersTable";
    tableModel->setQuery(sql);           //将数据库中的数据放到tablemodel对象中
    ui->tableView->setModel(tableModel); //执行多态,设置数据模型
    sql = "select 位置 from PlayersTable group by 位置";
    comboxModel->setQuery(sql);
    ui->comboBox->setModel(comboxModel);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_comboBox_currentTextChanged(const QString &arg1)
{
    QString sql;
    sql = QString("select * from PlayersTable where 位置 = '%1'").arg(arg1);
    tableModel->setQuery(sql);
}

qt容器

qt中的容器在使用方法上与标准库中的容器差别不大,但是大多数qt容器在内存管理上使用使用了引用计数和写时复制机制,这使得他们在大量数据插入或删除时的效率要高于标准库的容器

一些有较大区别的容器如下所示:
在这里插入图片描述

qt多线程

qt中主线程通常用来进行页面事件的响应,如果只有一个线程去处理各种各样的事情,很容易造成界面卡顿,这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多线程工作,提升程序的执行效率

1.qt多线程使用注意事项
要遵循一个子线程->主线程->ui界面的一个数据设置方式

  • 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
  • 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制

2.常用API

  • 公共成员函数
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;

// Qt中的线程优先级设置
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);//参数是一个枚举类型

//让线程完成当前正在处理的事件后就退出
void QThread::exit(int returnCode = 0);
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);
  • 信号槽
信号:
// 线程中执行的任务完成了, 发出该信号
[signal] void QThread::finished();
槽:
// 代用这个函数之后, 再调用 wait() 函数,和exit效果差不多
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
  • 静态成员函数
// 返回一个指向管理当前执行线程的QThread的指针,方便获取当前线程信息
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数,和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
  • 任务处理函数
    run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是对应的任务处理流程。另外,这个函数是一个受保护的成员函数,不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()启动子线程,当子线程被启动,这个run()函数也就在线程内部被调用了。
// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

3.多线程使用方法
方式一:重写run方法,在run函数内部编写子线程要执行的业务逻辑,缺点是在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护

  • 创建一个线程类的子类,让其继承QT中的线程类 QThread
  • 重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程
  • 主线程中创建子线程对象
  • 启动子线程, 调用 start() 方法

方式二:创建工作对象并移到线程子对象中,这种方式更灵活

  • 创建一个新的类,让这个类从QObject派生
  • 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
  • 在主线程中创建一个QThread对象, 这就是子线程的对象
  • 在主线程中创建工作的类对象(无需指定父对象)
  • 将工作类对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法
  • 调用 start()启动子线程,这时候线程启动了, 但是移动到线程中的对象并没有工作
  • 调用工作类对象的工作函数(通常用信号槽的方式),让这个函数开始执行,这时候是在移动到的那个子线程中运行的

qt网络通信

qt提供了用于套接字通信的类(基于TCP或UDP),两种通信方式各自的特点如下:
在这里插入图片描述
qt提供的基于TCP进行套接字通信的类都属于网络模块network,主要用到两个类:

  • QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
  • QTcpSocket:通信的套接字类,客户端、服务器端都需要使用

QTcpServer常用API:

//构造函数
QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);

- 设置监听
//设置监听,用Any表示自动绑定,端口指定0表示随机绑定,最好进行端口的指定
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// 判断当前对象是否在监听, 是返回true,没有监听返回false
bool QTcpServer::isListening() const;
// 如果当前对象正在监听返回监听的服务器地址信息, 否则返回 QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const;
// 如果服务器正在侦听连接,则返回服务器的端口; 否则返回0
quint16 QTcpServer::serverPort() const

//得到和客户端建立连接之后用于通信的QTcpSocket套接字对象
QTcpSocket *QTcpServer::nextPendingConnection();

- 信号
//有新连接可用时会发出该信号
[signal] void QTcpServer::newConnection();

QTcpSocket常用API:

- 服务器连接
//连接服务器,需要服务器绑定的ip地址和端口
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);

- 读数据
// 指定可接收的最大字节数 maxSize 的数据到指针 data 指向的内存中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大字节数 maxSize,返回接收的字符串
QByteArray QIODevice::read(qint64 maxSize);
// 将当前可用操作数据全部读出,通过返回值返回读出的字符串
QByteArray QIODevice::readAll();

- 写数据
// 发送指针 data 指向的内存中的 maxSize 个字节的数据
qint64 QIODevice::write(const char *data, qint64 maxSize);
// 发送指针 data 指向的内存中的数据,字符串以 \0 作为结束标记
qint64 QIODevice::write(const char *data);
// 发送参数指定的字符串
qint64 QIODevice::write(const QByteArray &byteArray);

- 信号
//QTcpSocket对象发射出readyRead()信号,说明对端发送的数据达到了,之后就可以调用 read 函数接收数据了
[signal] void QIODevice::readyRead();
//调用connectToHost()函数并成功建立连接之后发出connected()信号
[signal] void QAbstractSocket::connected();
//在套接字断开连接时发出disconnected()信号
[signal] void QAbstractSocket::disconnected();

有一点需要注意的是,我们进行数据读写的时候,操作的都是qt框架为我们维护的内存,而不是直接从网络上读写数据

qt网络请求

1.QNetworkAccessManager
该类用于发送网络请求,提供了get、post等HTTP请求方法

  • 公共成员函数
//构造函数
QNetworkAccessManager(QObject *parent = nullptr)
//get请求,从指定资源请求数据
QNetworkReply * get(const QNetworkRequest &request)
//post请求,向指定资源提交要被处理的数据
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data)
  • 信号
//网络请求完成时发出,这个信号提供了一个 QNetworkReply 对象,该对象包含了与请求相关的所有响应数据和状态信息
[siginal] void finished(QNetworkReply *reply)

2.QUrl
QUrl 是 Qt 中用于处理 URL 的类,提供了方便的方法来解析、构建和操作 URL

//构造函数
QUrl(const QString &ur)
//设置查询参数
void	setQuery(const QUrlQuery &query)

3.QUrlQuery
QUrlQuery 是 Qt 中处理 URL 查询参数的类,它提供了对 URL 查询部分(通常是 URL 中的键值对)的操作功能,如解析、构建和操作查询字符串。QUrlQuery 类支持对 URL 查询参数进行添加、删除、修改和编码。

//默认构造
QUrlQuery()
//添加查询参数,添加一个键值对到查询项中
void addQueryItem(const QString &key, const QString &value)
//返回与指定键关联的所有值,第二个参数为编码格式,一般用默认的即可
QStringList allQueryItemValues(const QString &key, QUrl::ComponentFormattingOptions encoding = QUrl::PrettyDecoded) const

4.QNetworkRequest
表示一个网络请求

//构造函数
QNetworkRequest(const QUrl &url);

5.QNetworkReply
表示网络请求的响应,要从这里面往出读数据

//返回一个枚举类型,表示本次请求出现了什么问题,NoError表示没有错误
QNetworkReply::NetworkError error() const
//以QByteArray的形式返回所有内容
QByteArray readAll()
//返回发生的错误类型
QString errorString()

网络请求流程
1.用QNetworkAccessManager创建网络请求管理器
2.用QUrl和QUrlQuery设置请求的url和参数
3.用QNetworkRequest创建网络请求对象
4.发送网络请求get、post等
5.处理响应并读取数据,响应到达时,QNetworkAccessManager 将触发相应的信号,如 finished(QNetworkReply*),用 QNetworkReply 对象的方法读取响应数据,比如 readAll()、read() 等。

6.示例程序
创建一个控制台应用程序,请求目标是一个免费的天气API

  • networkmessager.h
#ifndef NETWORKMANAGER_H
#define NETWORKMANAGER_H

#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QObject>
#include <QUrlQuery>

class NetworkManager : public QObject
{
    Q_OBJECT
public:
    NetworkManager(QObject *parent = nullptr);

private slots:
    void onFinished(QNetworkReply *reply);

private:
    QNetworkAccessManager *manager;
};

#endif // NETWORKMANAGER_H

  • networkmessager.cpp
#include "networkmanager.h"

NetworkManager::NetworkManager(QObject *parent)
    : QObject(parent)
{
    manager = new QNetworkAccessManager(this);
    connect(manager, &QNetworkAccessManager::finished, this, &NetworkManager::onFinished);

    // 构建 URL 和查询参数
    QUrl url("https://cn.apihz.cn/api/tianqi/tqyb.php");
    QUrlQuery query;
    query.addQueryItem("id", "88888888");
    query.addQueryItem("key", "88888888");
    query.addQueryItem("sheng", "江苏");
    query.addQueryItem("place", "苏州");
    url.setQuery(query);

    // 创建请求
    QNetworkRequest request(url);

    // 发送 GET 请求
    manager->get(request);
}

void NetworkManager::onFinished(QNetworkReply *reply)
{
    if (reply->error() == QNetworkReply::NoError) {
        QByteArray responseData = reply->readAll();
        qDebug() << responseData;
        QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
        if (!jsonDoc.isNull()) {
            QJsonObject jsonObj = jsonDoc.object();
            QString key = "temperature";
            qDebug() << "天气:" << jsonObj.value(key).toDouble();
        }
        qDebug() << "请求成功";
    } else {
        qDebug() << "错误:" << reply->errorString();
    }
    reply->deleteLater();
}

qt中的Json解析

JSON是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率,最主要的是它与语言无关,在什么语言中都可以实用Json

我们主要用Json进行一个数据序列化和反序列话的一个操作,序列化就是把不同类型的数据组织起来成为一个json字符串,方便数据的网络传输或者磁盘存储,反序列化就是一个json字符串解析的过程

1.json数据类型
json中的六种数据类型:整形、浮点、字符串、布尔类型、json数组、json对象、空值。其中数组和对象是主要的数据格式,且两种格式可以交叉嵌套使用

  • json数组
    用 [] 表示,[]里边是元素,元素和元素之间使用逗号间隔,最后一个元素后边没有逗号,数组中支持同时存在多种不同类型的成员
[
    ["cat", "dog", "panda", "beer", "rabbit"],
    ["北京", "上海", "天津", "重庆"],
    ["luffy", "boy", 19]
]
  • json对象
    Json对象使用 {} 来描述,每个Json对象中可以存储若干个元素,每一个元素对应一个键值对(key:value 结构),元素和元素之间使用逗号间隔,最后一个元素后边没有逗号,需要注意的是:键值(key)必须是字符串,且位于同一层级的键值不要重复(因为是通过键值取出对应的value值)
{
    "Name":"Ace",
    "Sex":"man",
    "Age":20,
    "Family":{
        "Father":"Gol·D·Roger",
        "Mother":"Portgas·D·Rouge",
        "Brother":["Sabo", "Monkey D. Luffy"]
    },
    "IsAlive":false,
    "Comment":"yyds"
}
  • json文件
    json在嵌套之后可以描述很复杂的事情,但是在一个json文件中只能有一个json数组或json对象的根节点,不允许同时存储多个并列的根节点

2.qt中的json操作
qt中的json操作主要依赖于四个类:

  • QJsonDocument:utf-8编码的字符串文本和json文件之间的转换器。可以使用QJsonDocument::fromJson()将JSON文档从基于文本的表示形式转换为QJsonDocument,toJson()将其转换回文本
//构造函数
QJsonDocument()
QJsonDocument(const QJsonObject &object)
QJsonDocument(const QJsonArray &array)

//静态成员函数
//从一个 JSON 字符串(QByteArray)创建一个 QJsonDocument 对象
QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error = nullptr)

//成员方法
//将 QJsonDocument 对象转换为 JSON 字符串
QByteArray QJsonDocument::toJson(QJsonDocument::JsonFormat format = QJsonDocument::Indented) 
//返回 QJsonDocument 的根对象/数组,如果文档是一个 JSON 对象/数组
QJsonObject QJsonDocument::object()
QJsonArray QJsonDocument::array()
// 检查 QJsonDocument 是否是一个 JSON 对象/数组。
bool QJsonDocument::isObject()
bool QJsonDocument::isArray() 
//检查是否为空
bool QJsonDocument::isEmpty()
  • QJsonArray:json数组,数组中的元素是QJsonValue里的类型
//添加数据
void QJsonArray::append(const QJsonValue &value);	// 在尾部追加
void QJsonArray::insert(int i, const QJsonValue &value); // 插入到 i 的位置之前
//计算元素个数
int QJsonArray::count() const;
int QJsonArray::size() const;
//取出一个元素的值
QJsonValue QJsonArray::at(int i) const;
QJsonValue QJsonArray::first() const; // 头部元素
QJsonValue QJsonArray::last() const; // 尾部元素
QJsonValueRef QJsonArray::operator[](int i);
//删除某个元素
void QJsonArray::pop_back();           // 删除尾部
void QJsonArray::pop_front();          // 删除头部
void QJsonArray::removeAt(int i);      // 删除i位置的元素
  • QJsonObject:JSON对象是键值对的列表,其中键是唯一的字符串,值由QJsonValue表示
//插入
iterator QJsonObject::insert(const QString &key, const QJsonValue &value);
//获取对象中键值对个数
int QJsonObject::size() const;
//查找value
QJsonValue QJsonObject::value(const QString &key) const;    // utf8
QJsonValue QJsonObject::operator[](const QString &key) const;
//删除键值对
void QJsonObject::remove(const QString &key);
  • QJsonValue:封装了json支持的数据类型
//通过构造函数将一个普通类型变量封装成一个QJsonValue对象
//转换为普通类型
如bool toBool()
//判断数据类型
如bool isArray()

qt文件操作

Qt中,文件操作主要是通过QFile类来完成的,QFile类提供了对文件的基本操作,如读、写、打开、关闭等

//用某个路径上的文件初始化对象,如果不存在就创建一个
QFile(const QString &name)
//打开文件,可以指定对文件的操作权限,ReadOnly只读,WriteOnly只写,ReadWrite可读可写
 bool open(QIODeviceBase::OpenMode mode)
 //读文件
read/readLine/readAll
//写文件
write()/QTextStream()
  • 逐行读取操作
    读取过程中以\n作为结束
QFile file("C:/Users/12495/Desktop/nnc.txt");
    if (file.open(QFile::ReadOnly)) {
        QTextStream in(&file);
        //in.setCodec("UTF-8");  // 指定文本流的编码为UTF-8
        while (!in.atEnd()) {
            QString line = in.readLine();
            qDebug() << line;
        }
        file.close();
    } else {
        qDebug() << "打开失败";
    }
  • 写入文件操作
方式一:
QFile file("example.txt");
if (file.open(QIODevice::WriteOnly)) {
    QByteArray content = "Hello, this is a test.\n";
    file.write(content);
    file.close();
}
方式二:
QFile file("example.txt");
if (file.open(QIODevice::WriteOnly)) {
    QTextStream out(&file);
    out << "Hello, this is a test.\n";
    out << "Using QTextStream for writing.";
    file.close();
}
  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值