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

news2024/9/20 12:36:42

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

这个玩意我做了两个,一个是安卓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;//创建一个新的 Widget
        first->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地址为IP
     if (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);//精度设置为1
    axisX2->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);//设置外边界全部为0
    chart2->setMargins(QMargins(0, 0, 0, 0));//设置内边界全部为0
    chart2->setBackgroundRoundness(0);//设置背景区域无圆角



    line2->attachAxis(axisX2);//将数据线 line2 附加到 X 轴 axisX2
    line2->attachAxis(axisY2);//将数据线 line2 附加到 Y 轴 axisY2


    ui->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中的数传递给temp2
        temp1 = 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转换为QString
        QString dataStr = QString::fromUtf8(temp2);//把数组里面的数据转化为字符串

        // 使用split函数按逗号拆分字符串,得到QStringList
        QStringList 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中,否则布尔变量为false
            if (ok) {//如果布尔变量为ture
                dataArray.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; //每来一个数据队列头就会+1
    if(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(一条指令最大的大小),也就是2048
        buffer[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个字节,只计算数据部分CRC
            if(!CheckCRC16(buffer+1,cmd_size-5))                      //CRC校验
                return 0;

            cmd_size -= 2;                                            //去掉CRC16(2字节)
#endif
            qDebug() << "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];          //画面ID
      uint8_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);
//          }
//      }
//      else
          if(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 坐标为 input
        i2++;
        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 坐标为 input
        data0 = 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 类型的 str
    qDebug()<<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程序肯定莫得问题。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

农业中的计算机视觉 2023

物体检测应用于检测田间收割机和果园苹果 一、说明 欢迎来到Voxel51的计算机视觉行业聚焦博客系列的第一期。每个月&#xff0c;我们都将重点介绍不同行业&#xff08;从建筑到气候技术&#xff0c;从零售到机器人等&#xff09;如何使用计算机视觉、机器学习和人工智能来推动…

【导入外部jar包到maven项目中--亲测可行】

若项目为springweb项目&#xff0c;则先将jar放到WEB-INF/lib 目录下选中对应的jar包&#xff0c;右键选项 add-lirrary &#xff1b;成功加入之后的jar包是一个项目的目录结构&#xff1a; 至此&#xff0c;项目能够正常运行&#xff0c;在代码周也能够进行导包 转折点&…

Vue2 第二节 ----初识Vue(简单示例,模板语法,数据绑定)

知识点&#xff1a; 1.Vue的简单示例 2.模板语法 3.数据绑定 4.el和data的两种写法 5.MVVM模型 一. Vue的简单实例 <div id"root"><h1>hello, {{name.toUpperCase()}}, {{address}}</h1></div><script type"text/javascript&q…

2023年二季度中国手机销量排行榜:华为逆袭上榜,苹果仅位列第五,第一名很意外!

近日&#xff0c;国际数据公司&#xff08;IDC&#xff09;现发布最新手机季度跟踪报告显示&#xff0c;2023 年第二季度&#xff0c;中国智能手机市场出货量约 6,570 万台&#xff0c;同比下降 2.1%&#xff0c;降幅明显收窄。 今年上半年&#xff0c;中国智能手机市场出货量…

了解Unity编辑器之组件篇Physics 2D(十二)

一、Area Effector 2D区域施加力&#xff09;&#xff1a;用于控制区域施加力的行为 Use Collider Mask&#xff08;使用碰撞器遮罩&#xff09;&#xff1a;启用后&#xff0c;区域施加力仅会作用于特定的碰撞器。可以使用Collider Mask属性选择要作用的碰撞器。 Collider Ma…

揭秘低代码谜团,好用到不行

一、前言 低代码“灵活、快速、低门槛”的标签&#xff0c;为其带来了诸多争议。在低代码平台上是否只能搭建极其简单、无亮点的小功能&#xff1f;低代码带来的“全民程序员”化是否能真正带来社会价值&#xff1f;这是一场繁荣的泡沫假象&#xff0c;还是真实的市场需求&…

浅谈深拷贝与浅拷贝

一、拷贝&#xff08;克隆&#xff09;的意义的场景 意义&#xff1a;保证原数据的完整性和独立性 常见场景&#xff1a;复制数据、函数入参、class构造函数 二、浅拷贝 只克隆对象的第一层级如果属性值是原始数据类型&#xff0c;拷贝其值&#xff0c;即&#xff1a;值拷贝…

anaconda切换python版本

1 查看环境 conda env list结果如下图&#xff0c;左侧表示已下载的环境信息&#xff0c;当前我已经下载了python3.10&#xff08;python310&#xff09;和3.9&#xff08;python39&#xff09;两个版本 2 切换python版本 conda activate python3103 下载python # 下载pyt…

【玩转pandas系列】巧妙处理某瓣电影top250空数据

向阳花花花花 - 个人主页 迄今所有人生都大写着失败&#xff0c;但并不妨碍我继续向前 Python 数据分析专栏 正在火热更新中 &#x1f525; 文章目录 前言一、处理某瓣电影top250空数据二、对于空值&#xff0c;有没有别的处理办法&#xff1f;三、上述案例总结3.1 查看数据信…

个人博客系统 -- 博客列表页删除Markdown字符

之前的博客系统的列表页会有在markdown编辑器中的特殊字符,比如标题的字符#之类的,在列表页进行展示的时候,我们需要将这些字符进行筛选. 对这些字符进行筛选,我们可以通过排设计正则表达式进行筛选,也可以使用组件的方式进行筛选.下面我来总结一下,使用组件的方式进行筛选. 这…

Codeforces Round 886 (Div. 4)F题解

文章目录 [We Were Both Children](https://codeforces.com/contest/1850/problem/F)问题建模问题分析1.分析到达的点与跳跃距离的关系2.方法1倍数法累计每个点所能达到的青蛙数代码 方法2试除法累计每个点能到达的青蛙数代码 We Were Both Children 问题建模 给定n个青蛙每次…

Mac平台首选原生轻量级的嵌入式数据库引擎:Native SQLite Manager for Mac

亲爱的读者&#xff0c;如果你是一位在Mac平台上使用SQLite数据库的开发者或数据分析师&#xff0c;那么本文将为你介绍一款非常实用的工具——原生SQLite管理器。 SQLite是一种轻量级的嵌入式数据库引擎&#xff0c;被广泛应用于各种应用程序和系统中。它具有高效、可靠和易于…

ThirdAI 的私有和可个性化神经数据库:增强检索增强生成(第 3/3 部分)

这是我们关于使用检索增强生成构建 AI 代理的系列的最后一章 &#xff08;3/3&#xff09;。在第 1/3 部分中&#xff0c;我们讨论了断开连接的嵌入和基于矢量的检索管道的局限性。在第 2/3 部分中&#xff0c;我们介绍了神经数据库&#xff0c;它消除了存储和操作繁重且昂贵的…

GitHub上怎么寻找项目?

前言 下面由我精心整理的关于github项目资源搜索的一些方法&#xff0c;这些方法可以帮助你更快更精确的搜寻到你需要的符合你要求的项目。 写文章不易&#xff0c;如果这一篇问文章对你有帮助&#xff0c;求点赞求收藏~ 好&#xff0c;下面我们直接进入正题——> 首先我…

接口测试必备的,2种常⽤的JSON解析⽅法

JSON简介 一、JSON是什么&#xff1f; JSON: JavaScript Object Notation JS对象简谱&#xff0c;是一种轻量级的数据交换模式。 二、JSON语法&#xff1a; 对象中通过键值对 (key: value)的形式来表示对象的属性 注意&#xff1a;value即可以表示属性变量&#xff0c;又可…

【数据结构(C++版)】哈希表(散列表)

目录 1. 散列表的概念 2. 散列函数的构造方法 2.1 直接定址法 2.2 除留余数法 2.3 数字分析法 2.4 平方取中法 3. 处理冲突的方法 3.1 开放定址法 3.1.1 线性探测法 3.1.2 平方探测法 3.1.3 双散列法 3.1.4 伪随机序列法 3.2 拉链法&#xff08;链接法&#xff09…

数据结构---并查集

目录标题 为什么会有并查集并查集的原理模拟实现并查集准备工作构造函数FindRootUnionSetCount 并查集实战题目一&#xff1a;省份数量题目解析题目二&#xff1a;等式方程的可满足性题目解析 为什么会有并查集 这里可以使用生活中的一个例子来带着大家理解并查集&#xff0c;…

机器学习03-数据理解(小白快速理解分析Pima Indians数据集)

机器学习数据理解是指对数据集进行详细的分析和探索&#xff0c;以了解数据的结构、特征、分布和质量。数据理解是进行机器学习项目的重要第一步&#xff0c;它有助于我们对数据的基本属性有全面的了解&#xff0c;并为后续的数据预处理、特征工程和模型选择提供指导。 数据理解…

从Arweave开始:4EVERLAND存储签入挑战开始

嗨&#xff0c;4evers&#xff0c; 今天&#xff0c;我们热烈欢迎您参加 Galxe 上的 4EVERLAND “Arweave 入门”活动。这是一项长期的重头活动&#xff0c;所有参与的用户都有机会获得相应的奖励。 Arweave 是一种革命性的去中心化存储协议&#xff0c;为寻求安全可靠的有价…