当前位置: 首页 > backend >正文

5、qt系统相关

文章目录

    • 1. Qt 事件
    • 1.1 事件介绍
    • 1.2 事件的处理
    • 1.3 按键事件
      • 1.3.1 单个按键
    • 1.4 ⿏标事件
      • 1.4.1 ⿏标单击事件
      • 1.4.2 ⿏标释放事件
      • 1.4.3 ⿏标双击事件
      • .4.4 ⿏标移动事件
      • 1.4.5 滚轮事件
    • 1.5 定时器
      • 1.5.1 QTimerEvent 类
      • 1.5.2 QTimer 类
      • 1.5.3 获取系统⽇期及时间
    • 1.6 事件分发器
      • 1.6.1 概述
      • 1.6.2 事件分发器⼯作原理
    • 1.7 事件过滤器
  • 扩展
  • 2. Qt ⽂件
    • 2.1 Qt ⽂件概述
    • 2.2 输⼊输出设备类
    • 2.3 ⽂件读写类
    • 2.4 ⽂件和⽬录信息类
  • 3. Qt 多线程
    • 3.1 Qt 多线程概述
    • 3.2 QThread 常⽤ API
    • 3.3 使⽤线程
    • 3.4 线程安全
      • 3.4.1 互斥锁
      • 3.4.2 条件变量
      • 3.4.3 信号量
    • 4. Qt ⽹络
    • 4.1 UDP Socket
      • 4.1.2 回显服务器
      • 4.1.3 回显客⼾端
    • 4.2 TCP Socket
      • 4.2.1 核⼼ API 概览
      • 4.2.3 回显客⼾端
    • 4.3 HTTP Client
      • 4.3.2 代码⽰例
    • 4.4 其他模块
  • 5. Qt ⾳视频
    • 5.1 Qt ⾳频
      • 5.1.1 核⼼API概览
      • 5.1.2 ⽰例
    • 5.2 Qt 视频
      • 5.2.1 核⼼API概览
      • 5.2.2 ⽰例
  • 总结

1. Qt 事件

类比
信号槽进行的各种操作,就可能产生出信号,可以给某个信号指定槽函数,当信号触发时,就能自动执行到对应的槽函数。
用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就能够执行到对应的代码。
信号槽就是对于事件的进一步封装。
事件是信号槽的底层机制。
实际开发程序过程中,绝大部分和用户进行的交互都是通过“信号槽”来完成的,有些特殊情况下,信号槽不一定能搞定,(某个用户的动作行为,Qt没有提供对应的信号…)此时需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑。
开发事件机制给咱们程序员,咱们程序员就可以根据实际的需要进行更深度的定制化DIY操作。
用户进行了很多操作,就会产生很多的事件(当然也会产生很多信号)。

1.1 事件介绍

**事件是应⽤程序内部或者外部产⽣的事情或者动作的统称。**在 Qt 中使⽤⼀个对象来表⽰⼀个事件。所有的 Qt 事件均继承于抽象类 QEvent。事件是由系统或者 Qt 平台本⾝在不同的时刻发出的。当⽤⼾按下⿏标、敲下键盘,或者是窗⼝需要重新绘制的时候,都会发出⼀个相应的事件。⼀些事件是在⽤⼾操作时发出,如键盘事件、⿏标事件等,另⼀些事件则是由系统本⾝⾃动发出,如定时器事件。常⻅的 Qt 事件如下:
在这里插入图片描述
由上图可知,不同场景下,需要关注的点是不一样的。这些事件的子类中就会包含一些对应的不同属性。
常⻅事件描述:
事件名称 描述
⿏标事件 ⿏标左键、⿏标右键、⿏标滚轮,⿏标的移动,⿏标按键的按下和松开
键盘事件 按键类型、按键按下、按键松开
定时器事件 定时时间到达 在这里插入图片描述

进⼊离开事件 ⿏标的进⼊和离开
滚轮事件 ⿏标滚轮滚动
绘屏事件 重绘屏幕的某些部分
在这里插入图片描述

显⽰隐藏事件 窗⼝的显⽰和隐藏
移动事件 窗⼝位置的变化
窗⼝事件 是否为当前窗⼝
⼤⼩改变事件 窗⼝⼤⼩改变
焦点事件 键盘焦点移动
拽事件 ⽤⿏标进⾏拖拽
在这里插入图片描述

1.2 事件的处理

事件处理⼀般常⽤的⽅法为:重写相关的 Event 函数。
让一段代码和某个事件关联起来。当事件触发的时候,就能指定到这段代码。
事件的处理 :让当前的类,重写某个事件处理函数。
某个事件处理函数:这里运用的是“多态”机制创建子类,继承自Qt已有的类。在子类中重写父类的事件处理函数;后续事件触发过程中,就会通过多态这样的机制,执行到咱们自己写的子类的函数中。

在 Qt 中,⼏乎所有的 Event 函数都是虚函数,所以可以重新实现。如:在实现⿏标的进⼊和离开事件时,直接重新实现 enterEvent() 和 leaveEvent() 即可。enterEvent() 和 leaveEvent() 函数原型如下
在这里插入图片描述
在这里插入图片描述
⽰例1:
1、新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰;
在这里插入图片描述
2、设计 UI ⽂件,如下图⽰;
在这里插入图片描述
3、在项⽬中新添加⼀个类:MyLabel;
先选中项⽬名称 QEvent,点击⿏标右键,选择 add new … ,弹出如下对话框:
在这里插入图片描述
4、选择:Choose … ,弹出如下界⾯:
在这里插入图片描述
5、此时项⽬中会新添加以下两个⽂件:
在这里插入图片描述
6、在帮助⽂档中查找对应的内容;
在这里插入图片描述
7、点击 “显⽰” 之后,出现如下内容:
在这里插入图片描述
8、复制 enterEvent() ,粘贴在项⽬⽂件 “mylabel.h” 中;
在这里插入图片描述
9、重写 enterEvent() ⽅法;
在这里插入图片描述

10、在 UI ⽂件中选中 Label,右键 ------> 提升为…
在这里插入图片描述
11、当点击 "提升为… " 之后,弹出如下对话框:
在这里插入图片描述
12、修改基类:
在这里插入图片描述

在这里插入图片描述
13、执⾏效果如下:当⿏标进⼊设计好的标签之后,就会在应⽤程序输出栏中打印:⿏标进⼊
在这里插入图片描述
实践(重写enterevent和leaveevent):

#include "label.h"
#include<QDebug>Label::Label(QWidget*parent):QLabel(parent)//继承关系
{}void Label::enterEvent(QEvent *event)
{(void) event;qDebug()<<"enterEvent";
}void Label::leaveEvent(QEvent *event)
{(void) event;qDebug()<<"leaveEvent";
}

在这里插入图片描述

⽰例2:当⿏标点击时,获取对应的坐标值;
1、在上述⽰例的基础上,在 mylabel.h 中声明 mousePressEvent() ⽅法;
在这里插入图片描述
2、在 mylabel.cpp 中重写 mousePressEvent() ⽅法;
在这里插入图片描述
实现效果如下:
在这里插入图片描述
⽰例:⿏标左键点击时,打印对应的坐标值,⿏标右键点击时,打印基于屏幕的坐标
在这里插入图片描述

1.3 按键事件

Qt 中的按键事件是通过 QKeyEvent 类来实现的。当键盘上的按键被按下或者被释放时,键盘事件便
会触发。在帮助⽂档中查找 QKeyEvent 类如下:
在这里插入图片描述
查找按键事件中所有的按键类型:在帮助⽂档中输⼊:Qt::Key,如下图:

在这里插入图片描述

1.3.1 单个按键

⽰例:当某个按键被按下时,输出:某个按键被按下了;
1、新建项⽬,在头⽂件 “widget.h” 中声明虚函数 keyPressEvent();如下图:
在这里插入图片描述
2、在 “widget.cpp” ⽂件中重写 keyPressEvent() 虚函数;
在这里插入图片描述
1.3.2 组合按键
在 Qt 助⼿中搜索:Qt::KeyboardModifier,如下图⽰:
在这里插入图片描述
**Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。**在 Qt 中,键盘事件可以与修改键⼀起使⽤,以实现⼀些复杂的交互操作。KeyboardModifier 中修改键的具体描述如下:

Qt::NoModifier ⽆修改键
Qt::ShiftModifier Shift 键
Qt::ControlModifier Ctrl 键
Qt::AltModifier Alt 键
Qt::MetaModifier Meta键(在Windows上指Windows键,在macOS上指Command键)
Qt::KeypadModifier 使⽤键盘上的数字键盘进⾏输⼊时,Num Lock键处于打开状态
Qt::GroupSwitchModifier ⽤于在输⼊法 组之间 切换
⽰例:
在这里插入图片描述
键盘实践:

#include "widget.h"
#include "ui_widget.h"
#include<QKeyEvent>
#include<QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);
}Widget::~Widget()
{delete ui;
}void Widget::keyPressEvent(QKeyEvent *event)
{//qDebug()<<event->key();if(event->key()==Qt::Key_A&&event->modifiers()==Qt::ControlModifier)//按下a和ctrl{qDebug()<<"按下ctrl+A键";}
}

在这里插入图片描述

1.4 ⿏标事件

在 Qt 中,⿏标事件是⽤ QMouseEvent 类来实现的。当在窗⼝中按下⿏标或者移动⿏标时,都会产⽣
⿏标事件。
利⽤ QMouseEvent 类可以获取⿏标的哪个键被按下了以及⿏标的当前位置等信息。在 Qt 帮助⽂档中
查找QMouseEvent类 如下图⽰:
在这里插入图片描述

在这里插入图片描述

1.4.1 ⿏标单击事件

在 Qt 中,⿏标按下是通过虚函数 mousePressEvent() 来捕获的。mousePressEvent() 函数原型如
下:
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event)
⿏标左右键及滚的表⽰如下:
Qt::LeftButton ⿏标左键
Qt::RightButton ⿏标右键
Qt::MidButton ⿏标滚轮
⽰例1:⿏标左键
1、在 “widget.h” 头⽂件中声明⿏标按下事件;
在这里插入图片描述
2、在 “widget.cpp” ⽂件中重新实现 mousePressEvent() 函数;
在这里插入图片描述
实现效果如下:
在这里插入图片描述
⽰例2:⿏标右键:
在这里插入图片描述

鼠标按下事件的实现:

#include "label.h"
#include<QDebug>
#include<QMouseEvent>
Label::Label(QWidget*parent):QLabel(parent)
{}void Label::mousePressEvent(QMouseEvent *event)
{if(event->button()==Qt::LeftButton){qDebug()<<"按下左键";}else if(event->button()==Qt::RightButton){qDebug()<<"按下右键";}//当前event对象就包含了鼠标点击位置的坐标qDebug()<<event->x()<<","<<event->y();//globalX和globalY是以屏幕左上方为原点,获取的坐标qDebug()<<event->globalX()<<","<<event->globalY();}

实现效果如下:
在这里插入图片描述
⽰例3:⿏标滚轮
在这里插入图片描述
实现效果如下:
在这里插入图片描述

1.4.2 ⿏标释放事件

⿏标释放事件是通过虚函数 mouseReleaseEvent() 来捕获的。mouseReleaseEvent() 函数原型如
下:[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event)
⽰例:
在这里插入图片描述

执⾏效果如下:
在这里插入图片描述

1.4.3 ⿏标双击事件

⿏标双击事件是通过虚函数:mouseDoubleClickEvent() 来实现的。mouseDoubleClickEvent()
函数原型如下:
[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event)
⽰例:⿏标左键双击
在这里插入图片描述
执⾏效果如下:
在这里插入图片描述
鼠标释放和双击
在这里插入图片描述
在这里插入图片描述

#include "label.h"
#include<QDebug>
#include<QMouseEvent>
Label::Label(QWidget*parent):QLabel(parent)
{}void Label::mousePressEvent(QMouseEvent *event)
{if(event->button()==Qt::LeftButton){qDebug()<<"按下左键";}else if(event->button()==Qt::RightButton){qDebug()<<"按下右键";}//当前event对象就包含了鼠标点击位置的坐标qDebug()<<event->x()<<","<<event->y();//globalX和globalY是以屏幕左上方为原点,获取的坐标qDebug()<<event->globalX()<<","<<event->globalY();}void Label::mouseReleaseEvent(QMouseEvent *event)
{if(event->button()==Qt::LeftButton){qDebug()<<"释放左键";}else if(event->button()==Qt::RightButton){qDebug()<<"释放右键";}
}void Label::mouseDoubleClickEvent(QMouseEvent *event)
{if(event->button()==Qt::LeftButton){qDebug()<<"双击左键";}else if (event->button()==Qt::RightButton){qDebug()<<"双击右键";}
}

在这里插入图片描述

.4.4 ⿏标移动事件

⿏标移动事件是通过虚函数:mouseMoveEvent() 来实现的。同时为了实时捕获⿏标位置信息,需要通过函数 setMouseTracking() 来追踪⿏标的位置。mouseMoveEvent()函数原型如下:
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event)
setMouseTracking()函数原型如下:
void setMouseTracking(bool enable)
说明:
setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获⿏标位置信息。否则只有当⿏标按下时才能捕获其位置信息。
⽰例:
在这里插入图片描述
执⾏效果:
在这里插入图片描述
eg:

#include "widget.h"
#include "ui_widget.h"
#include<QMouseEvent>
#include<QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//把这个选项设置为true,才能实现追踪鼠标的移动位置this->setMouseTracking(true);
}Widget::~Widget()
{delete ui;
}void Widget::mouseMoveEvent(QMouseEvent *event)
{qDebug()<<event->x()<<event->y();
}

在这里插入图片描述

1.4.5 滚轮事件

在 Qt 中,⿏标滚轮事件是通过 QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获
取。delta() 函数原型如下:
int QGraphicsSceneWheelEvent::delta() const
其中返回值代表滚轮滑动的距离。正数表⽰滚轮相对于⽤⼾向前滑动,负数表⽰滚轮相对于⽤⼾向后
滑动。
⽰例:
在这里插入图片描述
执⾏效果如下:
在这里插入图片描述
eg:

#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QWheelEvent>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);total =0;
}Widget::~Widget()
{delete ui;
}void Widget::wheelEvent(QWheelEvent *event)
{total+=event->delta();qDebug()<<total;
}

在这里插入图片描述

1.5 定时器

Qt 中在进⾏窗⼝程序的处理过程中,经常要周期性的执⾏某些操作,或者制作⼀些动画效果,使⽤定
时器就可以实现。所谓定时器就是在间隔⼀定时间后,去执⾏某⼀个任务。定时器在很多场景下都会
使⽤到,如弹窗⾃动关闭之类的功能等。
Qt中的定时器分为 QTimerEvent 和 QTimer 这2个类。
• QTimerEvent类 ⽤来描述⼀个定时器事件。在使⽤时需要通过 startTimer() 函数来开启⼀个定时
器,这个函数需要输⼊⼀个以毫秒为单位的整数作为参数来表明设定的时间,它返回的整型值代表
这个定时器。当定时器溢出时(即定时时间到达)就可以在 timerEvent() 函数中获取该定时器的
编号来进⾏相关操作。
• QTimer类 来实现⼀个定时器,它提供了更⾼层次的编程接⼝,如:可以使⽤信号和槽,还可以设
置只运⾏⼀次的定时器。

QTimer实现了定时器功能。
在QTimer背后是QTimerEvent定时器进行支撑的
QObject提供了一个timerEvent这个函数
startTimer启动定时器
killTimer关闭定时器
注意:
此时timeId类似于Linux篇的“文件描述符”身份识别效果。
使用timerEvent比QTime还是要更复杂一点.手动管理timerid,还要区分这次函数调用是那个Timer即可。

1.5.1 QTimerEvent 类

⽰例1:在UI界⾯上放置两个 Label 控件,⼀个让其1秒数字累加⼀次,⼀个让其2秒数字累加⼀次。
1、新建项⽬,在UI界⾯⽂件放置两个 Label 控件;
在这里插入图片描述
2、在 “widget.h” 头⽂件中声明 timerEvent() 函数,并定义两个整型变量;
在这里插入图片描述
3、在 “widget.cpp” ⽂件中重写 timerEvent() 函数;
在这里插入图片描述
实现效果如下:
在这里插入图片描述

1.5.2 QTimer 类

⽰例:在UI界⾯放置⼀个 Label 标签,两个按钮,分别是 “开始” 和 “停⽌” ,当点击 “开始” 按钮时,
开始每隔1秒计数⼀次,点击 “停⽌” 按钮时,暂停计数。
1、设计 UI 界⾯如下:
在这里插入图片描述

2、在 “widget.cpp” ⽂件中实现对应功能;
在这里插入图片描述
实现效果如下:
在这里插入图片描述

1.5.3 获取系统⽇期及时间

在 Qt 中,获取系统的⽇期及实时时间可以通过 QTimer 类 和 QDateTime类。
QDateTime类 提供了字符串格式的时间。字符串形式的时间输出格式由 toString() ⽅法中的 format
参数列表决定,可⽤的参数列表如下:
在这里插入图片描述
⽰例:获取系统⽇期及实时时间;
1、设计UI界⾯⽂件;放置⼀个 Label控件,⽤来显⽰⽇期及时间,放置两个按钮:“开始” 和 “停⽌” ;
在这里插入图片描述
2、在 “widget.h” 头⽂件中声明更新时间的槽函数;
在这里插入图片描述
3、在 “widget.cpp” ⽂件中实现对应功能;
在这里插入图片描述
实现效果如下:
在这里插入图片描述
qtimer实现定时功能

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//开启定时器事件//此处timerId是一个定时器的身份标识timerId = this->startTimer(1000);//每秒减一}Widget::~Widget()
{delete ui;
}void Widget::timerEvent(QTimerEvent *event)
{//如果一个程序中存在多个定时器(startTimer创建的定时器),此时每个定时器都会触发timeEvent函数//先判断一下这次触发是否是想要的定时器触发的if(event->timerId()!=this->timerId)//?{//如果不是我们的定时器触发的,就直接忽略//当前程序中只有这一个定时器return;}//是咱们自己搞得定时器int value = ui->lcdNumber->intValue();//在lcdif(value<=0){//停止定时器this->killTimer(this->timerId);//}value-=1;ui->lcdNumber->display(value);//lcd 是display}

1.6 事件分发器

1.6.1 概述

在 Qt 中,事件分发器(Event Dispatcher) 是⼀个核⼼概念,⽤于处理 GUI 应⽤程序中的事件。事件分
发器负责将事件从⼀个对象传递到另⼀个对象,直到事件被处理或被取消。每个继承⾃ QObject类 或
QObject类 本⾝都可以在本类中重写 bool event(QEvent *e) 函数,来实现相关事件的捕获和拦截。

1.6.2 事件分发器⼯作原理

在 Qt 中,我们发送的事件都是传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函
数。所有的事件都会进⼊到这个函数⾥⾯,那么我们处理事件就要重写这个 event() 函数。event() 函
数本⾝不会去处理事件,⽽是根据 事件类型(type值)调⽤不同的事件处理函数。事件分发器就是⼯
作在应⽤程序向下分发事件的过程中,如下图:
在这里插入图片描述
如上图,事件分发器⽤于分发事件。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是
通过 bool event(QEvent *e) 函数来实现。其返回值为布尔类型,若为 ture,代表拦截,不向下分
发。
Qt 中的事件是封装在 QEvent类 中,在 Qt 助⼿中输⼊ QEvent 可以查看其所包括的事件类型,如下图⽰
在这里插入图片描述
在这里插入图片描述
⽰例:
1、在 “widget.h” 头⽂件中声明 ⿏标点击事件 和 事件分发器;如下图⽰:
在这里插入图片描述
2、在 “widget.cpp” ⽂件中实现 ⿏标点击事件 和 拦截事件;
在这里插入图片描述
执⾏结果如下:
在这里插入图片描述

1.7 事件过滤器

在 Qt 中,⼀个对象可能经常要查看或拦截另外⼀个对象的事件,如对话框想要拦截按键事件,不让别
的组件接收到,或者修改按键的默认值等。通过上⾯的学习,我们已经知道,Qt 创建了 QEvent事件
对象之后,会调⽤QObject 的 event()函数 处理事件的分发。显然,我们可以在 event()函数 中实现拦
截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很
多个event()函数。这当然相当⿇烦,更不⽤说重写 event()函数还得⼩⼼⼀堆问题。好在 Qt 提供了另
外⼀种机制来达到这⼀⽬的:事件过滤器。
事件过滤器是在应⽤程序分发到 event事件分发器 之前,再做⼀次更⾼级的拦截。如下图⽰:
在这里插入图片描述
事件过滤器的⼀般使⽤步骤:
1、安装事件过滤器;
2、重写事件过滤器函数:eventfilter() 。
⽰例:
1、新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰;
在这里插入图片描述
2、设计 UI ⽂件,如下图⽰;
在这里插入图片描述
3、在项⽬新添加⼀个类:MyLabel;
先选中项⽬名称 QEvent,点击⿏标右键,选择 add new … ,弹出如下对话框:
在这里插入图片描述
4、选择:Choose … ,弹出如下界⾯:
在这里插入图片描述
5、此时项⽬中会新添加以下两个⽂件:
在这里插入图片描述
6、在 UI ⽂件中选中 Label,右键 ------> 提升为…
在这里插入图片描述
7、当点击 "提升为… " 之后,弹出如下对话框:
在这里插入图片描述
6、在 “mylabel.h” 中声明 ⿏标点击事件 和 事件分发器;
在这里插入图片描述
7、在 “mylabel.cpp” ⽂件中实现⿏标点击事件和事件分发器;
在这里插入图片描述
8、在 “widget.h” 头⽂件中声明事件过滤器函数;
在这里插入图片描述
9、在 “widget.cpp” ⽂件中实现事件过滤器的两个步骤;
在这里插入图片描述
在这里插入图片描述
执⾏结果如下所⽰:
在这里插入图片描述
过滤器小结:
事件分发/事件过滤 属于Qt事件机制背后的一些逻辑。
Qt也把这部分内容提供了一些API让程序员有更多的操作空间。
事件分发☞重写event函数,直接获取到所有的事件。
event函数☞杀伤力比较广,不当使用可能对现有逻辑(现有的事件体系造成一些负面影响)
当然,有的场景中,比如要禁用用户的某种操作,可以考虑使用事件过滤机制。

扩展

moveEvent窗口移动时出发的事件
resizeEvent窗口大小改变时触发的事件

2. Qt ⽂件

类比
C语言中,fopen打开文件,fread fwrite读写文件 fclose关闭文件
c++中,fstream打开文件,<< >>读写文件 close关闭文件
Linux中,open打开文件,read write读写文件 close关闭文件 ☞一般开发中很少会直接使用,主要是理解文件操作背后的原理

Qt也提供了一套文件操作。
Qt中使用上述的几种方案来读写文件,也可以完全可以的。(linux这一套,局限于linux系统windows上的Qt,就需要使用windows api)
但是即使如此,Qt还是又封装了一套。
Qt诞生的太早了,c++还没有“标准化概念”。
咱们在编写Qt程序的时候,更加推荐使用Qt自己提供的这一套文件操作。和QString等Qt内置的类可以很好的配合。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.1 Qt ⽂件概述

⽂件操作是应⽤程序必不可少的部分。Qt 作为⼀个通⽤开发库,提供了跨平台的⽂件操作能⼒。 Qt
提供了很多关于⽂件的类,通过这些类能够对⽂件系统进⾏操作,如⽂件读写、⽂件信息获取、⽂件
复制或重命名等。

2.2 输⼊输出设备类

在 Qt 中,⽂件读写的类为 QFile 。QFile 的⽗类为 QFileDevice ,QFileDevice 提供了⽂件交互操作的
底层功能。 QFileDevice 的⽗类是 QIODevice,QIODevice 的⽗类为 QObject 。
QIODevice 是 Qt 中所有输⼊输出设备(input/output device,简称 I/O 设备)的基础类,I/O 设备就
是能进⾏数据输⼊和输出的设备,例如⽂件是⼀种 I/O 设备,⽹络通信中的 socket 是 I/O 设备, 串
⼝、蓝⽛等通信接⼝也是 I/O 设备,所以它们也是从 QIODevice 继承来的。Qt 中主要的⼀些 I/O 设备
类的继承关系如下图所⽰:
在这里插入图片描述
上图中各类的说明如下:
• QFile 是⽤于⽂件操作和⽂件数据读写的类,使⽤ QFile 可以读写任意格式的⽂件。
• QSaveFile 是⽤于安全保存⽂件的类。使⽤ QSaveFile 保存⽂件时,它会先把数据写⼊⼀个临时⽂
件,成功提交后才将数据写⼊最终的⽂件。如果保存过程中出现错误,临时⽂件⾥的数据不会被写
⼊最终⽂件,这样就能确保最终⽂件中不会丢失数据或被写⼊部分数据。 在保存⽐较⼤的⽂件或复
杂格式的⽂件时可以使⽤这个类,例如从⽹络上下载⽂件等。
• QTemporaryFile 是⽤于创建临时⽂件的类。使⽤函数 QTemporaryFile::open() 就能创建⼀个⽂件
名唯⼀的临时⽂件,==在 QTemporaryFile 对象被删除时,临时⽂件被⾃动删除。 ==
QsaveFile 要写入大量的数据,写文件的时候,往往会把旧的文件先清空,再写。万一新的数据写一半,结果出错了,新的数据用不了旧的数据也没了。
如果你想要写一个文件,会自动的把内容先写到临时文件里(不会破坏原有文件)。等到所有的内容都写完了之后,再把就文件自动删除,并且用新的文件替换旧的文件。这种策略是一个广泛常见的策略。
• QTcpSocket 和 QUdpSocket 是分别实现了 TCP 和 UDP 的类。
• QSerialPort 是实现了串⼝通信的类,通过这个类可以实现计算机与串⼝设备的通信。
• QBluetoothSocket 是⽤于蓝⽛通信的类。⼿机和平板计算机等移动设备有蓝⽛通信模块,笔记本
电脑⼀般也有蓝⽛通信模块。通过QBluetoothSocket类,就可以编写蓝⽛通信程。如编程实现笔
记本电脑与⼿机的蓝⽛通信。
• QProcess 类⽤于启动外部程序,并且可以给程序传递参数。
• QBuffer 以⼀个 QByteArray 对象作为数据缓冲区,将 QByteArray 对象当作⼀个 I/O 设备来读写。

2.3 ⽂件读写类

在 Qt 中,⽂件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些⽤来读写⽂件的⽅法。对于⽂件的操作主要有:
• 读数据:QFile 类中提供了多个⽅法⽤于读取⽂件内容;如 read()、readAll()、readLine()等。
• 写数据:QFile 类中提供了多个⽅法⽤于往⽂件中写内容;如 write()、writeData()等。
• 关闭⽂件:⽂件使⽤结束后必须⽤函数 close() 关闭⽂件。
访问⼀个设备之前,需要使⽤ open()函数 打开该设备,⽽且必须指定正确的打开模式,QIODevice 中
所有的打开模式由 QIODevice::OpenMode 枚举变量定义,其取值如下:
在这里插入图片描述
QIODevice::NotOpen 没有打开设备
QIODevice::ReadOnly 以只读⽅式打开设备
QIODevice::WriteOnly 以只写⽅式打开设备
QIODevice::ReadWrite 以读写⽅式打开设备
QIODevice::Append 以追加⽅式打开设备,数据将写到⽂件末尾
QIODevice::Truncate 每次打开⽂件后重写⽂件内容,原内容将被删除
QIODevice::Text 在读⽂件时,⾏尾终⽌符会被转换为 ‘\n’;当写⼊⽂件时,⾏尾终⽌符会被转换为
本地编码。如 Win32上为’\r\n’;
QIODevice::Unbuffered ⽆缓冲形式打开⽂件,绕过设备中的任何缓冲区
QIODevice::NewOnly ⽂件存在则打开失败,不存在则创建⽂件
⽰例1:读取⽂件内容

  1. 新建 Qt 项⽬,在 UI ⽂件中拖⼊⼀个 LineEdit,⼀个pushButton,⼀个 TextEdit。当点击按钮
    时,弹出窗⼝选择要读取的⽂件,并将读取到的内容在 TextEdit 中显⽰;
    在这里插入图片描述
  2. 在 “widget.cpp” ⽂件中实现对应功能;
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->btn,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开⽂
件","C:\\Users\\Lenovo\\Desktop");
ui->lineEdit->setText(path);
QFile file(path); //path:代表⽂件路径this
//打开⽂件
file.open(QIODevice::ReadOnly); //以只读的⽅式打开⽂件
QString str = file.readAll();
ui->textEdit->setText(str);
//关闭⽂件
file.close();
});
}

实现效果如下:
在这里插入图片描述
⽰例2:写⽂件
在上述⽰例的基础上修改 “widget.cpp” ⽂件;

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->btn,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开⽂
件","C:\\Users\\Lenovo\\Desktop");
ui->lineEdit->setText(path);
QFile file(path); //path:代表⽂件路径
//打开⽂件 进⾏写操作
file.open(QIODevice::Append); //以追加的⽅式进⾏写
file.write("【这是⽰例!!!】");
//关闭⽂件
file.close();
});
}

实现效果如下:
在这里插入图片描述
eg:实现简易记事本(打开和保存功能)

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QPlainTextEdit>
#include<QFont>
#include<QFileDialog>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);this->setWindowTitle("简单的记事本");//获取到菜单栏QMenuBar *menuBar =this->menuBar();//必须是这个不然识别不了//添加菜单QMenu*menu = new QMenu("文件");menuBar->addMenu(menu);//添加菜单项QAction*action1 = new QAction("打开");QAction*action2 = new QAction("保存");menu->addAction(action1);menu->addAction(action2);//指定一个输入框edit =  new QPlainTextEdit();//创建输入框QFont font;font.setPixelSize(20);//设置样式edit->setFont(font);this->setCentralWidget(edit);//连接QAction的信号槽connect(action1,&QAction::triggered,this,&MainWindow::handleAction1);connect(action2,&QAction::triggered,this,&MainWindow::handleAction2);}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::handleAction1()
{//1、先弹出“打开文件”对话框,让用户选择打开那个文件QString path = QFileDialog::getOpenFileName(this);//2、把文件名显示到状态栏里QStatusBar *statusBar = this->statusBar();statusBar->showMessage(path);//3、根据用户选择的路径,构造一个QFile对象,并打开文件QFile file(path);bool ret = file.open(QIODevice::ReadOnly);if(!ret){//打开文件夹失败statusBar->showMessage(path+"打开文件失败");return;}//4、读取文件QString text = file.readAll();//5、关闭文件!!file.close();//6、读到的内容设置到输入框中edit->setPlainText(text);
}
void MainWindow::handleAction2()
{//1、先弹出“保存文件”对话框QString path = QFileDialog::getSaveFileName(this);//2、在状态栏中显示这个文件名QStatusBar *statusBar = this->statusBar();statusBar->showMessage(path);//3、根据用户选择的路径,构建一个QFile对象,并打开文件QFile file(path);bool ret = file.open(QIODevice::WriteOnly);if(!ret){//打开文件失败statusBar->showMessage(path+"打开文件失败");return;}//4、写文件const QString &text = edit->toPlainText();file.write(text.toUtf8());//5、关闭文件file.close();}

在这里插入图片描述

2.4 ⽂件和⽬录信息类

QFileInfo 是 Qt 提供的⼀个⽤于获取⽂件和⽬录信息的类,如获取⽂件名、⽂件⼤⼩、⽂件修改⽇期
等。QFileInfo类中提供了很多的⽅法,常⽤的有:
• isDir() 检查该⽂件是否是⽬录;
• isExecutable() 检查该⽂件是否是可执⾏⽂件;
• fileName() 获得⽂件名;
• completeBaseName() 获取完整的⽂件名;
• suffix() 获取⽂件后缀名;
• completeSuffix() 获取完整的⽂件后缀;
• size() 获取⽂件⼤⼩;
• isFile() 判断是否为⽂件;
• fileTime() 获取⽂件创建时间、修改时间、最近访问时间等;
⽰例:
在 “widget.cpp” ⽂件中添加如下代码:

#include <QFileInfo>
#include <QFileDialog>
#include <QDebug>
#include <QDateTime>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->btn,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开⽂
件","C:\\Users\\Lenovo\\Desktop");
//QFileInfo ⽂件信息类
QFileInfo fileinfo(path);
//⽂件名
qDebug() << "⽂件名为:" << fileinfo.fileName().toUtf8().data();
//⽂件后缀名
qDebug() << "后缀名为:" << fileinfo.suffix().toUtf8().data();
//⽂件⼤⼩
qDebug() << "⽂件⼤⼩为:" << fileinfo.size();
//⽂件路径
qDebug() << "⽂件路径为:" << fileinfo.path().toUtf8().data();
//判断是否为⽂件
qDebug() << "是否为⽂件:"<< fileinfo.isFile();
//⽂件创建时间
QDateTime time1 = fileinfo.fileTime(QFileDevice::FileBirthTime);
qDebug() << "创建时间为:" << time1.toString("yyyy-MM-dd
hh:mm:ss").toUtf8().data();
//⽂件的最后修改⽇期
QDateTime time2 = fileinfo.lastModified();
qDebug() << "最后修改时间为:"<< time2.toString("yyyy-MM-dd
hh:mm:ss").toUtf8().data();
//判断是否为⽂件夹
qDebug() << "是否为⽬录:" << fileinfo.isDir();
});
}

实现效果如下
在这里插入图片描述
eg:FileInfo的使用

#include "widget.h"
#include "ui_widget.h"
#include<QFileDialog>
#include<QDebug>
#include<QFileInfo>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{//弹出文件对话框,并获取到文件的属性信息QString path = QFileDialog::getOpenFileName(this);//构造出一个QFileInfo对象QFileInfo fileInfo(path);//打印相关属性qDebug()<<fileInfo.fileName();qDebug()<<fileInfo.suffix();qDebug()<<fileInfo.path();qDebug()<<fileInfo.size();qDebug()<<fileInfo.isFile();qDebug()<<fileInfo.isDir();//...
}

在这里插入图片描述

3. Qt 多线程

3.1 Qt 多线程概述

在 Qt 中,多线程的处理⼀般是通过 QThread类 来实现。
QThread 代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据。
QThread 对象管理程序中的⼀个控制线程。

3.2 QThread 常⽤ API

run() 线程的⼊⼝函数…
start() 通过调⽤ run() 开始执⾏线程。操作系统将根据优先级参数调度线程。如果线程已
经在运⾏,这个函数什么也不做。
currentThread() 返回⼀个指向管理当前执⾏线程的 QThread的指针。
isRunning() 如果线程正在运⾏则返回true;否则返回false。
sleep() / msleep() /
usleep()
使线程休眠,单位为秒 / 毫秒 / 微秒
wait() 阻塞线程,直到满⾜以下任何⼀个条件:
与此 QThread 对象关联的线程已经完成执⾏(即当它从run()返回时)。如果线程已
经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。
已经过了⼏毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程
必须从run()返回)。如果等待超时,此函数将返回 false。
这提供了与 POSIX pthread_join() 函数类似的功能。
terminate() 终⽌线程的执⾏。线程可以⽴即终⽌,也可以不⽴即终⽌,这取决于操作系统的调
度策略。在terminate() 之后使⽤ QThread::wait() 来确保。
finished() 当线程结束时会发出该信号,可以通过该信号来实现线程的清理⼯作。

3.3 使⽤线程

创建线程的步骤:

  1. ⾃定义⼀个类,继承于 QThread,并且只有⼀个线程处理函数(和主线程不是同⼀个线程),这个线
    程处理函数主要就是重写⽗类中的 run() 函数。
  2. 线程处理函数⾥⾯写⼊需要执⾏的复杂数据处理;
  3. 启动线程不能直接调⽤ run() 函数,需要使⽤对象来调⽤ start() 函数实现线程启动;
  4. 线程处理函数执⾏结束后可以定义⼀个信号来告诉主线程;
  5. 最后关闭线程。
    ⽰例:
    1、⾸先新建 Qt 项⽬,设计 UI界⾯如下:
    在这里插入图片描述

2、新建⼀个类,继承于 QThread类;
在这里插入图片描述
程序如下:

/******************************** timethread.h
********************************/
#ifndef TIMETHREAD_H
#define TIMETHREAD_H
#include <QThread> //添加头⽂件
class TimeThread : public QThread
{
Q_OBJECT
public:
TimeThread();
void run(); //线程任务函数
signals:
void sendTime(QString Time); //声明信号函数
};
#endif // TIMETHREAD_H
/******************************** widget.h ********************************/
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <timethread.h> //添加头⽂件
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_btn_clicked();
void showTime(QString Time);
private:
Ui::Widget *ui;
TimeThread t; //定义线程对象
};
#endif // WIDGET_H
/******************************** timethread.cpp
********************************/
#include "timethread.h"
#include <QTime>
#include <QDebug>
TimeThread::TimeThread()
{ }
void TimeThread::run()
{
while(1)
{
QString time = QTime::currentTime().toString("hh:mm:ss");
qDebug() << time;
emit sendTime(time); //发送信号
sleep(1);
}
}
/******************************** widget.cpp ********************************/
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(&t,&TimeThread::sendTime,this,&Widget::showTime);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btn_clicked()
{
t.start(); //开启线程
}
void Widget::showTime(QString Time)
{
ui->label->setText(Time);
}

执⾏效果:
在这里插入图片描述
说明:
1、线程函数内部不允许操作 UI 图形界⾯,⼀般⽤数据处理;
2、connect() 函数第五个参数表⽰的为连接的⽅式,且只有在多线程的时候才意义。
connect() 函数第五个参数为 Qt::ConnectionType,⽤于指定信号和槽的连接类型。同时影响信号的
传递⽅式和槽函数的执⾏顺序。Qt::ConnectionType 提供了以下五种⽅式:
Qt::AutoConnection
在 Qt 中,会根据信号和槽函数所在的线程⾃动选择连接类型。如果信号和槽函
数在同⼀线程中,那么使⽤ Qt:DirectConnection 类型;如果它们位于不同的
线程中,那么使⽤Qt::QueuedConnection 类型。
Qt::DirectConnection
当信号发出时,槽函数会⽴即在同⼀线程中执⾏。这种连接类型适⽤于信号和
槽函数在同⼀线程中的情况,可以实现直接的函数调⽤,但需要注意线程安全
性。
Qt::QueuedConnection
当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下
⼀次事件循环时执⾏。这种连接类型适⽤于信号和槽函数在不同线程中的情
况,可以确保线程安全。
Qt::BlockingQueuedConnec
tion
与 Qt:QueuedConnection 类似,但是发送信号的线程会被阻塞,直到槽函数
执⾏完毕,这种连接类型适⽤于需要等待槽函数执⾏完毕再继续的场景,但需
要注意可能引起线程死锁的⻛险。
Qt::UniqueConnection 这是⼀个标志,可以使⽤位或与上述任何⼀种连接类型组合使⽤。

3.4 线程安全

实现线程互斥和同步常⽤的类有:
• 互斥锁:QMutex、QMutexLocker
• 条件变量:QWaitCondition
• 信号量:QSemaphore
• 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

3.4.1 互斥锁

互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法,在 Qt 中,互斥锁主要是通过
QMutex类来处理。
• QMutex
特点:QMutex 是 Qt 框架提供的互斥锁类,⽤于保护共享资源的访问,实现线程间的互斥操作。
⽤途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。

QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁

• QMutexLocker
特点:QMutexLocker 是 QMutex 的辅助类,使⽤ RAII(Resource Acquisition Is Initialization)⽅式
对互斥锁进⾏上锁和解锁操作。
⽤途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。

QMutex mutex;
{
QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
//访问共享资源
//...
} //在作⽤域结束时⾃动解锁

• QReadWriteLocker、QReadLocker、QWriteLocker
特点:
QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。
QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。
QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。
⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提
供了更⾼效的并发访问⽅式。

QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
//...
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
//修改共享资源
//...
} //在作⽤域结束时⾃动解写锁

⽰例1:

******************************* myThread.h **********************************/
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QMutex>
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject *parent = nullptr);
void run();
private:
static QMutex mutex; //多个线程使⽤⼀把锁
static int num; //多个线程访问⼀个数据
};
#endif // MYTHREAD_H
/******************************* myThread.cpp
**********************************/
#include "mythread.h"
#include <QDebug>
QMutex myThread::mutex;
int myThread::num = 0;
myThread::myThread(QObject *parent) : QThread(parent)
{ }
void myThread::run()
{
while(1)
{
this->mutex.lock(); //加锁
qDebug() << "Current Thread: " << this << ", Value: " << this->num++;
this->mutex.unlock(); //解锁
QThread::sleep(1); //线程睡眠两秒
}
}
/******************************* mainwindow.h
**********************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
/******************************* mainwindow.cpp
**********************************/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
myThread *t1 = new myThread(this);
myThread *t2 = new myThread(this);
t1->start();
t2->start();
}
MainWindow::~MainWindow()
{
delete ui;
}

执⾏效果:
两个线程使⽤⼀把锁,操作⼀个数据,数据会被两个线程依次打印:0、1、2、3、4 …
在这里插入图片描述
⽰例2:在上述⽰例的基础上使⽤ QMutexLocker 锁。

/******************************* myThread.cpp
**********************************/
#include "mythread.h"
#include <QDebug>
QMutex myThread::mutex;
int myThread::num = 0;
myThread::myThread(QObject *parent) : QThread(parent)
{ }
void myThread::run()
{
while (1)
{
//QMutexLocker:创建的时候加锁,当QMutexLocker局部销毁的时候解锁
{
QMutexLocker lock(&this->mutex);
qDebug() << "Current Thread: " << this << ", Value: " << this-
>num++;
}
QThread::sleep(1);// 线程睡眠两秒
}
}

3.4.2 条件变量

在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒。
在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题。
特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调

QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();

3.4.3 信号量

有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。
特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。

QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作

4. Qt ⽹络

和多线程类似, Qt 为了⽀持跨平台, 对⽹络编程的 API 也进⾏了重新封装.
注意:
• 实际 Qt 开发中进⾏⽹络编程, 也不⼀定使⽤ Qt 装的⽹络 API, 也有⼀定可能使⽤的是系
统原⽣ API 或者其他第三⽅框架的 API.
在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块.
添加之后要⼿动编译⼀下项⽬, 使 Qt Creator 能够加载对应模块的头⽂件.

4.1 UDP Socket

4.1.1 核⼼ API 概览
主要的类有两个. QUdpSocket 和 QNetworkDatagram
QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件.
名称 类型 说明 对标原⽣ API
bind(const QHostAddress&,
quint16)
⽅法 绑定指定的端⼝号. bind
receiveDatagram() ⽅法 返回 QNetworkDatagram . 读取
⼀个 UDP 数据报.
recvfrom
writeDatagram(const
QNetworkDatagram&)
⽅法 发送⼀个 UDP 数据报. sendto
readyRead 信号 在收到数据并准备就绪后触发. ⽆ (类似于 IO 多路复⽤的通
知机制)
QNetworkDatagram 表⽰⼀个 UDP 数据报.
名称 类型 说明 对标原⽣ API
QNetworkDatagram(const
QByteArray&, const
QHostAddress& , quint16 )
构造函

通过 QByteArray , ⽬标 IP 地址,
⽬标端⼝号 构造⼀个 UDP 数据报.
通常⽤于发送数据时.
⽆ data() ⽅法 获取数据报内部持有的数据. 返回
QByteArray
⽆ senderAddress() ⽅法 获取数据报中包含的对端的 IP 地
址. ⽆, recvfrom 包含了该功能.
senderPort() ⽅法 获取数据报中包含的对端的端⼝号. ⽆, recvfrom 包含了该功
能.

4.1.2 回显服务器

  1. 创建界⾯, 包含⼀个 QListWidget ⽤来显⽰消息.
    在这里插入图片描述
  2. 创建 QUdpSocket 成员
    修改 widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QUdpSocket* socket;
}

修改 widget.cpp, 完成 socket 后续的初始化
⼀般来说, 要先连接信号槽, 再绑定端⼝
如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这
个请求就有可能错过了.

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题
this->setWindowTitle("服务器");
// 2. 实例化 socket
socket = new QUdpSocket(this);
// 3. 连接信号槽, 处理收到的请求
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
// 4. 绑定端⼝
bool ret = socket->bind(QHostAddress::Any, 9090);
if (!ret) {
QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
return;
}
}
  1. 实现 processRequest , 完成处理请求的过程
    • 读取请求并解析
    • 根据请求计算响应
    • 把响应写回到客⼾端
void Widget::processRequest()
{
// 1. 读取请求
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();
// 2. 根据请求计算响应
const QString& response = process(request);
// 3. 把响应写回到客⼾端
QNetworkDatagram responseDatagram(response.toUtf8(),
requestDatagram.senderAddress(), requestDatagram.senderPort());
ocket->writeDatagram(responseDatagram);
// 显⽰打印⽇志
QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
QString::number(requestDatagram.senderPort())
+ "] req: " + request + ", resp: " + response;
ui->listWidget->addItem(log);
}
  1. 实现 process 函数
    由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容.
QString Widget::process(const QString& request)
{
return request;
}

“根据请求处理响应” 是服务器开发中的最核⼼的步骤.
⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的.

此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.

4.1.3 回显客⼾端

  1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
    • 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的
    sizePolicy 为 Expanding
    • 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好.
    • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).
    在这里插入图片描述
  2. 在 widget.cpp 中, 先创建两个全局常量, 表⽰服务器的 IP 和 端⼝
// 提前定义好服务器的 IP 和 端⼝
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
  1. 创建 QUdpSocket 成员
    修改 widget.h, 定义成员
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 socket 成员
QUdpSocket* socket;
}

修改 widget.cpp, 初始化 socket

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝名字
this->setWindowTitle("客⼾端");
// 2. 实例化 socket
socket = new QUdpSocket(this);
}
  1. 给发送按钮 slot 函数, 实现发送请求
void Widget::on_pushButton_clicked()
{
// 1. 获取到输⼊框的内容
const QString& text = ui->lineEdit->text();
// 2. 构造请求数据
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP),
SERVER_PORT);
// 3. 发送请求
socket->writeDatagram(requestDatagram);
// 4. 消息添加到列表框中
ui->listWidget->addItem("客⼾端说: " + text);
// 5. 清空输⼊框
ui->lineEdit->setText("");
}
  1. 再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应.
connect(socket, &QUdpSocket::readyRead, this, [=]() {
const QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
ui->listWidget->addItem(QString("服务器说: ") + response);
});

最终执⾏效果
启动多个客⼾端都可以正常⼯作.
在这里插入图片描述

4.2 TCP Socket

4.2.1 核⼼ API 概览

核⼼类是两个: QTcpServer 和 QTcpSocket
QTcpServer ⽤于监听端⼝, 和获取客⼾端连接.
名称 类型 说明 对标原⽣ API
listen(const QHostAddress&,
quint16 port)
⽅法 绑定指定的地址和端⼝号, 并开始监
听.
bind 和 listen
nextPendingConnection() ⽅法 从系统中获取到⼀个已经建⽴好的
tcp 连接.
返回⼀个 QTcpSocket , 表⽰这个
客⼾端的连接.
通过这个 socket 对象完成和客⼾端
之间的通信.
accept
newConnection 信号 有新的客⼾端建⽴连接好之后触发. ⽆ (但是类似于 IO 多路复⽤
中的通知机制)
QTcpSocket ⽤⼾客⼾端和服务器之间的数据交互.
名称 类型 说明 对标原⽣ API
readAll() ⽅法 读取当前接收缓冲区中的所有数据.

返回 QByteArray 对象.
read
write(const QByteArray& ) ⽅法 把数据写⼊ socket 中. write
deleteLater ⽅法 暂时把 socket 对象标记为⽆效. Qt
会在下个事件循环中析构释放该对
象.
⽆ (但是类似于 “半⾃动化的
垃圾回收”)
readyRead 信号 有数据到达并准备就绪时触发. ⽆ (但是类似于 IO 多路复⽤
中的通知机制)
disconnected 信号 连接断开时触发. ⽆ (但是类似于 IO 多路复⽤
中的通知机制)
QByteArray ⽤于表⽰⼀个字节数组. 可以很⽅便的和 QString 进⾏相互转换.
例如:
• 使⽤ QString 的构造函数即可把 QByteArray 转成 QString.
• 使⽤ QString 的 toUtf8 函数即可把 QString 转成 QByteArray.
4.2.2 回显服务器

  1. 创建界⾯. 包含⼀个 QListWidget , ⽤于显⽰收到的数据.
    在这里插入图片描述
  2. 创建 QTcpServer 并初始化
    修改 widget.h, 添加 QTcpServer 指针成员.
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 QTcpServer
QTcpServer* tcpServer;
}

修改 widget.cpp, 实例化 QTcpServer 并进⾏后续初始化操作.
• 设置窗⼝标题
• 实例化 TCP server. (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
• 通过信号槽, 处理客⼾端建⽴的新连接.
• 监听端⼝

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题
this->setWindowTitle("服务器");
// 2. 实例化 TCP server
tcpServer = new QTcpServer(this);
// 3. 通过信号槽, 处理客⼾端建⽴的新连接.
connect(tcpServer, &QTcpServer::newConnection, this,
&Widget::processConnection);
// 4. 监听端⼝
bool ret = tcpServer->listen(QHostAddress::Any, 9090);
if (!ret) {
QMessageBox::critical(nullptr, "服务器启动失败!", tcpServer-
>errorString());
exit(1);
}
}
  1. 继续修改 widget.cpp, 实现处理连接的具体⽅法 processConnection
    • 获取到新的连接对应的 socket.
    • 通过信号槽, 处理收到请求的情况
    • 通过信号槽, 处理断开连接的情况
void Widget::processConnection()
{
// 1. 获取到新的连接对应的 socket.
QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";
ui->listWidget->addItem(log);
// 2. 通过信号槽, 处理收到请求的情况
connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
// a) 读取请求
QString request = clientSocket->readAll();
// b) 根据请求处理响应
const QString& response = process(request);
// c) 把响应写回客⼾端
clientSocket->write(response.toUtf8());
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] req: " +
request + ", resp: " + response;
ui->listWidget->addItem(log);
});
// 3. 通过信号槽, 处理断开连接的情况
connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
ui->listWidget->addItem(log);
// 删除 clientSocket
clientSocket->deleteLater();
});
}
  1. 实现 process ⽅法, 实现根据请求处理响应.
    由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容.
  QString Widget::process(const QString &request)
{
return request;
}

“根据请求处理响应” 是服务器开发中的最核⼼的步骤.
⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的.
此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.

4.2.3 回显客⼾端

  1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
    • 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的
    sizePolicy 为 Expanding
    • 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好.
    • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).
    在这里插入图片描述

  2. 创建 QTcpSocket 并实例化
    修改 widget.h, 创建成员.

2) 创建  QTcpSocket  并实例化 
修改 widget.h, 创建成员. 
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 新增 QTcpSocket
QTcpSocket* socket;
}; 

修改 widget.cpp, 对 QTcpSocket 进⾏实例化.
• 设置窗⼝标题
• 实例化 socket 对象 (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
• 和服务器建⽴连接.
• 等待并确认连接是否出错.

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 设置窗⼝标题.
this->setWindowTitle("客⼾端");
// 2. 实例化 socket 对象.
socket = new QTcpSocket(this);
// 3. 和服务器建⽴连接.
socket->connectToHost("127.0.0.1", 9090);
// 4. 等待并确认连接是否出错.
if (!socket->waitForConnected()) {
QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
exit(1);
}
}
  1. 修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器.
void Widget::on_pushButton_clicked()
{
// 获取输⼊框的内容
const QString& text = ui->lineEdit->text();
// 清空输⼊框内容
ui->lineEdit->setText("");
// 把消息显⽰到界⾯上
ui->listWidget->addItem(QString("客⼾端说: ") + text);
// 发送消息给服务器
socket->write(text.toUtf8());
}
  1. 修改 widget.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应.
/ 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {
QString response = socket->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") + response);
});

先启动服务器, 再启动客⼾端(可以启动多个), 最终执⾏效果:
由于我们使⽤信号槽处理同⼀个客⼾端的多个请求, 不涉及到循环, 也就不会使客⼾端之间相互影响
了.

4.3 HTTP Client

进⾏ Qt 开发时, 和服务器之间的通信很多时候也会⽤到 HTTP 协议.
通过 HTTP 从服务器获取数据.
• 通过 HTTP 向服务器提交数据.
4.3.1 核⼼ API
关键类主要是三个. QNetworkAccessManager , QNetworkRequest , QNetworkReply .
QNetworkAccessManager 提供了 HTTP 的核⼼操作.
⽅法 说明
get(const QNetworkRequest& ) 发起⼀个 HTTP GET 请求. 返回 QNetworkReply 对象.
post(const QNetworkRequest& , const
QByteArray& )
发起⼀个 HTTP POST 请求. 返回 QNetworkReply 对
象.
QNetworkRequest 表⽰⼀个 HTTP 请求(不含 body).
如果需要发送⼀个带有 body 的请求(⽐如 post), 会在 QNetworkAccessManager 的 post ⽅法
中通过单独的参数来传⼊ body.
⽅法 说明
QNetworkRequest(const QUrl& ) 通过 URL 构造⼀个 HTTP 请求.
setHeader(QNetworkRequest::KnownHeaders
header,
const QVariant &value)
设置请求头.
其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型, 常⽤取值:
取值 说明
ContentTypeHeader 描述 body 的类型.
ContentLengthHeader 描述 body 的⻓度.
LocationHeader ⽤于重定向报⽂中指定重定向地址. (响应中使⽤, 请求
⽤不到)
CookieHeader 设置 cookie
UserAgentHeader 设置 User-Agent
QNetworkReply 表⽰⼀个 HTTP 响应. 这个类同时也是 QIODevice 的⼦类.
⽅法 说明
error() 获取出错状态.
errorString() 获取出错原因的⽂本.
readAll() 读取响应 body.
header(QNetworkRequest::KnownHeaders
header)
读取响应指定 header 的值
此外, QNetworkReply 还有⼀个重要的信号 finished 会在客⼾端收到完整的响应数据之后触
发.

4.3.2 代码⽰例

给服务器发送⼀个 GET 请求.

  1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton
    • 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的
    sizePolicy 为 Expanding
    • 再使⽤垂直布局把 QPlainTextEdit 和上⾯的⽔平布局放好. ( QPlainTextEdit 的
    readOnly 设为 true )
    • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).
    在这里插入图片描述

此处建议使⽤ QPlainTextEdit ⽽不是 QTextEdit . 主要因为 QTextEdit 要进⾏富
⽂本解析, 如果得到的 HTTP 响应体积很⼤, 就会导致界⾯渲染缓慢甚⾄被卡住.

2) 修改 widget.h, 创建 QNetworkAccessManager 属性

class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
// 新增属性
QNetworkAccessManager* manager;
};
  1. 修改 widget.cpp, 创建实例
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化属性
manager = new QNetworkAccessManager(this);
}
  1. 编写按钮的 slot 函数, 实现发送 HTTP 请求功能.
void Widget::on_pushButton_clicked()
{
// 1. 获取到输⼊框中的 URL, 构造 QUrl 对象
QUrl url(ui->lineEdit->text());
// 2. 构造 HTTP 请求对象
QNetworkRequest request(url);
// 3. 发送 GET 请求
QNetworkReply* response = manager->get(request);
// 4. 通过信号槽来处理响应
connect(response, &QNetworkReply::finished, this, [=]() {
if (response->error() == QNetworkReply::NoError) {
// 响应正确
QString html(response->readAll());
ui->plainTextEdit->setPlainText(html);
// qDebug() << html;
} else {
// 响应出错
ui->plainTextEdit->setPlainText(response->errorString());
}
response->deleteLater();
});
}

执⾏程序, 观察效果
在这里插入图片描述
发送 POST 请求代码也是类似. 使⽤ manager->post() 即可. 此处不再演⽰.

4.4 其他模块

Qt 中还提供了 FTP, DNS, SSL 等⽹络相关的组件⼯具. 此处不再⼀⼀展开介绍. 有需要的可以⾃⾏
翻阅官⽅⽂档学习相关 API 的使⽤.

5. Qt ⾳视频

5.1 Qt ⾳频

在 Qt 中,⾳频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只⽀持播放 wav 格式的⾳频⽂件。也就是说如果想要添加⾳频效果,那么⾸先需要将 ⾮wav格式 的⾳频⽂件转换为 wav 格式。
通过帮助⼿册查看 QSound 类如下:
在这里插入图片描述
注意:
使⽤ QSound 类时,需要添加模块:multimedia.

5.1.1 核⼼API概览

play() 开始或继续播放当前源。

5.1.2 ⽰例

/********************************* SoundTest.pro
*********************************/
QT += core gui multimedia //添加⾳频模块
/********************************* widget.cpp
*********************************/
#include "widget.h"
#include "ui_widget.h"
#include <QSound> //添加⾳频头⽂件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//实例化对象
QSound *sound = new QSound(":/1.wav",this);
connect(ui->btn,&QPushButton::clicked,[=](){
sound->play(); //播放
});
}
Widget::~Widget()
{
delete ui;
}

5.2 Qt 视频

在 Qt 中,视频播放的功能主要是通过 QMediaPlayer类 和 QVideoWidget类 来实现。在使⽤这两个类
时要添加对应的模块 multimedia 和multimediawidgets 。

5.2.1 核⼼API概览

setMedia() 设置当前媒体源。
setVideoOutput() 将QVideoWidget视频输出附加到媒体播放器。
如果媒体播放器已经附加了视频输出,将更换⼀个新的。

5.2.2 ⽰例

⾸先在 .pro ⽂件中添加 multimedia 和 multimediawidgets 两个模块;如下图⽰:
在这里插入图片描述

/********************************* widget.h *********************************/
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QHBoxLayout> //⽔平布局
#include <QVBoxLayout> //垂直布局
#include <QVideoWidget> //显⽰视频
#include <QMediaPlayer> //播放声⾳
#include <QPushButton> //按钮
#include <QStyle> //设置图标
#include <QFileDialog> //选择⽂件/⽂件夹
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void chooseVideo();
private:
QMediaPlayer *mediaPlayer;
QVideoWidget *videoWidget;
QVBoxLayout *vbox;
//创建两个按钮:选择视频按钮和开播放按钮
QPushButton *chooseBtn,*playBtn;
};
#endif // WIDGET_H
/********************************* widget.cpp
*********************************/
#include "widget.h"
#include <QMediaPlayer>
#include <QSlider>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//对象实例化
mediaPlayer = new QMediaPlayer(this);
videoWidget = new QVideoWidget(this);
//设置播放画⾯的窗⼝
videoWidget->setMinimumSize(600,600);
//实例化窗⼝布局---垂直布局
this->vbox = new QVBoxLayout(this);
this->setLayout(this->vbox);
//实例化选择视频按钮
chooseBtn = new QPushButton("选择视频",this);
//实例化播放按钮
playBtn = new QPushButton(this);
//设置图标代替⽂件
playBtn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
//实例化⼀个⽔平布局,将以上控件放⼊⽔平布局中
QHBoxLayout *hbox = new QHBoxLayout;
//添加控件
hbox->addWidget(chooseBtn);
hbox->addWidget(playBtn);
//将播放窗⼝和⽔平布局都添加到垂直布局中
vbox->addWidget(videoWidget);
//布局中添加布局
vbox->addLayout(hbox);
//将选择视频对应的按钮和槽函数进⾏关联
connect(chooseBtn,&QPushButton::clicked,this,&Widget::chooseVideo);
}
void Widget::chooseVideo()
{
//选择视频,返回⼀个播放视频的名字
QString name = QFileDialog::getSaveFileName(this,"选择视
频",".","WMV(*.wmv)");
//设置媒体声⾳
mediaPlayer->setMedia(QUrl(name));
//输出视频画⾯
mediaPlayer->setVideoOutput(videoWidget);
//播放
mediaPlayer->play();
}
Widget::~Widget()
{ }

eg;
添加音频
在这里插入图片描述

#include "widget.h"
#include "ui_widget.h"
#include<QSound>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);sound = new QSound(":/n/a.wav");
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{//在这里进行音频播放sound->play();
}

总结

以上就是qt系统相关的内容了。

http://www.xdnf.cn/news/15395.html

相关文章:

  • LLM表征工程还有哪些值得做的地方
  • linux打包固件shell脚本
  • FOC算法中SIMULINK一些常用模块(1)(个人留存)
  • 多客户端-服务器(select,poll)
  • 第二章 基于新版Onenet搭建云服务(stm32物联网)
  • elementPlus中的el-table实现合并单元格
  • MMKV 存储json list数据(kotlin)
  • 《Linux篇》自动化构建-make/Makefile
  • 自动润滑系统:从 “盲目养护“ 到智能精注的工业运维革命
  • MMaDA:多模态大型扩散语言模型
  • 动态规划题解_将一个数字表示成幂的和的方案数【LeetCode】
  • 互斥锁详解(操作系统os)
  • BERT系列模型
  • 前端工程化-构建打包
  • Flink数据流高效写入MySQL实战
  • Actor-Critic重要性采样原理
  • 九、官方人格提示词汇总(上)
  • 构造函数延伸应用
  • 数据结构 Map和Set
  • 一些git命令
  • SQL预编译:安全高效数据库操作的关键
  • Linux操作系统之信号概念启程
  • 【读书笔记】《C++ Software Design》第七章:Bridge、Prototype 与 External Polymorphism
  • IPC框架
  • [2025CVPR]GNN-ViTCap:用于病理图像分类与描述模型
  • 晋升指南-笔记
  • 【Docker基础】Dockerfile指令速览:环境与元数据指令详解
  • React强大且灵活hooks库——ahooks入门实践之状态管理类hook(state)详解
  • 【C++】多线程同步三剑客介绍
  • AutoLabor-ROS-Python 学习记录——第一章 ROS概述与环境搭建