【北京迅为】《STM32MP157开发板嵌入式开发指南》-第七十六章 C++入门

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等


第六篇 嵌入式GUI开发篇

在嵌入式上,我们少不了界面的开发,一种是用安卓,一种是用QT,那么安卓对CPU的性能要求比较高,不是所有的CPU都可以运行,但是QT对CPU要求不高,甚至可以在单片机上来运行,而且QT是一个非常优秀的跨平台工具,一套代码我们可以在多个平台上来运行,比如Windows,Android,Linux等,换一套编译器即可更换不同的平台。所以非常的方便和有趣。接下来我们就来学习上嵌入式上的QT开发,因为QT开发需要C++基础,不过大家不用担心,QT上用的C++并不多,手册上的C++知识大家掌握了即可开始QT的学习,大家不必担心。

第七十六章 C++入门

本章内容对应视频讲解链接(在线观看):

C++基础(上)  C++基础(上)_哔哩哔哩_bilibili

C++基础(下)  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=4

76.1 c++基础

c++是c语言的升级版,在c的基础上增加了很多功能。是一种高级语言,常见后缀:cpp,c++,cc等。

g++编译:gcc是一个通用命令,它会根据不同的参数调用不同的编译器或链接器,GCC 为了让操作简单推出g++命令用来编译 C++。

C++命名空间:中大型软件项目在多人合作开发时会出现变量或函数的命名冲突,C++ 引入了命名空间来解决变量重复定义。声明命名空间 std:using namespace std,后续如果有未指定命名空间的符号,那么默认使用 std,cin、cout ,endl都位于命名空间 std。

#include <iostream>
namespace A{int num = 1;
}
namespace B{int num = 2;
}
int main(int argc, const char *argv[])
{int m = A::num;std::cout << m << std::endl;m = B::num;std::cout << m << std::endl;return 0;
}

76.2 c++类和对象

1.什么是面向对象,什么又是面向过程

c语言就是面向过程的,c++就是面向对象的,面向对象特点:封装,继承,多态。

举例:a+b

直接计算a+b就是面向过程。

面向对象就是给a+b穿上了一层衣服。不是直接计算a+b。

2.c++的类

类大家可以把他看成c语言结构体的升级版。类的成员不仅可以是变量,也可以是函数,类可以看作是一种数据类型,这种数据类型是一个包含成员变量和成员函数的集合。

如:

class student
{
public:
//成员函数char name[64];char age;//成员函数void do(){Cout << “function” <<endl;
}
};

对象:类的实例化

直接定义:

 student my;  // student就是类  my就是对象

在堆里面定义。

 student *my = new student;

删除对象

 delete my;  目的是释放堆里面的内存

 my = NULL;   变成空指针,杜绝成为野指针。

访问类的成员

 	student my;student *my1 = new student;my.age = 18;my1->age =19;cout << my.age << endl;cout << my1->age << endl;

访问方法和C语言结构体是一样的,普通变量通过“.”  指针通过“->”。

4.类的函数成员

因为类里面的成员不仅可以是变量,也可以是函数。

第一步:在类里面声明

第二步:实现这个函数。我们可以直接在类里面写,也可以写在类的外面。

直接写在类的里面

class student
{
public:char name[64];int age;void test(){cout << 123 << endl;};
};

写到类的外面

class student
{
public:char name[64];int age;void test();
};void student::test(){  //student:表示属于这个类里面的函数,不加的话会被识别成普通函数。cout << 123 << endl;
};};

5.访问修饰符

类的访问修饰符就是对类的成员进行权限管理。

public: 表示函数和变量是公开的,任何人都可以访问。

private: 表示函数和变量只能在自己的类里面自己访问自己,不能通过对象来访问。

能不能强行访问?可以的。

protected:表示函数和变量只能在自己的类里面自己访问自己,但是可以被派生类来访问的。

76.3 函数的重载

1.类函数的重载特性

类函数的重载特性就是说我们可以在类里面定义同名的函数,但是参数不同的函数。

class student
{
public:char name[64];int age;void test();void test(int a);private:int  haha;};

重载函数在调用的时候,会根据参数的类型,然后去匹配相应的函数进行调用。

76.4 构造函数和析构函数

析构函数:假如我们定义了析构函数,当对象被删除或者生命周期结束的时候,就会触发析构函数。

构造函数:假如我们定义了构造函数,就会触发这个构造函数。

我们要怎么定义析构函数和构造函数?

1.析构函数和构造函数的名字必须和类名一模一样。

2.析构函数要在前面加上一个~

举例:

class student
{
public:student();~student();char name[64];int age;void test();void test(int a);private:int  haha;
};student::student(){cout << "hello" << endl;
}student::~student(){cout << "bye" << endl;
}

构造函数是可以被重载的。

析构函数不能被重载。

3.类的继承

什么是类的继承?

类的继承允许我们在新的类里面继承父类的public还有protected部分,private是不能被继承的。

当我们觉得这个类不好的时候,可以使用类的继承,添加我们需要的功能。·

格式:

class 儿子:public 爸爸{
public:
........
Protected:
}

例子:

class mystudent:public student
{
public:int grade;
};

如果在子类里面去访问父类的成员?

也是通过.和->来访问的。

76.5 虚函数和纯虚函数

虚函数:有实际定义的,允许派生类对他进行覆盖式的替换,virtual来修饰。

纯虚函数:没有实际定义的虚函数就是纯虚函数。

举例:

 virtual void test();  //虚函数

 virtual void testa(){} //纯虚函数

怎么定义一个虚函数?

用virtual来修饰,虚函数是用在类的继承上的。

虚函数的优点?

可以预留接口,实现分工合作。

76.1 安装Qtcreator

本章内容对应视频讲解链接(在线观看):

在Windows上搭建QT开发环境  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=2

Qtcreator下载地址:https://download.qt.io/archive/qt/5.12/5.12.10/进入选择版本号界面,本教程使用的是5.12.10,也推荐读者选择此版本。进入如界面后,选择安装包,我们在windows下学习Qt,所以选择qt-opensource-windows-x86-5.11.1.exe,点击即可下载。

下载后右键点击exe文件,选择以管理员身份运行。注册账号,点击下一步(或next),选择安装路径。选择下一步,勾选需要用到的组件,本阶段教程需要勾选以下七个选项: 

 

选择完后一直点下一步,安装完成后如下图: 

 

76.2 创建工程

创建一个QT工程步骤:

步骤一:

 

步骤二:

填写工程名字,不要有中文路径:

 

步骤三:填写类名: 

 

创建成功后如下图: 

 

工程目录下的.pro工程文件分析: 

 

点击forms,然后双击ui文件,就可以进入ui编辑器。 

 

ui编辑器面板介绍: 

 

76.3 信号和槽

本章内容对应视频讲解链接(在线观看):

QT信号和槽  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=6

信号就是指控件发出的特定的信号。槽就是槽函数的意思,信号和槽都位于类中,不是C++标准代码。我们可以把槽函数绑定在某一个控件的信号上。当需要调用外部函数时,发送一个信号,此时与该信号相关联的槽便会被调用,槽其实就是一个函数,槽与信号的关联要由程序员来完成,关联方法有自动关联和手动关联。

76.3.1 自动关联

使用Qt信号和槽的自动关联,可加快开发速度,一般用于同一个窗体之间的控件关联,槽函数格式非常关键,格式为:

void on_<窗口部件名称>_<信号名称>(<signal parameters>);

自动关联步骤:

步骤一:手动选择相应的控件,然后右键->转到槽。

 

选择信号类型: 

 

自动关联会在.h文件声明槽函数。槽函数只能声明到private slots或者public  slots 下面。按住Ctrl+鼠标左键,跳转到.cpp文件对应的函数功能实现部分。填写功能代码,我们在槽函数内用qDebug打印信息。 

 

添加完成qDebug内容之后,点击构建,运行: 

 

每次点击,按钮都会发信号,对应的槽函数就会执行,结果如下图: 

 

76.3.2 手动关联

信号和槽机制是QT的核心机制,要精通QT编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是QT的核心特性,也是QT区别于其它工具包的重要地方。此外如果遇到不懂的函数或类,可以先选中,然后按F1键,即可查看介绍。

虽然Qt有自动关联功能,但涉及到多个窗体和复杂事件的时候,只能使用手动关联,手动关联使用connect这个函数。

函数定义:

connect(const QObject *sender, const char *signal,

       const QObject *receiver, const char *member,

  Qt::ConnectionType = Qt::AutoConnection);

参数含义:

sender:发送对象;

singal:发送对象里面的一个信号,格式一般为SIGNAL(信号);

receiver:接收对象;

member:接收对象里面的槽函数,格式一般为SLOT(信号)。

ConnectionType:设置信号和槽的同步异步,一般使用默认值Qt::AutoConnection,可不填。

通常只传递前四个参数,如下所示:

connect(A,SIGNAL(B),C,SLOT(D));
当对象A发出B信号时候,就
会触发对象C的槽函数D

 signals 是 QT 的关键字,而非 C/C++ 的。signals 关键字指出进入了信号声明区,随后即可声明自己的信号。

slots   槽函数是普通的 C++ 成员函数,当与其关联的信号被发射时,这个槽函数就会被调用。槽函数有的参数个数和类型,在对应的信号函数中必须一一对应,即信号函数的参数个数必须多于或等于槽函数的个数。

emit  Qt定义的一个宏,作用是发射信号,与此信号关联的槽函数就会被调用。

例程:我们在widget.h中添加以下内容自定义一个信号和一个槽函数

signals:void my_Signal(void);    //自定义的信号private slots:void my_Solt(void);   //自定义的槽函数	

添加完成如下图所示:

并在widget.cpp实现槽函数: 

void Widget::my_Solt(void)
{qDebug("按下");
}

然后在widget.cpp中绑定信号和槽:

    connect(this,SIGNAL(my_Signal()),this,SLOT(my_Solt()));

 

在widget.ui中创建按钮,并转到槽,自动关联的槽函数如下图: 

发射信号 

这样,点击按钮,就会发射自定义的信号my_Signal(),与my_Signal()相关联的this对象槽函数my_Solt就会被调用,槽函数就会输出打印信息,如下图: 

部分核心代码如下:

Widget.h:

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();
signals:void my_Signal(void);    //自定义的信号private slots:void my_Solt(void);   //自定义的槽函数void on_pushButton_clicked();private:Ui::Widget *ui;
};
#endif // WIDGET_H

Widget.cpp:

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this,SIGNAL(my_Signal()),this,SLOT(my_Solt()));
}Widget::~Widget()
{delete ui;
}
void Widget::my_Solt(void)
{qDebug("按下");
}void Widget::on_pushButton_clicked()
{emit my_Signal();
}

76.4 给界面添加图片

本章内容对应视频讲解链接(在线观看):

仿写一个智能家居界面(上)  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=7

76.4.1 添加资源

选中项目名称,右键单击—>选择添加新文件

在弹出窗口中选择Qt—>Qt Resource File ,选择Choose 

 

填写写资源名称, 

 

例如填写 picture后,在工程下的Resources会出现picture.qrc文件,成功后如下图。

双击picture.qrc,点击“Add Frefix”,如下图所示:

 

76.4.2 添加图片

我们首先将要添加的图片复制到工程目录下。然后点击点击“Add files”,

 

选中图片,点击“打开”,进入资源编辑器,在资源编辑器中会看到添加的图片,然后保存。 

 

以此点开Resources下的各个文件夹,即可看到添加的图片,此时图片已经添加到工程。 

 

76.4.3 Label添加图片

在ui文件添加Label组件,添加完成如下图所示:

 

然后我们右击该组件->选择改变样式表,

 

弹出对话框,选择添加资源的小三角->border image, 

 

选择要添加的图片,如下图: 

 

点击OK,apply,OK,即可完成添加,如下图: 

76.5 界面布局

本章内容对应视频讲解链接(在线观看):

仿写一个智能家居界面(中)  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=8

 

76.5.1 水平布局

Horizontal Layout  水平方向布局,组件自动在水平方向上分布

使用时先选中组件,然后点击水平布局即可完成,可看到组件变为水平排列。如下图:

 

76.5.2 垂直布局

Vertical Layout 垂直方向布局,组件自动在垂直方向上分布,操作方法和水平布局一致,在布局之后组件垂直排列。

我们点击打破布局按钮,重新选择要布局的组件,然后点击垂直布局按钮,如下图:

 

76.5.3 栅格布局

Grid Layout  网格状布局,网状布局大小改变时,每个网格的大小都改变

 

我们发现布局之后各个组件都是紧挨着的,这时候可以用“弹簧”控件来控制组件位置。

Horizontal Spacer  一个用于水平分隔的空格

 

 

完成后如下图: 

 

Vertical Spacer  一个用于垂直分隔的空格,拖拽组件如下图: 

 

选中点击垂直布局,完成后如下图: 

 

76.6 界面切换

本章内容对应视频讲解链接(在线观看):

仿写一个智能家居界面(下)  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=9

本节通过实验介绍通过创建窗口对象的方式实现界面切换:

步骤一:

在主界面ui文件添加pushButton按钮,然后右击转到槽函数。

 

然后右击工程,点击新建一个窗口如下图所示: 

 

随后在,工程下创建新的Qt设计师界面类,如下图: 

我们选择Widget,用户可以根据需要选择,然后输入类名windowRun。 

 

创建完成后如下图: 

步骤二:在widget窗口的头文件widget.h添加windowrun头文件,并创建win窗口,添加内容如下:

#include "windowrun.h"

    windowRun *win;

添加完成如下图所示(注意,这两个添加的位置不在同一处):

 

步骤三:在widget.cpp的按钮槽函数on_pushButton_clicked()中添加以下内容:

    win = new windowRun();    //显示新窗口

    win->setGeometry(this->geometry());//设置win窗口尺寸与此窗口尺寸相同

    win->show();//显示

添加完成之后如下图所示:

 

运行程序后,点击按钮后即可跳转到第二个界面。 

 

76.7 Qt串口编程

本章内容对应视频讲解链接(在线观看):

QT上位机开发之串口助手(上)  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=10

QT上位机开发之串口助手(下)  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=11

本节我们使用Qt来编写一个简单的上位机。

实验介绍:组装ui界面,使用Qt提供的串口类来实现串口收发功能,需要掌握的相关 Qt知识有以下几点:

QSerialPort是 Qt5中的附加模块,提供了基本的功能,包括配置、I/O操作、获取和设置RS-232引脚的信号,要链接QtSerialPort模块,需要在.pro文件中添加+=serialport。

QSerialPort封装了丰富的成员变量来对应串口属性,还有很多操作串口的成员函数,常用的成员函数有setPort()或setPortName(),setBaudRate(),setDataBits(),setStopBits(),setParity()等,可以用这些函数设置要访问的串口设备。本实验使用了readyRead()信号,当有数据到来时会触发类对象的readyRead()信号,然后利用它的成员函数 readAll()读取。

类QSerialPortInfo可以获取可用的串口信息,如端口名称,系统位置,产品号,描述,制造商等信息。我们把它获取到的端口信息交给QSerialPort类对象。

 

76.7.1 界面布局

步骤一:将控件拖到ui界面上

接收框使用Plain Text Edit,发送框使用lineEdit,属性选择组件使用Combo Box。

 

步骤二:属性设置栏布局,以串口号为例,依次水平布局属性选择位。 

 

然后全部选中属性选择框,点垂直布局 

 

效果如下图:

 

步骤三:功能栏布局,在按钮间添加弹簧,点击水平布局。 

 

选中Lbel,发送框和功能按钮,点击垂直布局: 

 

如下图:

选中属性栏和右侧组件,然后点击水平布局,如下图: 

 

完成后:

再仿照上边的方法将下方的功能部分和接收框垂直布局: 

 

添加完组件后,更改接收框为只读:点击接收框,在QTextEdit里标记readOnly。

在右上角更改ui界面对象名,界面组装完成后可以根据需要自行修改,

双击属性选择框添加属性:

 

如下图: 

76.7.2 实现串口功能

1.编辑工程文件(后缀为 .pro的文件)在QT       += core gui后添加 serialport。

 

2.自动获取串口

使用QSerialPortInfo:::availablePorts()获取当前串口,该函数返回容器类Qlist<QSerialPortInfo>,用Qt定义的关键字foreach遍历容器Qlist里的串口信息,并将串口信息放到QStringList的类对象serialNamePort,显示到ui的串口组件。

{
{ui->setupUi(this);QStringList serialNamePort;//遍历:availablePorts()返回的串口信息foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){serialNamePort << info.portName();}ui->serialCb->addItems(serialNamePort);
}

编译后点击串口选择框,会出现已经连接的串口。

 

3.打开串口功能和属性设置

步骤一:实例化串口类QSerialPort对象serialPort,对串口的操作就是对serialPort对象的操作,调用QSerialPort封装的成员变量(属性)和成员函数(功能)就能控制串口。

class Example : public QMainWindow
{
public:
..........
QSerialPort * serialPort;
..........
};ui(new Ui::Example)
{ui->setupUi(this);
......serialPort = new QSerialPort;......
}

步骤二:填充波特率,数据位,停止位,校验位等属性。获取ui组件传递过来的串口信息,将串口属性填充到serialPort对象。

步骤三:打开串口,判断是否打开成功。 

/*打开按钮*/
void Example::on_openCb_clicked()
{QSerialPort::BaudRate bauRate;   //波特率QSerialPort::DataBits dataBits;  //数据位QSerialPort::StopBits stopBits;  //停止位QSerialPort::Parity   checkBits; //校验位//设置波特率if     (ui->baudCb->currentText() == "4800"  )    { bauRate = QSerialPort::Baud4800;  }else if(ui->baudCb->currentText() == "9600"  )    { bauRate = QSerialPort::Baud9600;  }else if(ui->baudCb->currentText() == "115200")    { bauRate = QSerialPort::Baud115200;}//设置数据位if    (ui->dataCb->currentText() == "5")   { dataBits = QSerialPort::Data5;}else if(ui->dataCb->currentText() == "6")   { dataBits = QSerialPort::Data6;}else if(ui->dataCb->currentText() == "7")   { dataBits = QSerialPort::Data7;}else if(ui->dataCb->currentText() == "8")   { dataBits = QSerialPort::Data8;}//设置停止位if     (ui->stopCb->currentText() == "1"  ) { stopBits = QSerialPort::OneStop;         }else if(ui->stopCb->currentText() == "1.5" ) { stopBits = QSerialPort::OneAndHalfStop;  }else if(ui->stopCb->currentText() == "2"   ) { stopBits = QSerialPort::TwoStop;         }//设置校验位if(ui->checkCb->currentText() == "none"  )  { checkBits = QSerialPort::NoParity; }//填充串口对象的属性值serialPort->setPortName(ui->serialCb->currentText());serialPort->setBaudRate(bauRate);serialPort->setDataBits(dataBits);serialPort->setStopBits(stopBits);serialPort->setParity(checkBits);//设置好属性后打开串口if(serialPort->open(QIODevice::ReadWrite) == true){QMessageBox::information(this,"提示","成功");}else{QMessageBox::critical(this,"提示","失败");}
}

4.收发串口数据功能

读数据:每当数据流从串口到达系统一次,就会传到Qt应用程序一次,readyRead信号就会触 发一次,所以可以用前面章节讲的信号和槽机制将readyRead信号和槽函数绑定,然后 就可以在槽函数中读取串口数据。槽函数中使用readAll()读取数据,使用带换行功能的appendPlainText()显示到ui的接收窗口。

//类中声明槽函数
private slots:void serialPortReadyRead_Solt(void);
//readyRead信号和槽函数绑定
connect(serialPort,SIGNAL(readyRead()),this,SLOT(serialPortReadyRead_Solt()));//读串口
void Example::serialPortReadyRead_Solt(void)
{QString buf;buf = QString(serilaPort->readAll());ui->recvEdit->appendPlainText(buf);
}

写数据:获取ui 界面填写的信息,ui->sendEdit->text(),使用QSerialPort的成员函数 write将数据写到串口。

void Widget::on_sendBt_clicked()
{serilaPort->write(ui->sendEdit->text().toLocal8Bit().data());
}

5.关闭串口功能

使用QSerialPort的成员函数close()关闭串口。

void Widget::on_closeBt_clicked()
{
serilaPort->close();
}

6.清空发送栏数据

调用ui组件lineEdit的成员函数clear即可清空数据

void Widget::on_clearBt_clicked()
{ui->recvEdit->clear();
}

编译测试,结果如

76.8 Qt程序打包和部署

本章内容对应视频讲解链接(在线观看):

把QT程序打包成Windows软件  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=12

因为我们要把写好的程序发给用户来用,写好的源码也不方便给别人看,所以要把程序进行打包部署。

步骤一:点击左下角的电脑图标将Debug模式切换到Release模式。

 

release模式:发布版本,不对源代码进行调试,基本没有调试信息。

debug模式:调试版本,有很多调试信息。

步骤二:找到release模式构建的文件夹。

 

步骤三:修改可执行程序图标。先把图标加到工程所在文件夹。然后在pro文件里面添加

 RC_ICONS=serial_iocn.ico

注意:图标的格式必须为.ico这个格式的,其他格式不行。

 

步骤四:封包操作。需要用到QT的控制台,点击电脑左下角,在搜索栏搜索qt,即可看到 qt控制台,双击即可打开,如下图  

 

 

我们需要电脑桌面上创建一个新的文件夹,注意千万不要有中文路径。然后把exe文件拷贝到我们新创建的文件夹里面,在控制台进入可执行文件所在的目录,如 

 

步骤五:使用windeployqt工具把库加到我们新创建的这个文件夹里面。

格式:windeployqt exe 文件的名称,如

 

打包成功后,进入执行文件路径,双击程序就可以打开,如 

 

76.9 Qt网络编程

本章内容对应视频讲解链接(在线观看):

QT网络编程之TCP通信  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=13

QT网络编程之UDP通信  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=14

网络编程有TCP和UDP,TCP编程需要用到两个类:QTcpServer和QTcpSocket。

区别

TCP 

UDP

是否连接

面向连接

面向非连接

传输可靠性 

可靠

不可靠

应用场合

少量数据,如传输文件

传输大量数据,如传输视频语音

76.9.1 TCP实现服务器和客户端

TCP协议(Transmission Control Protocol)是一种面向连接的,可靠的,基于字节流的传输层通信协议,传输数据稳定可靠。

在 help索引中搜索到如两个重要类:

 

服务器编程中两个类都会用到,客户端编程中只会用到QTcpSocket对象。

本实验中对QTcpServer类的基本使用:

(1)监听客户端连接。

(2)每当有新的客户端连接服务器的时候,会自动触发信号,

(3)根据当前连接上的新的客户端创建一个Socket对象,将数据的收发动作交给socket套 接字去处理。

(4)关闭服务器close();

对QTcpSocket类的基本使用:

(1)服务器端:有新连接时获取连接状态,绑定socket 。

(2)客户端:通过socket连接服务器,连接成功触发信号。

(3)当有数据到来时会触发信号,用readAll()读取。

(4)通过读写socket收发数据。

具体步骤:

步骤一:创建工程,在工程文件.pro中添加network,如

 

步骤二:设计ui界面,

  1. 在属性编辑栏设置主窗口大小:

 

  1. 添加组件

     接收窗口: Plain Text Edit

   发送窗口,IP地址窗口,端口号窗口:Line Edit

   打开服务器,关闭服务器:Push Button

拖拽完成后逐个布局,根据需要设置组件大小,这里端口号框设置成了最小200

 

按钮布局:拖拽按钮和弹簧,然后点击水平布局。 

 

然后选中全部组件,点击栅格布局: 

 

最后更改组件名称注释,完成后如 

 

步骤三:服务器端编程:

1.创建QTcpServer对象

2.创建监听端口,使得客户端可以使用这个端口访问服务器,使用listen函数。

bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);

第一个参数是绑定指定的地址(即本机服务器IP),第二个参数是绑定的本服务器端口号

监听某个端口时,如果有新连接进来就发出newConnection()信号

3.当服务器对象被访问时,会发出newConnection()信号,所以为该信号添加槽函数并用一个QTcpSocket对象接受客户端的访问。

4.当socket接收缓冲区有新数据到来时,会发出readyRead()信号,为该信号添加槽函数,使用readyRead()读取。

5.socket发送数据可直接调用write()成员函数。

6.关闭端口号。

代码如下:

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>namespace Ui {
class TcpServer;
}class TcpServer : public QMainWindow
{Q_OBJECTpublic:explicit TcpServer(QWidget *parent = 0);~TcpServer();QTcpServer * tcpServer;QTcpSocket * tcpSocket;
public slots:void newConnection_Slot(void);void readyRead_Solt(void);
private slots:void on_openBu_clicked();void on_sendBu_clicked();void on_closeBu_clicked();private:Ui::TcpServer *ui;
};
#include "tcpserver.h"
#include "ui_tcpserver.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <QString>
TcpServer::TcpServer(QWidget *parent) :QMainWindow(parent),ui(new Ui::TcpServer)
{ui->setupUi(this);tcpServer = new QTcpServer(this);tcpSocket = new QTcpSocket(this);//连接信号与槽函数进行绑定connect(tcpServer,SIGNAL(newConnection()),SLOT(newConnection_Slot()));
}//连接信号槽函数
void TcpServer::newConnection_Slot(void)
{//连接客户端后sockettcpSocket = tcpServer->nextPendingConnection();//套接字的接收数据信号与都数据槽函数连接connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Solt()));
}
//读取数据
void  TcpServer::readyRead_Solt(void)
{QString buf;//读取buf = tcpSocket->readAll();ui->recvEdit->appendPlainText(buf);
}TcpServer::~TcpServer()
{delete ui;
}//打开
void TcpServer::on_openBu_clicked()
{//监听tcpServer->listen(QHostAddress::Any,ui->portEdit->text().toUInt());
}//发送数据
void TcpServer::on_sendBu_clicked()
{tcpSocket->write(ui->sendEdit->text().toLocal8Bit().data());
}//关闭
void TcpServer::on_closeBu_clicked()
{tcpSocket->close();
}

步骤四:客户端编程

1.创建QTcpSocket套接字对象

2.使用套接字对象的成员函数去请求连接服务器。

void connectToHost(const QHostAddress &address, quint16 port, openMode mode = ReadWrite);

第一个参数为服务器IP地址,第二个参数为服务器端口号。第三个参数为打开方式,默认为可读可

函数功能:请求连接服务器连接成功后发出connected()信号,绑定槽函数connected_Solt()去操作socket

3.使用write函数向服务器发送数据,当socket接收缓冲区有新数据到来时

会发出readyRead()信号,为该信号添加槽函数以读取数据。

4.断开与服务器的连接。

class TcpClient : public QMainWindow
{
.......
private slots:void on_openBt_clicked();void connected_Solt(void);void readyRead_Solt(void);void on_sendEdit_2_clicked();void on_closeBt_clicked();
};TcpClient::TcpClient(QWidget *parent) :QMainWindow(parent),ui(new Ui::TcpClient)
{ui->setupUi(this);//创建socket 对象tcpSocket = new QTcpSocket(this);
}TcpClient::~TcpClient()
{delete ui;
}
//打开(连接服务器)
void TcpClient::on_openBt_clicked()
{tcpSocket->connectToHost(ui->ipEdit->text(),ui->portEdit->text().toUInt());connect(tcpSocket,SIGNAL(connected()),this,SLOT(connected_Solt()));
}
//等待数据到来
void TcpClient::connected_Solt(void)
{connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Solt()));
}
//读取数据
void TcpClient::readyRead_Solt(void)
{ui->recvEdit->appendPlainText(tcpSocket->readAll());
}
//发送
void TcpClient::on_sendEdit_2_clicked()
{tcpSocket->write(ui->sendEdit->text().toLocal8Bit().data());
}
//关闭
void TcpClient::on_closeBt_clicked()
{tcpSocket->close();
}

编译运行成功,使用服务器和客户端通信如: 

76.9.2 UDP实现服务器和客户端

UDP协议是开放式,无连接,不可靠的传输层通信协议,但它收发数据的速度相对于TCP快很多,常用在传输音视频等数据量非常大的场合。

udp网络编程只需要使用一个类QUdpSocket。

 

本实验中对QUdpSocket的基本使用:

1.创建QUdpSocket对象。

2.绑定端口号

3.数据到来触发readyRead()信号。

4.读取发送数据。

5.关闭。

具体步骤:

步骤一:组装ui界面,和TCP章节搭建UI界面方法一致。

 

步骤二:编写代码

1.创建QUdpSocket对象,使用bind函数绑定端口号和套接字,数据报到来后会发出信 号readyRead(),在绑定的槽函数内去读取数据。

2.读取数据,数据到来hasPendingDatagrams()返回true,再用pendingDatagramSize()获取数据报的长度,如果数据没有被读取

完,hasPendingDatagrams()就会返回true,直至数据都被读取完。

readDatagram(data,size);

参数data为读取的数据,size为数据长度。

3.发送数据,使用writeDatagram函数,

writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);

Data:发送的数据。

Len:发送的数据长度。

Host:目标IP地址。

Port:目标端口号。

4.关闭socket套接字。

代码如下:

udp.h

#include <QMainWindow>
#include <QUdpSocket>namespace Ui {
class Udp;
}class Udp : public QMainWindow
{Q_OBJECTpublic:explicit Udp(QWidget *parent = 0);~Udp();QUdpSocket * udpSocket;
private slots:void on_pushButton_clicked();void readyRead_Slot(void);void on_pushButton_3_clicked();void on_pushButton_2_clicked();private:Ui::Udp *ui;
};

udp.cpp:

Udp::Udp(QWidget *parent) :QMainWindow(parent),ui(new Ui::Udp)
{ui->setupUi(this);udpSocket = new QUdpSocket(this);
}Udp::~Udp()
{delete ui;
}
/** 打开按钮*/
void Udp::on_pushButton_clicked()
{//绑定本端口的端口号if(udpSocket->bind(ui->cliEdit->text().toUInt()) == true){QMessageBox::information(this,"提示","成功");}else{QMessageBox::information(this,"提示","失败");}//绑定数据信号和槽函数connect(udpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_Slot()));
}
/**读取数据槽函数*/
void Udp::readyRead_Slot()
{QString buf;QByteArray array;//hasPendingDatagrams()返回true时表示至少有一个数据报在等待被读取while(udpSocket->hasPendingDatagrams()){//获取数据array.resize(udpSocket->pendingDatagramSize());udpSocket->readDatagram(array.data(),array.size());buf = array.data();ui->recvEdit->appendPlainText(buf);}
}/** 发送数据*/
void Udp::on_pushButton_3_clicked()
{quint16 port;QString sendBuff;QHostAddress address;address.setAddress(ui->ipEdit->text());//目标机地址port = ui->portEdit->text().toInt();//目标机端口号sendBuff = ui->sendEdit->text();//发送的数据//发送udpSocket->writeDatagram(sendBuff.toLocal8Bit().data(),sendBuff.length(),address,port);
}/**关闭*/
void Udp::on_pushButton_2_clicked()
{udpSocket->close();
}

步骤三:运行测试,收发功能正常如

76.10 Qt定时器

本章内容对应视频讲解链接(在线观看):

QT时间编程之QT时钟  https://www.bilibili.com/video/BV1tp4y1i7EJ?p=15

实验目标:实现计时器功能,并且点击打点按钮将当前时间打印出来。

用到的类有QTimer和QTime,QTimer是一个计时器类,相当于秒表,QTimer是一个时间类,相当于手表。

76.10.1 实验步骤

步骤一:界面布局:

拖拽组件,在属性编辑栏设置大小,然后选中按钮,点击水平布局;

 

在属性编辑栏设置Label的最小高度为50,选中全部组件,点击栅格布局,如 

 

根据实际情况调整大小,更改对象名后如下图: 

 

 

步骤二:创建计时器类对象timer和时间类time,设置初始时间为0。 

    class TimerP : public QMainWindow{Q_OBJECTpublic:explicit TimerP(QWidget *parent = 0);~TimerP();QTimer timer;
QTime time;
..........};

步骤三:开启计时器对象,设置定时时间,时间到后会发出 timeout() 信号,绑定此信号和自定义的槽函数timeOut_Slot()。

void start(int msec);

函数功能:开启定时器,时间到后发出timeout信号,并重新计时。

参数msec含义:定时时间,单位毫秒。

    TimerP::TimerP(QWidget *parent) :QMainWindow(parent),ui(new Ui::TimerP){ui->setupUi(this);
//信号timeout与槽函数绑定connect(&timer,SIGNAL(timeout()),this,SLOT(timeOut_Slot()));time.setHMS(0,0,0,0);ui->showTime->setText("00:00:00:000");}
/**开始定时*/
void TimerP::on_starBu_clicked()
{timer.start(3);
}

步骤四:槽函数timeOut_Slot()内处理时间类对象,使每次计时时间结束后,时间对象能增加  相同的时间,实现计时功能。

QTime addMSecs(int ms) const;

参数msec含义:增加的时间值,单位毫秒。

函数功能:返回一个当前时间对象之后ms毫秒之后的时间对象。

	/** 计时*/void TimerP::timeOut_Slot(){//qDebug("timt out");time = time.addMSecs(3);ui->showTime->setText(time.toString("hh:mm:ss.zzz"));}

步骤五:打点记录功能,使用全局变量记录排名,并显示到界面。

/** 记录*/
void TimerP::on_bitBu_clicked()
{QString temp;i=i+1;temp.sprintf("%d: ",i);ui->bitTime->append(temp);ui->bitTime->append(time.toString("hh:mm:ss.zzz"));
}

76.10.2 部分代码

timerp.h:
class TimerP : public QMainWindow
{Q_OBJECTpublic:explicit TimerP(QWidget *parent = 0);~TimerP();QTimer timer;QTime time;private slots:void on_starBu_clicked();//开始计时按钮槽函数void timeOut_Slot();//定时时间到槽函数void on_closeBu_clicked();//关闭按钮槽函数void on_resetBu_clicked();//重置按钮槽函数void on_bitBu_clicked();//打点记录按钮槽函数private:Ui::TimerP *ui;
};
timerp.cpp:
#include <QTimer>
#include <QTime>static int i;
TimerP::TimerP(QWidget *parent) :QMainWindow(parent),ui(new Ui::TimerP)
{ui->setupUi(this);connect(&timer,SIGNAL(timeout()),this,SLOT(timeOut_Slot()));time.setHMS(0,0,0,0);ui->showTime->setText("00:00:00:000");
}TimerP::~TimerP()
{delete ui;
}void TimerP::on_starBu_clicked()
{timer.start(3);
}
/** 处理时间类对象*/
void TimerP::timeOut_Slot()
{//qDebug("timt out");time = time.addMSecs(3);ui->showTime->setText(time.toString("hh:mm:ss.zzz"));
}
/** 关闭*/
void TimerP::on_closeBu_clicked()
{timer.stop();i=0;
}
/** 重置*/
void TimerP::on_resetBu_clicked()
{timer.stop();time.setHMS(0,0,0,0);ui->showTime->setText("00:00:00:000");ui->bitTime->clear();i=0;
}/** 记录*/
void TimerP::on_bitBu_clicked()
{QString temp;i=i+1;temp.sprintf("%d: ",i);ui->bitTime->append(temp);ui->bitTime->append(time.toString("hh:mm:ss.zzz"));
}

运行结果:

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

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

相关文章

小菜家教平台:基于SpringBoot+Vue打造一站式学习管理系统

前言 现在已经学习了很多与Java相关的知识&#xff0c;但是迟迟没有进行一个完整的实践&#xff08;之前这个项目开发到一半&#xff0c;很多东西没学搁置了&#xff0c;同时原先的项目中也有很多的问题&#xff09;&#xff0c;所以现在准备从零开始做一个基于SpringBootVue的…

基于Matlab的语音识别

一、引言 语音识别技术是让计算机识别一些语音信号&#xff0c;并把语音信号转换成相应的文本或者命令的一种高科技技术。语音识别技术所涉及的领域非常广泛&#xff0c;包括信号处理、模式识别、人工智能等技术。近年来已经从实验室开始走向市场&#xff0c;渗透到家电、通信…

如何在 IntelliJ IDEA 中调整 `Ctrl+/` 快捷键生成注释的位置

前言 在使用 IntelliJ IDEA 编写代码时&#xff0c;注释是代码可读性和维护性的重要组成部分。IDEA 提供了快捷键 Ctrl/ 用于快速生成单行注释。然而&#xff0c;默认情况下&#xff0c;使用此快捷键生成的注释会出现在行首&#xff0c;导致注释与代码之间存在较大的空格&…

源鲁杯 2024 web(部分)

[Round 1] Disal F12查看: f1ag_is_here.php 又F12可以发现图片提到了robots 访问robots.txt 得到flag.php<?php show_source(__FILE__); include("flag_is_so_beautiful.php"); $a$_POST[a]; $keypreg_match(/[a-zA-Z]{6}/,$a); $b$_REQUEST[b];if($a>99999…

使用 ADB 在某个特定时间点点击 Android 设备上的某个按钮

前提条件 安装 ADB&#xff1a;确保你已经在计算机上安装了 Android SDK&#xff08;或单独的 ADB&#xff09;。并将其添加到系统环境变量中&#xff0c;以便你可以在命令行中运行 adb。 USB调试&#xff1a;确保 Android 设备已启用 USB 调试模式。这可以在设备的“设置” -…

数据库的使用02:SQLServer的连接字符串、备份、还原、SQL监视相关设置

目录 一、连接字符串 【本地连接字符串】 【远程连接字符串】 二、备份 三、还原 &#xff08;1&#xff09;还原数据库-bak、btn文件 &#xff08;2&#xff09;附加数据库mdf文件 四、SQL监视器的使用 一、连接字符串 【本地连接字符串】 server DESKTOP-FTH2P3S; Da…

Oracle视频基础1.3.6练习

1.3.6 以下是您的需求清单&#xff08;不含解决方案&#xff09;&#xff1a; 检查数据库启动情况等待会话结束&#xff0c;进行正常关机等待事务全部提交后再关机查看 alert 日志文件查看后台跟踪文件查看用户跟踪文件 检查数据库启动情况 ps -ef | grep oracle ipcs clear…

【大数据学习 | HBASE】hbase的原理与组成结构

1. hbase的简述 hbase作为google的大数据三篇比较重要的论文之一&#xff0c;它的起源叫做bigtable&#xff0c;意思非常简单就是大表的意思&#xff0c;是一个分布式存储很多数据的大型表格系统&#xff0c;它是对于hdfs中的数据不能直观查询和随机读写的病痛的一个补充和完善…

苍穹外卖Bug集合

初始化后端项目运行出现以下问题 以上报错是因为maven和jdk版本不符合&#xff0c;需要将jdk改成17&#xff0c;mavne改成3.9.9

【C++篇】在秩序与混沌的交响乐中: STL之map容器的哲学探寻

文章目录 C map 容器详解&#xff1a;高效存储与快速查找前言第一章&#xff1a;C map 的概念1.1 map 的定义1.2 map 的特点 第二章&#xff1a;map 的构造方法2.1 常见构造函数2.1.1 示例&#xff1a;不同构造方法 2.2 相关文档 第三章&#xff1a;map 的常用操作3.1 插入操作…

太空旅游:科技能否让星辰大海变为现实?

内容概要 在这个快速变化的时代&#xff0c;太空旅游成为了一个让人热血沸腾的话题。想象一下&#xff0c;坐在一颗漂浮的太空舱里&#xff0c;手中端着饮料&#xff0c;眺望着无尽的星辰大海&#xff0c;简直就像科幻电影中的情节一样。不过&#xff0c;这不仅仅是一个空洞的…

程序中怎样用最简单方法实现写excel文档

很多开发语言都能找到excel文档读写的库&#xff0c;但是在资源极其受限的环境下开发&#xff0c;引入这些库会带来兼容性问题。因为一个小功能引入一堆库&#xff0c;我始终觉得划不来。看到有项目引用的jar包有一百多个&#xff0c;看着头麻&#xff0c;根本搞不清谁依赖谁。…

【春秋云镜】CVE-2023-23752

目录 CVE-2023-23752漏洞细节漏洞利用示例修复建议 春秋云镜&#xff1a;解法一&#xff1a;解法二&#xff1a; CVE-2023-23752 是一个影响 Joomla CMS 的未授权路径遍历漏洞。该漏洞出现在 Joomla 4.0.0 至 4.2.7 版本中&#xff0c;允许未经认证的远程攻击者通过特定 API 端…

解决虚拟机启动报:此主机支持AMD-V,但AMD-V处于禁用状态

首先要知道你自己使用的主板型号&#xff0c;如果是京东购买的&#xff0c;可以直接上京东去问客服。如果没有订单号&#xff0c;如果能提供正确的主板型号&#xff0c;他们应该也是会帮忙解答的。 您好&#xff0c;AMD 平台与 Intel 平台以及部分新老主板开启虚拟化的步骤和细…

【EI会议推荐】抢先掌握学术前沿!快来参加EI学术会议投稿,展示你的研究成果,开启科研新高度!

【EI会议推荐】抢先掌握学术前沿&#xff01;快来参加EI学术会议投稿&#xff0c;展示你的研究成果&#xff0c;开启科研新高度&#xff01; 【EI会议推荐】抢先掌握学术前沿&#xff01;快来参加EI学术会议投稿&#xff0c;展示你的研究成果&#xff0c;开启科研新高度&#…

2.若依vue表格数据根据不同状态显示不同颜色style

例如国标显示蓝色&#xff0c;超标是红色 使用是蓝色&#xff0c;未使用是绿色 <el-table-column label"外卖配送是否完成评价" align"center" prop"isOverFlag"> <template slot-scope"scope"> …

Java基础-内部类与异常处理

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 一、Java 内部类 什么是内部类&#xff1f; 使用内部类的优点 访问局部变量的限制 内部类和继承 内部…

Java 基于SpringBoot+Vue 的公交智能化系统,附源码、文档

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Spring Boot开发入门教程

简介 Spring Boot是一个开源的Java基础框架&#xff0c;用于创建独立、生产级的基于Spring框架的应用程序。通过Spring Boot&#xff0c;你可以轻松地创建独立的、生产级的Spring应用程序。 环境准备 Java开发环境&#xff1a;确保你的机器上安装了Java 8或更高版本。Maven…