QT基于TCP协议实现数据传输以及波形绘制
这个玩意我做了两个,一个是安卓app,一个是Windows程序。代码并非全部都是由我从无到有实现,只是实现了我想要的功能。多亏了巨人的肩膀,开源万岁!!!
我把程序放到GitHub上,需要的可自取。
下面我分别对安卓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程序。代码并非全部都是由我从无到有实现,只是实现了我想要的功能。多亏了巨人的肩膀,开源万岁!!! 我把程序放到GitHub上,需要的可…...

苹果safari浏览器播放不了video标签视频
今天遇到了个神奇的问题,视频文件在pc端和安卓手机上播放都没问题,但是在ios上就是播放不了,大概代码如下: 前端代码: <video id"video" width"350" height"500" controls><s…...

【粒子群算法和蝴蝶算法组合】粒子群混沌混合蝴蝶优化算法研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Java设计模式之单例模式详解(懒汉式和饿汉式)
在开发工作中,有些类只需要存在一个实例,这时就可以使用单例模式。Java中的单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供全局访问点。下面来介绍一下两种常见的单例模式:懒汉式和饿汉式。 一、懒汉式…...

软件测试基本知识
安全测试 安全防护策略?(漏洞扫描、入侵检查、安全日志、隔离防护) 安全日志:用于记录非法用户的登录名称、操作时间及内容等信息,以便发现问题并提出解决措施;安全日志仅记录相关信息,不对非…...

Vue项目中强制刷新页面的方法
我们在动态切换组件的过程中,导航栏和底栏不动,动态切换中间区域的情况,在首页可以进行跳转任意组件,在组件与组件之间不能相互跳转,路由发生了变化,但是页面未改变,这时我们就需要强制刷新页面…...

文件按关键字分组-切割-染色-写入excel
1. 背景 针对下面的文件data.csv,首先根据fid进行排序,然后分组,使相同fid的记录放到同一个excel文件中,并对每列重复的数据元素染上红色。 fid,user_id -1000078398032092029,230410010036537520 -1000078398032092029,23042301…...

爬虫的基本原理:爬虫概述及爬取过程
前言 随着互联网的不断发展和普及,我们的生活越来越离不开网络。而网络世界中有着海量的信息和数据,这些信息和数据对于我们的工作和生活都有很大的帮助。但是,如何高效地获取这些数据呢?这时候,爬虫这个工具就派上用…...
cocos2D插件转3D插件
cocos2D插件转3D插件 use strict;/*** 3d插件api映射,兼容2d插件* */let fs require("fs");let path require("path");let baseDir ;const prsPath (Editor.Project && Editor.Project.path ? Editor.Project.path : Editor.remote.projectP…...

[Angular] 主从表结构,从表记录在主表固定栏位上呈现
Background 主从表结构,有时为了方便数据呈现,在UI上不显示从表资料,那么需要动态把从表的资料加载到主表的固定栏位上。 例如:主表是人员信息,从表是银行卡信息,一个人在同一家银行可能有多张银行卡&…...

Kotlin Multiplatform 创建多平台分发库
目标:通过本教程学习如何使用 Kotlin Multiplatform Library 创建多平台分发库(iOS,安卓)。 创建一个项目 1、本教程使用的是Android Studio创建 2、选择 新建工程,选择 Kotlin Multiplatform Library 3、点击next 输入需要创建的项目名称以…...
[SQL挖掘机] - union/union all 使用注意事项
因为当使用union和union all操作符时,有一些注意事项需要考虑: 1. 列数和数据类型匹配: 要使用union或union all合并结果集,两个或多个查询的 select 语句必须返回相同数量和类型的列。确保每个查询返回相同的列数,并…...
php 单例模式
1,单例模式,属于创建设计模式,简单来说就是一个类只能有一个实例化对象,并提供一个当前类的全局唯一可访问入口; 2,例子 <?phpclass Singleton {private static $instance null;// 禁止被实例化priva…...

【数据结构】实验二:顺序表
实验二 顺序表 一、实验目的与要求 1)熟悉顺序表的类型定义; 2)熟悉顺序表的基本操作; 3)灵活应用顺序表解决具体应用问题。 二、实验内容 1)在一个整数序列a1,a2,…,an中,若存在一个数&…...

【高级数据结构】线段树
目录 最大数(单点修改,区间查询) 线段树1(区间修改,区间查询) 最大数(单点修改,区间查询) 洛谷:最大数https://www.luogu.com.cn/problem/P1198 题目描述 …...

qt简易闹钟
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->stopBtn->setDisabled(true);this->setFixedSize(this->size()); //设置固定大小this->s…...

python和c加加有什么区别,c和c++和python先学哪个
本篇文章给大家谈谈c加加编程和python编程有什么区别,以及python和c加加有什么区别,希望对各位有所帮助,不要忘了收藏本站喔。 1、python和c学哪个好 学C好。 C通常比Python更快,因为C是一种编译型语言,而Python则是…...

Visual Studio 2022 cmake配置opencv开发环境
1. 环境与说明 这里我用的是 widnows 10 64位,Visual Studio 用的 Visual Studio Community 2022 (社区版) 对于Android开发工程师来说,为什么要使用Visual Studio 呢 ? 因为在Visual Studio中开发调试OpenCV方便,可以开发调试好后…...

C++ GDAL找出多时相遥感影像缺失的日期并自动生成新的全零图像作为替补
本文介绍基于C 语言的GDAL库,基于一个存储大量遥感影像的文件夹,依据每一景遥感影像的文件名中表示日期的那个字段,找出这些遥感影像中缺失的成像日期,并新生成多个像元值全部为0的栅格文件,作为这些缺失日期当日的遥感…...

【AI底层逻辑】——篇章5(下):机器学习算法之聚类降维时间序列
续上: 目录 4、聚类 5、降维 6、时间序列 三、无完美算法 往期精彩: 4、聚类 聚类即把相似的东西归在一起,与分类不同的是,聚类要处理的是没有标签的数据集,它根据样本数据的分布特性自动进行归类。 人在认知是…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...

ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...

uni-app学习笔记二十七--设置底部菜单TabBar的样式
官方文档地址:uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容,通常写在项目的App.vue的onLaunch方法中,用于项目启动时立即执行 重要参数: indexnumber是tabBar 的哪一项&…...
2025年全国I卷数学压轴题解答
第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos x − cos ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min t max x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…...

MySQL 数据库深度剖析:事务、SQL 优化、索引与 Buffer Pool
在当今数据驱动的时代,数据库作为数据存储与管理的核心,其性能与可靠性至关重要。MySQL 作为一款广泛使用的开源数据库,在众多应用场景中发挥着关键作用。在这篇博客中,我将围绕 MySQL 数据库的核心知识展开,涵盖事务及…...

React与原生事件:核心差异与性能对比解析
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
Prompt工程学习之思维树(TOT)
思维树 定义:思维树(Tree of Thoughts, ToT) 是一种先进的推理框架,它通过同时探索多条推理路径对思维链(Chain of Thought)** 进行了扩展。该技术将问题解决视为一个搜索过程 —— 模型生成不同的中间步骤…...