<Qt> 系统 - 网络编程 | 音视频

目录

前言:

一、QUdpSocket

(一)核心 API 概览

 (二)设计一个UDP回显服务器

二、QTCPSocket

(一)核心 API 概览

(二)设计一个TCP回显服务器

三、HTTP Client

四、Qt音视频

(一)Qt 音频

(二)Qt 视频


前言:

Qt 为了支持跨平台,对网络编程的API也重新封装了。 网络编程其实编写的是应用层代码,但是需要传输层的支持,传输层的核心协议有UDP和TCP,Qt 也提供了两套API,分别是QUdpSocketQTcpSocket。实际 Qt 开发中进行网络编程,也不一定使用 Qt 封装的网络 API,也有一定可能使用的是系统原生 API 或者其他第三方框架的 API。 

 还有一点要注意的是,要想实现网络编程,还要在.pro文件中添加network模块。我们之前提到过的各种控件都包含在QtCore模块中,为了不让可执行程序变得过于庞大,导致一些性能不够好的机器承受太大的压力,所以就进行了模块化的处理,默认情况下额外的模块不会参与编译,有需要就在.pro文件中添加:

一、QUdpSocket

(一)核心 API 概览

主要的类有两个:QUdpSocket 和 QNetworkDatagram

QUdpSocket 表示一个 UDP 的 socket 文件

名称类型说明
bind(const QHostAddress&,quint16)方法绑定指定的端⼝号
receiveDatagram()方法返回 QNetworkDatagram,读取⼀个 UDP 数据报.
writeDatagram(const QNetworkDatagram&)方法发送⼀个 UDP 数据报
readyRead信号在收到数据并准备就绪后触发

QNetworkDatagram 表示一个 UDP 数据报:

名称类型说明
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 )构造函数通过 QByteArray,⽬标 IP 地址,⽬标端⼝号 构造⼀个 UDP 数据报,通常⽤于发送数据时
data()方法获取数据报内部持有的数据,返回QByteArray
senderAddress()方法获取数据报中包含的对端的 IP 地址
senderPort()方法获取数据报中包含的对端的端⼝号

 (二)设计一个UDP回显服务器

代码示例:设计一个UDP回显服务器

在ui界面中设置一个 QListWidget 来显示客户端消息日志:

在写代码之前一定要在.pro文件中添加network模块。也记得要在头文件声明成员和成员函数(这里就不显示出来了)。

写一个服务器首先就要有一个Socket对象,之后就要连接信号和槽,捕捉readyRead信号,对应的槽函数就要完成服务器的核心逻辑,之后就是bind端口号,一个Udp服务器就做好了。 

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 创建出套接字对象socket = new QUdpSocket(this);// 设置窗口标题this->setWindowTitle("服务器");// 连接信号槽connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);// 绑定端口号bool ret = socket->bind(QHostAddress::Any, 8080);if(!ret){QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());return;}
}Widget::~Widget()
{delete ui;
}

完成处理请求的过程:

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
// 完成处理请求的过程
void Widget::processRequest()
{// 1.读取请求并解析const QNetworkDatagram &requestDatagram = socket->receiveDatagram();QString request = requestDatagram.data();// 返回的是一个QByteArray,可以赋值给QString// 2. 根据请求计算响应(由于是回显服务器,响应不需要计算,就是请求本身)const QString &response = process(request);// 3. 把响应写回到客户端QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());socket->writeDatagram(responseDatagram);// 显示打印日志(将交互消息显示到界面)QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())+ "] req: " + request + ", resp: " + response;ui->listWidget->addItem(log);
}QString Widget::process(const QString &request)
{// 由于当前是回显服务器,响应和请求完全一样return request;
}

Udp使用的是数据报的形式,所以接收要接收一个数据报对象,这个数据报中有对端发来的数据和其他属性字段。给客户端进行响应的时候,也要响应一个数据报,构建一个数据报对象,再填充数据,使用toUtf8就可以把QString转换成QByteArray。最后再显示到服务器的QListWidget中就可以了。


下面就是客户端的界面了,给客户端设计一个界面。有一个回显框、输入框和发送按钮,再使用布局管理器修饰一下,调整一下垂直布局管理器,让下面的发送栏宽一点:

没有变宽就是因为没有调整下面两个控件的sizePolicy,都设置成Expanding就可以了:

我们想要实现的功能是现在输入框输入内容,点击发送按钮发送给服务端,所以先写一个按钮的槽函数:

void Widget::on_pushButton_clicked()
{// 1. 获取输入框的内容const QString &text = ui->lineEdit->text();ui->lineEdit->setText("");// 2. 构造 UDP 的请求数据QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);// 3. 发送请求数据socket->writeDatagram(requestDatagram);// 4. 把发送的请求也添加到列表框中ui->listWidget->addItem("客户请求: " + text);// 5. 清空输入框内容ui->lineEdit->setText("");
}

现在客户端就有了发送的能力,接下来就要写接收服务端数据的代码了:

#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>const QString &SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8080;Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);socket = new QUdpSocket(this);// 修改窗口标题,方便区分这是一个客户端程序this->setWindowTitle("客户端");// 通过信号槽来处理服务器返回的数据connect(socket, &QUdpSocket::readyRead, this, &Widget::processReponse);
}Widget::~Widget()
{delete ui;
}void Widget::processReponse()
{// 通过这个槽函数处理收到的响应// 读取响应数据const QNetworkDatagram& responseDatagram = socket->receiveDatagram();const QString& response = responseDatagram.data();// 把响应数据显示到界面中ui->listWidget->addItem(response);
}

端口到本质上是一个 2 字节的无符号整数。

quint16:本质上就是一个 unsigned short(虽然 short 通常都是 2 个字节,但是 C++ 标准中没有明确规定这一点,只是说 short 不应该少于 2 个字节)。

最终执行效果:

客户端服务器测试的基本原则:一定是先启动服务器,后启动客户端。

启动多个客户端都可以正常工作,但是不能在界面选择直接运行,否则会覆盖上一个客户端:

二、QTCPSocket

(一)核心 API 概览

核心类是两个:QTcpServer 和 QTcpSocket

QTcpServer 用于监听端口,和获取客户端连接。

名称类型说明对标原生API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号并开始监听bind和listen
nextPendingConnection()方法

从系统中获取一个已经建立好的tcp连接

返回一个TcpSocket,表示这个连接

通过这个socket对象完成与客户端之间的通信

accept
newConnection信号有新的客户端建立好连接后触发无(类似与IO多路复用中的通知机制)

QTcpSocket 用户客户端和服务器之间的数据交互。

API类型说明对标原生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。

(二)设计一个TCP回显服务器

代码示例:设计一个UDP回显服务器

在ui界面中设置一个 QListWidget 来显示客户端消息日志:

在写代码之前一定要在.pro文件中添加network模块。也记得要在头文件声明成员和成员函数(这里就不显示出来了)。

客户端和服务端的界面都是不变的,变得是这是一个TCP服务器,除了bind还需要设置成监听状态,使用listen方法就可以完成,只要有新的连接就会触发newConnection信号。

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 1. 修改窗口标题this->setWindowTitle("服务器");// 2. 创建QTcpServer的示例tcpServer = new QTcpServer(this);// 3. 通过信号槽,指定如何处理连接connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);// 4. 绑定并监听端口bool ret = tcpServer->listen(QHostAddress::Any, 8080);if(!ret){QMessageBox::critical(nullptr, "服务器启动失败", tcpServer->errorString());exit(1);}
}Widget::~Widget()
{delete ui;
}

接下来就是设置好listen状态后,触发了newConnection信号之后执行processConnection的操作:

void Widget::processConnection()
{// 1. 通过tcpServer拿到一个socket对象,通过这个对象来和客户端进行通信QTcpSocket *clientSocket = tcpServer->nextPendingConnection();// peerAddress 表示对端的IP地址QString log = "[" + 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());// d. 把上述信息记录到日志中QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort())+ "] req: " + request + ", resp: " + response;ui->listWidget->addItem(log);});// 3. 通过信号槽,处理客户端断开连接的情况connect(clientSocket, &QTcpSocket::disconnected, this, [=](){// a.把断开连接的信息通过日志显示出来QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort())  + "] 客户端下线";ui->listWidget->addItem(log);// b.手动释放 clientSocketclientSocket->deleteLater();});
}QString Widget::process(const QString request)
{// 因为这里写的是回显服务器,所以请求和响应完全一样return request;
}

上述代码其实不够严谨,但在这里作为回显服务器已经够了。实际在使用 TCP 的过程中,TCP 是面向字节流的,一个完整的请求可能会分成多段字节数组进行传输。虽然 TCP 已经帮我们处理了很多棘手的问题,但是 TCP 本身并不负责区分从哪里到哪里是一个完整的应用层数据(粘包问题)。更严谨的做法:每次收到的数据都给它放到一个字节数组缓冲区中,并且提前约定好应用层协议的格式(分隔符 / 长度 / 其他办法),再按照协议格式对缓冲区数据进行更细致的解析处理。


下面就是客户端的界面了,同UDP界面一致:

下面就是客户端的代码了,除了要维护连接,编写上和Udp客户端没有太大的差别:

#include "widget.h"
#include "ui_widget.h"#include <QMessageBox>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", 8080);// 4. 链接信号槽,处理响应connect(socket, &QTcpSocket::readyRead, this, [=](){// a. 读取相应内容QString response = socket->readAll();// b. 把相应内容显示到界面上ui->listWidget->addItem(QString("服务器说: ") + response);});// 5. 等待连接确立的结果,并确认是否连接成功bool ret = socket->waitForConnected();if(!ret){QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());exit(1);}
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{// 1. 获取到输入框中的内容const QString &text = ui->lineEdit->text();// 2. 发送消息给服务器socket->write(text.toUtf8());// 3. 把发的消息显示到界面上ui->listWidget->addItem(QString("客户端说: ") + text);// 4. 清空输入框的内容ui->lineEdit->setText("");
}

先启动服务器,再启动客户端(可以启动多个),最终执行效果:

由于我们使用信号槽处理同一个客户端的多个请求,不涉及到循环,也就不会使客户端之间相互影响了:

三、HTTP Client

进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议。

  • 通过 HTTP 从服务器获取数据
  • 通过 HTTP 向服务器提交数据

TTP相比TCP/UDP还要使用的更多一点,而HTTP协议本质上是基于TCP协议实现的,也就是封装了TcpSocketQt 只是提供了 HTTP客户端,并没有提供服务端

下面是核心API,三个类,分别是QNetworkAccessManager,QNetworkRequest,QNetworkReply

 QNetworkAccessManager 提供了HTTP的核心操作:

方法说明
get(const QNetworkRequest& )发起⼀个 HTTP GET 请求,返回 QNetworkReply 对象
post(const QNetworkRequest& , const QByteArray& )发起⼀个 HTTP POST 请求,返回 QNetworkReply 对象

QNetworkRequest 表示一个 HTTP 请求(不包含请求正文 body),想要发送一个带有body的请求需要再QNetworkAccessManager的post方法中的参数传入body:

方法说明
QNetworkRequest(const QUrl& )通过 URL 构造⼀个 HTTP 请求
setHeader(QNetworkRequest::KnownHeaders header,const QVariant &value)设置请求头

其中 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值为:

取值说明
ContentTypeHeader描述 body 的类型
ContentLengthHeaderContentLengthHeader
ContentLengthHeader⽤于重定向报⽂中指定重定向地址(响应中使⽤,请求⽤不到)
CookieHeaderCookieHeader
UserAgentHeader设置 User-Agent

QNetworkReply 表示一个 HTTP响应,这个类同时也是 QIODevice 的子类。QNetworkReply 还有一个重要的信号 finishied,在客户端收到完整的响应数据后触发:

常用方法说明
error()获取出错状态
errorString()获取出错原因的⽂本
readAll()读取响应 body
header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值

下面就来写一个HTTP客户端,使用的界面与上面的差不多,通过指定一个Url发送请求,响应的结构大概率是一个 HTML,这里使用的是 QPlainTextEdit 来表示:

注意:此处建议使用 QPlainTextEdit,而不是 QTextEdit。主要是因为 QTextEdit 要进行富文本解析,最终显示的结果就不是原始的 HTML 了,如果得到的 HTTP 响应体积很大,会导致界面渲染缓慢甚至被卡住。


在写代码之前一定要在.pro文件中添加network模块。也记得要在头文件声明成员和成员函数(这里就不显示出来了)。

在构造函数中设置一下标题,并new一个 QNetworkAccessManager 对象,之后就可以写槽函数了: 

#include "widget.h"
#include "ui_widget.h"
#include <QNetworkReply>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);this->setWindowTitle("客户端");manager = new QNetworkAccessManager(this);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{// 1. 获取到输入框中的urlQUrl url(ui->lineEdit->text());// 2. 构造一个HTTP请求对象QNetworkRequest request(url);// 3. 发送请求QNetworkReply *response = manager->get(request);// 4. 通过信号槽来处理响应connect(response, &QNetworkReply::finished, this, [=](){if(response->error() == QNetworkReply::NoError){// 响应正确并获取到了QString html = response->readAll();ui->plainTextEdit->setPlainText(html);}else{// 响应出错了ui->plainTextEdit->setPlainText(response->errorString());}// 还需要对response进行释放response->deleteLater();});
}

运行效果如下,输入一个Url就会返回一个html格式的文本: 

发送 POST 请求代码也是类似,使用 manager->post() 即可。

实际开发中,HTTP Client 获取到的的数据也不一定非得是 HTML,更大的可能性是客户端开发和服务器开发约定好交互的数据格式。按照约定的格式,客户端拿到之后进行解析,并显示到界面上。

四、Qt音视频

(一)Qt 音频

在 Qt 中,音频主要通过 QSound 类来实现。但是需要注意的是 QSound 类只支持播放 wav 格式的音频文件。在这之前也需要先引入 multimedia 模块,最核心的API就是play方法,用来播放音频。

在界面中添加一个按钮,命名为播放,当我们点击按钮,就会播放音乐。首先要有一个wav后缀的文件,像这种文件还是使用qrc来保存。

class MainWindow : public QMainWindow
{Q_OBJECTprivate slots:void on_pushButton_clicked();private:QSound* sound;
};MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);sound = new QSound(":/music/zjl_qingtian.wav", this);
}void MainWindow::on_pushButton_clicked()
{// 在这里进行音频播放sound->play();
}

(二)Qt 视频

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

方法说明
setMedia()设置当前媒体源。
setVideoOutput()

将 QVideoWidget 视频输出附加到媒体播放器。

如果媒体播放器已经附加了视频输出,将更换一个新的。

class Widget : public QWidget
{Q_OBJECT
public:// ...
private:Ui::Widget *ui;QMediaPlayer *mediaPlayer; // 播放声音QVideoWidget *videoWidget; // 显示视频//创建两个按钮:选择视频按钮和播放按钮QPushButton *chooseBtn, *playBtn;
};

 接下来就是设置视频播放窗口的代码:

#include "widget.h"
#include "ui_widget.h"#include <QMediaPlayer>
#include <QSlider>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 对象实例化mediaPlayer = new QMediaPlayer(this);videoWidget = new QVideoWidget(this);// 设置播放窗口videoWidget->setMinimumSize(600, 600);// 垂直布局QVBoxLayout *vbox = new QVBoxLayout();this->setLayout(vbox);// 实例化按钮chooseBtn = new QPushButton("选择视频", this);playBtn = new QPushButton(this);// 设置图标playBtn->setIcon(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, [=](){// 选择视频,返回视频的路径QString url = QFileDialog::getOpenFileName(this, "选择视频");// 设置声音mediaPlayer->setMedia(QUrl(url));// 输出画面mediaPlayer->setVideoOutput(videoWidget);// 播放mediaPlayer->play();});
}Widget::~Widget()
{delete ui;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400711.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

msgqueue.hpp队列模块

目录 一.MsgQueue模块介绍 二.MsgQueue类的实现 成员变量 构造函数与析构函数 成员函数 参数设置函数 setArgs 参数获取函数 getArgs 三.MsgQueueMapper类的实现 成员变量 构造函数 成员函数 创建表格函数 createTable 删除表格函数 dropTable 插入数据函数 inse…

GPT-4o:开启多模态AI识别新纪元

GPT-4o功能简介 在人工智能的演变历程中&#xff0c;图像识别技术始终占据着核心地位。技术的发展日新月异&#xff0c;使得AI不仅能够识别图像内容&#xff0c;还能将其转化为文字描述。特别值得一提的是&#xff0c;OpenAI在春季发布的GPT-4o模型&#xff0c;将图像识别技术…

微软Detours Hook库编译与使用

Detours 是微软开发的一个强大的Windows API钩子库&#xff0c;用于监视和拦截函数调用。它广泛应用于微软产品团队和众多独立软件开发中&#xff0c;旨在无需修改原始代码的情况下实现函数拦截和修改。Detours 在调试、监控、日志记录和性能分析等方面表现出色&#xff0c;已成…

shell命令行解释器—既陌生有熟悉的东西

今天做一个感性的认识来&#xff0c;用一个生活的例子。 你生活在有一条村子里面&#xff0c;在村的东边就是王婆&#xff0c;王婆呢&#xff1f;她主要做什么呢啊&#xff1f;她在村儿里面呢&#xff0c;也不种地啊&#xff0c;那她干什么呢&#xff1f;他主要做帮别人进行婚嫁…

【TabBar嵌套Navigation案例-发现页面-按钮上的图片旋转 Objective-C语言】

一、接下来,我们来做这个,点击以后,让它出一个蓝色的View 1.就是我们示例程序的这种效果, 一点击,让这个按钮旋转,然后呢,再让它出来一个蓝色的View, 首先,我们要去监听它的点击事件,这是第一,我点击以后,我要做一些什么样的操作,要有点击事件, 所以呢,我要把…

JS基础进阶Webs-API、HTML 、DOM

一、JS中的API 1. 定义 JavaScript API是指为JavaScript提供的一组编程接口和对象&#xff0c;用以允许开发者访问和操作Web浏览器或其他JavaScript环境&#xff08;如Node.js&#xff09;提供的特定功能。这些API使得开发者能够编写更加动态和交互式的Web应用程序。 2. 主要…

服务器数据恢复—raid5阵列热备盘未全部启用导致阵列崩溃的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台EMC某型号存储中有一组RAID5磁盘阵列。该raid5阵列中有12块硬盘&#xff0c;其中2块硬盘为热备盘。 服务器存储故障&#xff1a; 该存储raid5阵列中有两块硬盘离线&#xff0c;只有1块热备盘启用替换掉其中一块离线盘&#xff0c;另外…

​产品经理-​你如何理解“互联网思维(35)

在产品规划和功能改版中&#xff0c;确实非常重视用户需求和体验。产品需求是互联网产品的核心 用户体验是互联网产品的重点。在互联网新产品规划中&#xff0c;会非常重视用户验证环节 确保做出来的东西确实是用户想要的&#xff1b;而在已经上线的产品中&#xff0c;往往会有…

人工智能与机器学习原理精解【12】

文章目录 分级聚类理论分级聚类的详细说明1. 定义2. 算法3. 计算4. 例子5. 例题 皮尔逊相关系数 julia实现 参考文献 分级聚类 理论 分级聚类的详细说明 1. 定义 分级聚类&#xff08;Hierarchical Clustering&#xff09;&#xff0c;又称为层次聚类&#xff0c;是一种通过…

谷歌反垄断官司败诉后,或又面临被拆分风险?

KlipC报道&#xff1a;上周8月5日&#xff0c;美国法院裁定谷歌的搜索业务违反了美国反垄断法&#xff0c;非法垄断在线搜索和搜索文本广告市场。据悉&#xff0c;胜诉的美国司法部正在考虑拆分谷歌。其他选项包括强制谷歌与竞争对手分享更多数据&#xff0c;以及防止其在人工智…

【二叉树进阶】--- 根据二叉树创建字符串

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构 从本篇文章开始&#xff0c;博主将分享一些结合二叉树的进阶算法题。 &#x1f3e0; 根据二叉树创建字符串 &#x1f4cc; 题目内容 根据二叉…

从行为面试问题(behavioral questions)看中美程序员差异。

中美程序员在职场中的工作状态和职能、福利等有很大区别&#xff0c;从面试中的BQ轮就可见一斑。 中美程序员的面试轮差异&#xff1f; 国内的面试轮在不同公司间差异很大&#xff0c;但总体的问题类型包含笔试面试&#xff08;算法题、概念题、项目深挖、职业目标、职场文化…

FGUI+TS如何实现数字翻滚

FGUITS如何实现数字翻滚 实现效果如下&#xff1a; 实现步骤&#xff1a; fgui制作组件和特效 fgui制作组件&#xff0c;设置一条竖向数字包含1-9或者小数点符号等&#xff0c;可见区域为一个数字大小&#xff0c;最好可见区域紧贴数字&#xff0c;这样滚动的时候滚动区域范围…

深度学习------------------卷积神经网络(LeNet)

目录 LeNet网络手写的数字识别MNIST总结卷积神经网络&#xff08;LeNet&#xff09; 问题 LeNet网络 手写的数字识别 MNIST ①输入的是&#xff1a;3232的image ②放到一个55的卷积层里面&#xff08;为什么是5&#xff1f;因为32-x128&#xff0c;∴x5&#xff09;&#xff0c…

【教程】Ubuntu给pycharm添加侧边栏快捷方式

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 以下教程不仅限于pycharm&#xff0c;其他软件也是一样操作 1、进入到pycharm的目录&#xff0c;先通过命令行打开pycharm&#xff1a; ./bin/pycharm…

keepalived+haproxy高可用负载均衡集群

简介 使用haproxy制作负载均衡集群&#xff0c;keepalived通过状态检测脚本检测本机haproxy状态&#xff0c;若为离线状态&#xff0c;则会降低该节点的优先级。 实验准备 四台虚拟机&#xff1a;KA1、KA2为keepalivedhaproxy&#xff0c;web1、web2为后端服务器&#xff0c;均…

阿里云-java调用短信服务,第三方接口的开启(傻瓜式教程)

第一步&#xff1a;在浏览器中&#xff0c;搜索阿里云 第二步&#xff1a;打开aly的主页 第三步&#xff1a;在最上方的导航栏中&#xff0c;找到云市场&#xff0c;注意不要点击&#xff0c;会自动有触发悬浮框出现&#xff0c;在悬浮框中找到 短信 第四步&#xff1a;点击 短…

无人机之电池注意事项

1、外场作业时&#xff0c;电池一定要放置在阴凉处&#xff0c;避免太阳直射&#xff1b; 2、刚作业完的电池发热严重时&#xff0c;请降至室温再充电&#xff1b; 3、注意电池状态&#xff0c;一旦发现电池出现鼓包、漏液等现象&#xff0c;必须马上停止使用&#xff1b; 4…

UE5 C++项目的配置

创建项目 首先启动UE5,然后选择要创建的项目&#xff0c;选择c进行创建 创建项目完毕之后&#xff0c;会自动打开visual studio&#xff0c;页面如下图所示 点击总体配置状态的刷新按钮&#xff0c;会自动检测总体的配置状态 一般会在下图所示的两项出现警告 Unreal Engi…

舵机模块学习

舵机是一种根据输入PWM信号占空比来控制输出角度的装置 执行逻辑&#xff1a;PWM信号输入到控制板&#xff0c;给控制版一个指定的目标角度&#xff0c;然后电位器检测输出轴的当前角度&#xff0c;如果大于目标角度&#xff0c;电机反转&#xff0c;小于正转&#xff0c;最终使…