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、聚类 聚类即把相似的东西归在一起,与分类不同的是,聚类要处理的是没有标签的数据集,它根据样本数据的分布特性自动进行归类。 人在认知是…...

P1980 [NOIP2013 普及组] 计数问题
[NOIP2013 普及组] 计数问题 题目描述 试计算在区间 1 1 1 到 n n n 的所有整数中,数字 x x x( 0 ≤ x ≤ 9 0\le x\le9 0≤x≤9)共出现了多少次?例如,在 1 1 1 到 11 11 11 中,即在 1 , 2 , 3 , 4…...

需求管理全过程流程图及各阶段核心关注点详解
分析报告指出,多达76%的项目失败是因为差劲的需求管理,这个是项目失败的最主要原因,比落后的技术、进度失控或者混乱的变更管理还要关键。很多项目往往在开始的时候已经决定了失败,谜底就在谜面上,开始就注定的失败&am…...

Android开源 自定义emoji键盘,EmojiPack v2.1版本
目录 一,简介 二、安装 添加jitpack 仓库 添加依赖: 混淆规则: 三、使用 1、一次性配置emoji显示处理 二、emoji的自定义键盘的使用 一,简介 EmojiPack当前已提供emoji的显示和emoji的选择自定义键盘,在emoji显示这一方面࿰…...

SOLIDWORKS软件的优势分析 硕迪科技
在现代的机械设计领域,SOLIDWORKS是一款备受青睐三维设计软件,它具备强大的建模和设计功能,在全球范围内广泛应用于机械设计和工程领域,为用户提供了全面的工程解决方案。本文就SOLIDWORKS的优势进行详细分析。 1、易于学习和使用…...

Android性能优化之游戏的Theme背景图
近期,对游戏的内存优化,通过内存快照发现,某个Activity的theme背景图 占用3M 多。考虑着手对齐进行优化。 问题 查看游戏中的内存快照,发现有一个图片bitmap 占用3M 多,设置在Activity的背景中: 查看Phon…...

网络安全(黑客)系统自学,成为一名白帽黑客
前言 黑客技能是一项非常复杂和专业的技能,需要广泛的计算机知识和网络安全知识。你可以参考下面一些学习步骤,系统自学网络安全。 在学习之前,要给自己定一个目标或者思考一下要达到一个什么样的水平,是学完找工作(…...

lua学习-2 常见运算符
文章目录 赋值运算符普通赋值多重赋值交换赋值 算数运算符常见符号标识 关系运算符常见符号标识TIP 逻辑运算符常见符号标识模拟三目运算 赋值运算符 普通赋值 a 1b "123"c truec "true"多重赋值 a,b 1,2 a,b,c 2,"ss" -- c的值为nil交换赋…...

【图像处理】使用 OpenCV 将您的照片变成卡通
图像到卡通 一、说明 在当今世界,我们被图像和视频所包围。从社交媒体到广告,图像已成为一种强大的交流媒介。但是你有没有想过,如果你能把你的照片变成卡通会发生什么?想象一下,为您最喜欢的照片创建动画版本…...

暖手宝UL认证 亚马逊UL测试报告 UL499测试项目
UL499测试内容:1、 漏电流测试 2、 输入测试 3、 潮态下漏电流测试4、正常温升测试 5、 耐高压测试 6、 稳定性测试7、异常测试(DRY)8、 异常测试 9、 静压及强度测试10、 烧熔断器测试、 电源线拉力测试11、 电源线推力测试12、 塑件变…...

ES6模块化与异步编程高级用法
1. ES6模块化 1.1 回顾:node.js 中如何实现模块化 node.js 遵循了 CommonJS 的模块化规范。其中: 导入其它模块使用 require() 方法模块对外共享成员使用 module.exports 对象 模块化的好处: 大家都遵守同样的模块化规范写代码࿰…...