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

QT基于TCP协议实现数据传输以及波形绘制——安卓APP及Windows程序双版本

文章代码有非常非常之详细的解析!!!诸位可放心食用

这个玩意我做了两个,一个是安卓app,一个是Windows程序。代码并非全部都是由我从无到有实现,只是实现了我想要的功能。多亏了巨人的肩膀,开源万岁!!!

我把程序放到GitHub上,需要的可自取。

安卓app:

windows程序:

(上面暂且为空,过两天把代码整理好就发上来)

下面我分别对安卓app版本和Windows程序版本进行简要技术整理

安卓app:

首先需要对安卓环境进行配置,这个不同的QT版本对应不同的SDK,NDK那些玩意,在这里不做详细解析。

配置环境完成以后就可以使用工程了,烧入手机以后呈现出来的效果是这样的。

 整个工程的框架是:

1、ui页面绘制

ui页面有两个

还有一个就是上面的效果图

主要是一些PushButton、Lable、CharView控件

2、控件槽函数的设计

2.1、第二张图的“全国大学生电子设计大赛上位机”的PushButton

void Widget2::on_MenuBt1_clicked()
{if(Widget::flag == 0)//通过判断静态成员变量flag来判断是否创建了Widget{Widget *first = new Widget;//创建一个新的 Widgetfirst->setGeometry(this->geometry());返回当前 Widget2 对象的位置和大小信息,用于Widget的呈现first->show();//将新创建的 Widget 对象显示在屏幕上}this->close();//关闭Widget2
}

这段代码的功能就是按下按钮以后,创建一个Widget并关闭Widget2,实现简单的跳转功能。

2.2、按下开启服务器按钮之后的槽函数

void Widget::on_pushButton_Open_clicked()
{if(net_flag == 0)//检查是否连接网络{if(ui->lineEdit_IP->currentText() != "")//如果IP地址栏不为空{QString str = ui->lineEdit_IP->currentText();//收集当前IP地址栏的IP地址if(isIpAddr(str))//判断IP地址是否有效{address.setAddress(str);//设置收集的IP地址为当前IP地址if(ui->lineEdit_Port->text() != "")//判断端口栏是否为空{port = ui->lineEdit_Port->text().toUInt();//设置当前端口栏的数字为端口号,并且转为uint16_t格式net_flag = 1;//网络连接标志tcpServer->listen(address,port);//传入IP地址和端口号开始监听connect(tcpServer,SIGNAL(newConnection()),this,SLOT(TCPnewconnect_slot()));//当连接成功后,将 newConnection 信号连接到 this(即 Widget 对象)的 TCPnewconnect_slot() 槽函数ui->label_show->setText("正在连接...");//ui页面上的label_show发送字符串"正在连接..."}else//与上面对应,如果端口栏为空{QMessageBox::warning(NULL, QStringLiteral("警告"), QStringLiteral("端口号不能为空"),QMessageBox::Ok | QMessageBox::Ok);}}else//与上面对应,如果IP地址无效{QMessageBox::warning(NULL, QStringLiteral("警告"), QStringLiteral("IP不合法"),QMessageBox::Ok | QMessageBox::Ok);//弹窗警告}}else//与上面对应,如果为IP地址栏为空{QMessageBox::warning(NULL, QStringLiteral("警告"), QStringLiteral("IP不能为空"),QMessageBox::Ok | QMessageBox::Ok);//弹窗警告}}
}

这个槽函数的作用是:根据IP地址栏和端口号栏的输入信息,判断是否有效以后,根据提供的IP地址和端口号开启监听,并通过connect函数开启一个TCP server,如果连接客户端成功则转到另外一个槽函数TCPnewconnect_slot(),并且输出“连接成功”的信息。

TCPnewconnect_slot():
void Widget::TCPnewconnect_slot()
{tcpSocket = tcpServer->nextPendingConnection();//获取新的客户端连接,并将返回的 QTcpSocket 对象赋值给 tcpSocket 成员变量connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(TCPreadyread_slot()));//当有TcpSocket的readyRead()信号,也就是客户端有数据发送上来的时候,连接当前Wiget的槽函数TCPreadyread_slot()ui->label_show->setText("网络已连接");//标签显示
}

这个槽函数收到客户端发送的数据以后,再跳转到TCPreadyread_slot()槽函数,这个槽函数是数据处理这块的,我到第四大点的时候再详细讲。

2.3、按下关闭服务器按钮以后的槽函数

void Widget::on_pushButton_Close_clicked()
{if(net_flag == 1)//判断网络是否连接,如果是,往下{tcpSocket->close();//关闭tcpSocket,断开与当前客户端的连接tcpServer->close();//关闭tcpServer,停止监听新的客户端连接disconnect(tcpServer,SIGNAL(newConnection()),this,SLOT(TCPnewconnect_slot()));//将tcpServer的newConnection()信号与槽函数TCPnewconnect_slot()断开连接disconnect(tcpSocket,SIGNAL(readyRead()),this,SLOT(TCPreadyread_slot()));//将tcpSocket的readyRead()信号与槽函数TCPreadyread_slot()断开连接net_flag = 0;//将flag置为0,表示网络断开}ui->label_show->setText("连接已关闭");//输出标签信息
}

这个槽函数是断开对客户端的监听和连接,断开各种信号与相应槽函数的连接,关闭服务器。

3.4、按下扫描按键以后的槽函数

void Widget::on_Scan_clicked()
{ui->lineEdit_IP->clear();//IP地址栏全部清空QList<QString> strIpAddress;QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();//获取主机所有IP地址并存放在ipAddressesList这个列表里// 获取第一个本主机的IPv4地址int nListSize = ipAddressesList.size();for (int i = 0; i < nListSize; ++i){if (ipAddressesList.at(i) != QHostAddress::LocalHost &&ipAddressesList.at(i).toIPv4Address())//排除主机IP地址,并且保证IP地址是IPV4格式{strIpAddress.append(ipAddressesList.at(i).toString());//将满足条件的IP地址放入strIpAddress里面// break;}}// 如果没有找到,则以本地IP地址为IPif (strIpAddress.isEmpty())strIpAddress.append(QHostAddress(QHostAddress::LocalHost).toString());ui->lineEdit_IP->addItems(strIpAddress);//把IP地址显示到IP地址栏}

这个槽函数是用于查找设备当中的IP地址,并且将它显示到IP地址栏。

3、绘图板块的设计

    //设置坐标轴chart2->addSeries(line2);// 添加数据线 line2 到图表 chart2 中chart2->setTheme(QChart::ChartThemeQt);//设置图表 chart2 的主题为 Qt 默认的主题line2->setName("时域波形");//数据线名称line2->setColor(Qt::red);//数据线的颜色//创建两个 QValueAxis 对象 axisX2 和 axisY2,用于设置 X 轴和 Y 轴的显示样式和属性QValueAxis *axisX2 = new QValueAxis;QValueAxis *axisY2 = new QValueAxis;axisX2->setLabelFormat("%.0f");//显示格式为 "%.0f",即只显示整数部分axisX2->setLabelsAngle(45);//标签角度为 45 度(x轴或者y轴的刻度标签)axisX2->setLabelsColor(Qt::blue);//标签颜色axisY2->setLabelFormat("%.0f");axisY2->setLabelsAngle(45);axisY2->setLabelsColor(Qt::blue);axisX2->setRange(0,200);//x轴的范围axisY2->setRange(0,255);//y轴的范围axisX2->setGridLineVisible(true);//设置网格线是否可见axisX2->setGridLineColor(Qt::black);//网格线的颜色axisX2->setMinorTickCount(1);//精度设置为1axisX2->setMinorGridLineColor(Qt::black);//设置小网格为黑色axisX2->setMinorGridLineVisible(true);//设置小网格可见axisX2->setLabelsVisible(false); //设置刻度是否显示axisY2->setGridLineVisible(true);axisY2->setGridLineColor(Qt::black);axisY2->setMinorTickCount(1);axisY2->setMinorGridLineColor(Qt::black);axisY2->setMinorGridLineVisible(true);axisY2->setLabelsVisible(false); //设置刻度是否显示//将 X 轴和 Y 轴添加到图表 chart2 中chart2->addAxis(axisX2,Qt::AlignBottom);chart2->addAxis(axisY2,Qt::AlignLeft);chart2->layout()->setContentsMargins(0, 0, 0, 0);//设置外边界全部为0chart2->setMargins(QMargins(0, 0, 0, 0));//设置内边界全部为0chart2->setBackgroundRoundness(0);//设置背景区域无圆角line2->attachAxis(axisX2);//将数据线 line2 附加到 X 轴 axisX2line2->attachAxis(axisY2);//将数据线 line2 附加到 Y 轴 axisY2ui->widget_2->setChart(chart2);//显示图表 chart2

4、数据处理板块设计

根据第二大点所遗留下来的问题,TCPnewconnect_slot()槽函数收到客户端发送的数据以后,再跳转到TCPreadyread_slot()槽函数。数据处理就在TCPreadyread_slot()里面进行。


extern uint8_t  cmd_buffer[A_CMD_MAX_SIZE];
static uint16_t  size = 0;
void Widget::TCPreadyread_slot()
{uint16_t  size = 0;//定义两个数组QByteArray temp1;QByteArray temp2;int iterationCount = 0; // 用于计数循环迭代次数do {temp2 = temp1;//先将temp1中的数传递给temp2temp1 = tcpSocket->readAll();//temp1读客户端发送的数据iterationCount++;//次数加一qDebug() << "Iteration:" << iterationCount;打印次数qDebug() << "Data read in this iteration:" << temp2;//打印temp2里面的值} while (!temp1.isEmpty());//当temp1里面没有数据时,退出循环qDebug() << "temp2 len is:" << temp2.length();//打印数据的长度// 将QByteArray转换为QStringQString dataStr = QString::fromUtf8(temp2);//把数组里面的数据转化为字符串// 使用split函数按逗号拆分字符串,得到QStringListQStringList strList = dataStr.split(',');// 创建一个整型数组,用于存储拆分后的数字QList<int> dataArray;// 遍历QStringList,将每个字符串转换为整数并存储到dataArray中//注意这里和C语言的用法不太一样,它的初始化部分和执行条件以及迭代部分会自动检测更新for (const QString& str : strList) {bool ok;//布尔变量🆗,用于存储ture和false两个值int number = str.toInt(&ok);//如果成功,则布尔变量返回ture,并且把值存放在number中,否则布尔变量为falseif (ok) {//如果布尔变量为turedataArray.append(number);//将number里面的值放入dataArray数组里面}}// 输出拆分后的整数数组for (int number : dataArray) {//输出dataArray里面的数qDebug() << number;}qDebug() << "dataArray len is:" << dataArray.length();//长度for(int i = 0;i<dataArray.length();i++){A_queue_push(dataArray[i]);                            //添加指令里面的数据size = A_queue_find_cmd(cmd_buffer,A_CMD_MAX_SIZE);    //从缓冲区中获取一条指令 ,这里会有个返回值size,如果size=0,代表没有一条完整的指令,自然叶无法进入指令处理函数里面去(一条完整的指令包括帧头和帧尾以及数据体)                                               if(size>0)                                              //接收到指令{qDebug() << "Contents of cmd_buffer:";for (uint16_t i = 0; i < size; i++) {qDebug() << static_cast<int>(cmd_buffer[i]);}qDebug() << "size:" << size;A_ProcessMessage(cmd_buffer, size,ui);                             //指令处理}}}

经过上面的分析我们知道,数据处理主要是对指令的处理。而指令处理主要包括以下三个函数

A_queue_push(dataArray[i]);
A_queue_find_cmd(cmd_buffer,A_CMD_MAX_SIZE); 
A_ProcessMessage(cmd_buffer, size,ui); 

下面逐个来分析:

A_queue_push(dataArray[i]);

#define A_CMD_HEAD 0XEE                                                  //帧头
#define A_CMD_TAIL 0xFFFCFFFF                                           //帧尾(这里需要注意以下,不是一个数喔,不然数据溢出没法判断是不是帧尾咯)typedef struct A_QUEUE
{uint16_t _head;                                                       //队列头uint16_t _tail;                                                       //队列尾uint8_t _data[A_QUEUE_MAX_SIZE];                                       //队列数据缓存区
} A_QUEUE;static A_QUEUE A_que = {0,0,0};                                            //指令队列
static uint32_t A_cmd_state = 0;                                           //队列帧尾检测状态
static uint16_t A_cmd_pos = 0;                                              //当前指令指针位置/*!
*  \brief  清空指令数据
*/
void A_queue_reset()
{A_que._head = A_que._tail = 0;A_cmd_pos = A_cmd_state = 0;
}
/*!
* \brief  添加指令数据
* \detial 串口接收的数据,通过此函数放入指令队列
*  \param  _data 指令数据
*/
void A_queue_push(uint32_t _data)
{uint16_t pos = (A_que._head+1)%A_QUEUE_MAX_SIZE; //每来一个数据队列头就会+1if(pos!=A_que._tail)                                                //非满状态{A_que._data[A_que._head] = _data;        //数据依次进入依次存放在队列里面A_que._head = pos;                       //队列头每来一个数据+1的基础// 添加调试信息qDebug() << "A_queue_push: Data added to queue:" << static_cast<int>(_data);qDebug() << "A_queue_push: Queue head:" << A_que._head << "Queue tail:" << A_que._tail;}}

A_queue_push(dataArray[i]);的作用是在dataArray数组遍历的条件下,将数组里面的数据依次放入队列中。

A_queue_find_cmd(cmd_buffer,A_CMD_MAX_SIZE);

//从队列中取一个数据
static void queue_pop(uint8_t* _data)
{if(A_que._tail!=A_que._head)                                          //非空状态{*_data = A_que._data[A_que._tail];A_que._tail = (A_que._tail+1)%A_QUEUE_MAX_SIZE;}
}//获取队列中有效数据个数
uint16_t A_queue_size()
{return ((A_que._head+A_QUEUE_MAX_SIZE-A_que._tail)%A_QUEUE_MAX_SIZE);
}
/*!
*  \brief  从指令队列中取出一条完整的指令
*  \param  cmd 指令接收缓存区
*  \param  buf_len 指令接收缓存区大小
*  \return  指令长度,0表示队列中无完整指令
*/
uint16_t A_queue_find_cmd(uint8_t *buffer,uint16_t buf_len)//qdata uint8_t   qsize uint16_t
{uint16_t cmd_size = 0;uint8_t _data = 0;while(A_queue_size()>0)//当队列的长度(有效数字个数)大于0时往下{//取一个数据queue_pop(&_data);if(A_cmd_pos==0&&_data!=A_CMD_HEAD)                               //指令第一个字节必须是帧头,否则跳过{qDebug() << "Skipping data:" << static_cast<int>(_data);continue;}// 输出当前取出的数据和指令指针位置qDebug() << "A_queue_find_cmd: Current data: " << _data << " A_cmd_pos: " << A_cmd_pos;if(A_cmd_pos<buf_len)                                           //防止缓冲区溢出,这里传入的形参是A_CMD_MAX_SIZE(一条指令最大的大小),也就是2048buffer[A_cmd_pos++] = _data;//每进入一次while循环,数据会依次更新到buffer里面A_cmd_state = ((A_cmd_state<<8)|_data);   //拼接最后4个字节,组成一个32位整数,如果不是最后四个字节,那自然无法进入下面的if语句qDebug() <<" A_cmd_state: " << A_cmd_state;//最后4个字节与帧尾匹配,得到完整帧if(A_cmd_state==A_CMD_TAIL){cmd_size = A_cmd_pos;                                       //指令字节长度A_cmd_state = 0;                                            //重新检测帧尾巴A_cmd_pos = 0;                                              //复位指令指针#if(CRC16_ENABLE)//去掉指令头尾EE,尾FFFCFFFF共计5个字节,只计算数据部分CRCif(!CheckCRC16(buffer+1,cmd_size-5))                      //CRC校验return 0;cmd_size -= 2;                                            //去掉CRC16(2字节)
#endifqDebug() << "Found complete command! Command size:" << cmd_size;return cmd_size;                        //返回指令的大小}}return 0;                                                         //没有形成完整的一帧
}

这个函数是寻找一条完整的指令,如果寻找成功则返回指令的字节大小。解析里面有说一条完整的指令需要帧头和帧尾,如果不满足则无法获取cmd_size的值,也就无法进入数据处理函数A_ProcessMessage(cmd_buffer, size,ui);所以发送指令的时候一定要按照标准的格式来发送。我的数据格式如下:

		int touAndCmd[] = {0XEE,0x03,0,1};//帧头以及以下id号,这篇代码下面就讲int dataBuffer[] = {10,20,30,40,50,60,70,80,90,100,110,100,90,80,70,60,50,40,30,20,10};int zhenwei[] = {0xFF,0xFC,0xFF,0xFF};//真尾,别傻傻的0xFFFCFFFF了(手动狗头)int len = sizeof(touAndCmd)/sizeof(touAndCmd[0]);int len1 = sizeof(dataBuffer)/sizeof(dataBuffer[0]);int len2 = sizeof(zhenwei)/sizeof(zhenwei[0]);//下面自己看了,就是发送一串数据,然后以逗号隔开就完了for(int i = 0;i<len;i++){printf("%d,",touAndCmd[i]);}for(int k = 0;k<5;k++){for(int i = 0;i<len1;i++){printf("%d,",dataBuffer[i]);}}for(int m = 0;m<len2;m++){if(m == (len2-1)){printf("%d",zhenwei[m]);}else{printf("%d,",zhenwei[m]);}}

好了,最后一步:

A_ProcessMessage(cmd_buffer, size,ui);怎么处理我们发送上来的数据?

#include "qglobal.h"
#include "cmd_queue.h"
#include "process_fun.h"
#include "widget.h"
uint8_t  cmd_buffer[A_CMD_MAX_SIZE];
void A_ProcessMessage(/*A_PCTRL_MSG */uint8_t msg[2048], uint16_t size,Ui::Widget *dis)
{/*这是cmd_type的宏定义,其实用不上这么多
enum A_CtrlType
{A_kCtrlUnknown=0x00,A_kCtrlButton=0x01,                             //按钮A_kCtrlText = 0x02,                            //文本A_kCtrlGraph = 0x03,                           //曲线图控件A_kCtrlTable = 0x04,                           //表格控件A_kCtrlMenu = 0x05,                            //菜单控件A_kCtrlSelector = 0x06,                        //选择控件
};
*/uint8_t cmd_type = msg[1];            //命令类型uint8_t screen_id = msg[2];          //画面IDuint8_t control_id = msg[3];        //控件ID
//调试信息打印qDebug() << "cmd_type:"<<msg[1];qDebug() << "screen_id:"<<screen_id;qDebug() << "control_id:"<<control_id;switch(cmd_type){case A_kCtrlButton:                                                   //按钮控件A_NotifyButton(screen_id,control_id);break;case A_kCtrlText:                                                     //文本控件
//        A_NotifyText(screen_id,control_id,msg->param,dis);A_NotifyText(screen_id,control_id,&msg[4],dis);break;case A_kCtrlGraph:
//        A_NotifyGraph(screen_id,control_id,len,&msg->param[2]);                   //画图控件A_NotifyGraph(screen_id, control_id, size - 8, &msg[4]); // 注意减去帧头、帧尾和控件ID的长度default:break;}
}void A_NotifyText(uint16_t screen_id, uint16_t control_id, uint8_t *str,Ui::Widget *dis)
{if(screen_id == 0)//判断输入的第二个数是否为0,如果是,就可以为那些基波啥啥啥的赋值了{if(control_id == 3){Widget::updatedata3(str,1,dis);}else if(control_id == 4){Widget::updatedata3(str,2,dis);}else if(control_id == 5){Widget::updatedata3(str,3,dis);}else if(control_id == 6){Widget::updatedata3(str,4,dis);}else if(control_id == 7){Widget::updatedata3(str,5,dis);}else if(control_id == 8){Widget::updatedata3(str,6,dis);}}
}
void A_NotifyButton(uint16_t screen_id, uint16_t control_id)
{if(screen_id == 0){if(control_id == 10){}else if(control_id == 3){}}
}void A_NotifyGraph(uint16_t screen_id, uint16_t control_id, uint16_t length,uint8_t *str)
{int i = 0;float temp = 0;if(screen_id == 0){
//      if(control_id == 0)//这里因为我只用了一个画布,所以这里就不要啦
//      {
//          qDebug()<<length<<endl;
//          qDebug()<<str;
//          while(i<length)
//          {
//              temp = str[i++];
//              Widget::updatedata(temp);
//          }
//      }
//      elseif(control_id == 1){while(i<length){temp = str[i++];Widget::updatedata2(temp);//Widget中不断进行updatedata2这个函数}}}
}

好的,到这里所有代码基本分析完毕了,由上面我们知道updatedata2是画布的updatedata3是各个空白框的。下面最后来看一下这两个函数:

void Widget::updatedata2(float input)
{static int i2=0;//先整个作用在这个函数里边的静态变量static QVector<QPointF> data0=line2->pointsVector();//QVector<QPointF> 类型的容器,用于存储图表的数据点//控制数值在0-255之间if(input<0)input = 0;if(input>255)input = 255;if(i2<1024){data0.append(QPointF(i2,input));//在 data0 中添加一个新的数据点,x 坐标为 i2,y 坐标为 inputi2++;line2->replace(data0);//将更新后的数据点更新到 line2 图表中}else//如果数据量超过1024{QVector<QPointF> data2;//再来一个容器for(int j = 0;j<1023;j++){data2.append(QPointF(j,data0.at(j+1).y()));//将 data0 中的数据点从索引 1 开始拷贝到 data2,相当于删除data0中的数据}data2.append(QPointF(1023,input));//data2添加新的数据点,x 坐标为 1023,y 坐标为 inputdata0 = data2;//更新data0中的数据line2->replace(data0);///将更新后的数据点更新到 line2 图表中}}

updatedata3:

void Widget::updatedata3(uint8_t* input,int index,Ui::Widget *dis)
{QByteArray qstr;//定义一个数组int i = 0;for(i;i<sizeof(input);i++)//遍历input里面的数据{qstr.append(input[i]);//qstr存放input里面的数据}QString str = QString(qstr);//将 QByteArray 类型的 qstr 转换为 QString 类型的 strqDebug()<<str;switch(index)//index不同写入不同的空白框{case 1:{dis->lineEdit->setText(str);break;}case 2:{dis->lineEdit_2->setText(str);break;}case 3:{dis->lineEdit_3->setText(str);break;}case 4:{dis->lineEdit_4->setText(str);break;}case 5:{dis->lineEdit_5->setText(str);break;}case 6:{dis->lineEdit_6->setText(str+"%");break;}}
}

Windows程序和这个大同小异,就不具体介绍了,看懂上面这些Windows程序肯定莫得问题。

相关文章:

QT基于TCP协议实现数据传输以及波形绘制——安卓APP及Windows程序双版本

文章代码有非常非常之详细的解析&#xff01;&#xff01;&#xff01;诸位可放心食用 这个玩意我做了两个&#xff0c;一个是安卓app&#xff0c;一个是Windows程序。代码并非全部都是由我从无到有实现&#xff0c;只是实现了我想要的功能。多亏了巨人的肩膀&#xff0c;开源…...

mac 中 brctl 怎么用

mac 中 brctl 怎么用 mac 中 brctl 怎么用1.使用 Homebrew 安装 bridge2.安装完成后&#xff0c;你可以使用 bridge 命令来管理网络桥接。 mac 中 brctl 怎么用 在 macOS 中&#xff0c;没有官方提供的 brctl 命令行工具。但是&#xff0c;你可以使用一个名为 bridge 的开源工…...

20.2 HTML 常用标签

1. head头部标签 <head>标签用于定义网页的头部, 其中的内容是给浏览器读取和解析的, 并不在网页中直接显示给用户. <head>标签通常包含以下一些常见的子标签: - <title>: 定义网页的标题, 在浏览器的标题栏或标签页上显示. - <meta>: 用于设置网页的…...

mysql_2.5——【约束】详解

1、查看约束 SHOW CREATE TABLE table_name 2、主键约束(PRIMARY KEY) 主键约束最显著的特征是主键列中的值是不允许重复(唯一)的&#xff0c;通过主键约束可强制表 的实体完整性。当创建或更改表时可通过定义 primary key 约束来创建主键。一个表只 能有一个primary key约束…...

回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLA…...

opencv顺时针,逆时针旋转视频并保存视频

原视频 代码 import cv2# 打开视频文件 video cv2.VideoCapture(inference/video/lianzhang.mp4)# 获取原视频的宽度和高度 width int(video.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))# 创建视频编写器并设置输出视频参数 fourcc …...

【LeetCode】最小路径和

最小路径和 题目描述算法流程编程代码 链接: 最小路径和 题目描述 算法流程 编程代码 class Solution { public:int minPathSum(vector<vector<int>>& grid) {int m grid.size();int n grid[0].size();vector<vector<int>> dp(m1,vector<in…...

zygote forkSystemServer及systemServer启动

###zygote forkSystemServer方法 通过上一篇文章我们了解到zygote 在ZygoteInit.java类的main方法中调用forkSystemServer方法 UnsupportedAppUsagepublic static void main(String[] argv) {ZygoteServer zygoteServer null;....省略部分代码//根据环境变量(LocalServerSocke…...

享元模式-提供统一实现对象的复用

下围棋时&#xff0c;分为黑白棋子。棋子都一样&#xff0c;这是出现的位置不同而已。如果将每个棋子都作为一个独立的对象存储在内存中&#xff0c;将导致内存空间消耗较大。我们可以将其中不变的部分抽取出来&#xff0c;只存储它的位置信息来实现节约内存。 图 围棋 1 享元模…...

Jenkins工具系列 —— 在Ubuntu 18.04上安装各种版本OpenJDK

文章目录 安装java方式一、使用apt-get工具安装方式二、手动安装java 卸载java各种版本OpenJDK安装包下载 安装java 方式一、使用apt-get工具安装 1、安装各种JAVA版本 若要安装新版本的java11&#xff0c;安装命令&#xff1a; sudo apt install default-jre若选择安装jav…...

vue基础-虚拟dom

vue基础-虚拟dom 1、真实dom目标2、虚拟dom目标 1、真实dom目标 在真实的document对象上&#xff0c;渲染到浏览器上显示的标签。 2、虚拟dom目标 本质是保存节点信息、属性和内容的一个JS对象 更新会监听变化的部分 给真实的DOM打补丁...

C#时间轴曲线图形编辑器开发2-核心功能实现

目录 三、关键帧编辑 1、新建Winform工程 &#xff08;1&#xff09;界面布局 &#xff08;2&#xff09;全局变量 2、关键帧添加和删除 &#xff08;1&#xff09;鼠标在曲线上识别 &#xff08;2&#xff09;键盘按键按下捕捉 &#xff08;3&#xff09;关键帧添加、删…...

【Git】初始化仓库配置与本地仓库提交流程

目录 一、仓库配置邮箱与用户名 二、本地仓库提交流程 一、仓库配置邮箱与用户名 【Git】Linux服务器Centos环境下安装Git与创建本地仓库_centos git仓库搭建_1373i的博客-CSDN博客https://blog.csdn.net/qq_61903414/article/details/131260033?spm1001.2014.3001.5501 在…...

学习day53

今天主要是做一个案例 TodoList 组件化编码流程&#xff1a; 1. 拆分静态组件&#xff1a;组件要按照功能点拆分&#xff0c;命名不要与html元素冲突 2.实现动态组件&#xff1a;考虑好数据的存放位置&#xff0c;数据是一个组件在用&#xff0c;还是一些组件在用&#xff1a…...

【最短路算法】SPFA

引入 在计算机科学的世界里&#xff0c;算法就像是星空中的繁星&#xff0c;各自闪烁着智慧的光芒。它们沉默而坚定&#xff0c;像是一群不语的哲人&#xff0c;默默地解答着世界的问题。 算法的步骤&#xff0c;如同优美的诗行&#xff0c;让复杂的问题在流转的字符中得以释…...

牛客网Verilog刷题——VL48

牛客网Verilog刷题——VL48 题目答案 题目 在data_en为高期间&#xff0c;data_in将保持不变&#xff0c;data_en为高至少保持3个B时钟周期。表明&#xff0c;当data_en为高时&#xff0c;可将数据进行同步。本题中data_in端数据变化频率很低&#xff0c;相邻两个数据间的变化&…...

Unity UGUI的Shadow(阴影)组件的介绍及使用

Unity UGUI的Shadow(阴影)组件的介绍及使用 1. 什么是Shadow(阴影)组件&#xff1f; Shadow(阴影)组件是Unity UGUI中的一个特效组件&#xff0c;用于在UI元素上添加阴影效果。通过调整阴影的颜色、偏移、模糊等属性&#xff0c;可以使UI元素看起来更加立体和有层次感。 2. …...

Kubernetes系列

文章目录 1 详解docker,踏入容器大门1.1 引言1.2 初始docker1.3 docker安装1.4 docker 卸载1.5 docker 核心概念和底层原理1.5.1 核心概念1.5.2 docker底层原理 1.6 细说docker镜像1.6.1 镜像的常用命令 1.7 docker 容器1.8 docker 容器数据卷1.8.1 直接命令添加1.8.2 Dockerfi…...

同步锁: synchronized

synchronized 1. synchronized的特性2. synchronized的使用3. synchronized的锁机制 1. synchronized的特性 原子性: 所谓原子性就是指一个操作或者多个操作&#xff0c;要么全部执行并且执行的过程不会被任何因素打断&#xff0c;要么就都不执行。可见性: 可见性是指多个线程…...

【微服务】springboot 多模块打包使用详解

目录 一、前言 1.1 为什么需要掌握多模块打包 二、工程模块概述 2.1 前后端不分离...

嵌入式工程师面试经常遇到的30个经典问题

很多同学说很害怕面试&#xff0c;看见面试官会露怯&#xff0c;怕自己的知识体系不完整&#xff0c;怕面试官考的问题回答不上了&#xff0c;所以今天为大家准备了嵌入式工程师面试经常遇到的30个经典问题&#xff0c;希望可以帮助大家提前准备&#xff0c;不再惧怕面试。 1&a…...

ER系列路由器多网段划分设置指南

ER系列路由器多网段划分设置指南 - TP-LINK 服务支持 TP-LINK ER系列路由器支持划分多网段&#xff0c;可以针对不同的LAN接口划分网段&#xff0c;即每一个或多个LAN接口对应一个网段&#xff1b;也可以通过一个LAN接口与支持划分802.1Q VLAN的交换机进行对接&#xff0c;实现…...

3 PostGIS基础查询

PostGIS 基础查询 数据库维护 ps aux | grep postgrespsql 使用命令登录数据库psql -U postgres -d testdb -h localhost -p 5432postgres用户名&#xff0c;testdb数据库名称&#xff0c;localhost ip地址&#xff0c;可以省略&#xff0c;5432端口&#xff0c;可以省略。 …...

Shell错误:/bin/bash^M: bad interpreter: No such file or directory

目录 错误原因和现象 解决方案 错误原因和现象 在执行shell脚本的时候&#xff0c;报错&#xff1a;/bin/bash^M: bad interpreter: No such file or directory。 是由于该脚本文件是在Windows平台编写&#xff0c;然后在MacOS平台中执行。 在Windows平台上文件是dos格式&…...

Golang之路---01 Golang的安装与配置

Golang之路—01 Golang语言安装与配置 官网上下载Windows环境下的安装包 官网下载地址 双击下载后的文件进行安装&#xff0c;可根据需要自定义选择解压后的文件位置。 接着新创建一个文件夹&#xff0c;保存Golang语言项目。 在里面新建bin,pkg,src三个文件夹。 环境变量…...

Anolis OS 8.8服务器采用docker容器方式搭建gerrit3.8.1服务

采用docker容器方式搭建gerrit3.8.1服务 一、选择管理帐户密码的方式二、部署gerrit服务1. 采用docker compose部署单服务的方式部分gerrit(1) docker-compose.yaml文件内容(2) 在docker-compose.yaml文件所在目录调用下面命令先进行初始化操作 2. 在宿主机上部署httpd服务用于…...

PyTorch 中的多 GPU 训练和梯度累积作为替代方案

动动发财的小手&#xff0c;点个赞吧&#xff01; 在本文[1]中&#xff0c;我们将首先了解数据并行&#xff08;DP&#xff09;和分布式数据并行&#xff08;DDP&#xff09;算法之间的差异&#xff0c;然后我们将解释什么是梯度累积&#xff08;GA&#xff09;&#xff0c;最后…...

Appium+python自动化(三十五)- 命令启动appium之 appium服务命令行参数(超详解)

简介 前边介绍的都是通过按钮点击启动按钮来启动appium服务&#xff0c;有的小伙伴或者童鞋们乍一听可能不信&#xff0c;或者会问如何通过命令行启动appium服务呢&#xff1f;且听一一道来。 一睹为快 其实相当的简单&#xff0c;不看不知道&#xff0c;一看吓一跳&#xf…...

vmware的window中安装GNS3

1.向vmware中的windows虚拟机传送文件 点击虚拟机-安装VMwaretools 安装在虚拟机上面 此图标代表已经成功&#xff0c;将文件复制到虚拟机上里面 2.安装 安装gns3&#xff0c;需要先安装winpcap&#xff08;检查网卡&#xff09;和wireshark&#xff08;对winpcap上数据进行抓…...

FPGA XDMA 中断模式实现 PCIE3.0 AD7606采集 提供2套工程源码和QT上位机源码

目录 1、前言2、我已有的PCIE方案3、PCIE理论4、总体设计思路和方案AD7606数据采集和缓存XDMA简介XDMA中断模式QT上位机及其源码 5、vivado工程1--BRAM缓存6、vivado工程2--DDR4缓存7、上板调试验证8、福利&#xff1a;工程代码的获取 1、前言 PCIE&#xff08;PCI Express&am…...