Data Analysis and Visualization of POI Checking-In Data
- CS2309-2 问题求解与实践 FinalProject 心路历程
0: 开胃菜:QT 初体验 Design a GUI
这是问求英文班的最后一次小作业,要求如下:
We provide a .csv file about the statistics of checking-in data over time of a POI, which contains two columns named week and num. Design a GUI that can
provide a dialog that allows users to select the .csv file and load it from disk.
provide boxes that allow users to tune the starting and ending week for display.
draw a line chart using week as x axis and num as y axis, from the starting week to the ending week set by 2.
也就是说要实现三个功能,要能从磁盘中提取csv文件读数据,然后通过交互方框获取用户的输入值,最后通过输入值和数据作出x-y 折线图。
Version 1.0
在做小作业之前我苦苦思索,花了一天时间阅读 Qt学习之路,除了了解一些函数和类的基本功能外(这是当时边学边复制粘贴来的[笔记](https://yevzwming.github.io/2021/12/01/Qt GUI Programming Notes/),并没有学会怎么去运用这些去实际解决问题。上最后一次问求课的时候,同学提醒我面向CSDN编程,我恍然大悟,开始直接着手捣鼓这个小作业。
我觉得画图比较简单,于是先从第三部分做起,上CSDN搜索发现一个宝贝类:QLineSeries
。我选择了一些基本功能的代码加入我的程序:
(先修改pro文件并添加名称空间)
//设置line和point
QLineSeries*line=new QLineSeries();
line->setName(QString("POI"));
line->setVisible(true);
line->setPointLabelsVisible(false);
line->setPointsVisible(true);
//假设我们的数据存储在本地一个p数组内,这里添加进line
for(int i=front;i<=rear;i++)
{
line->append(i,p[i]);
}
//进行画图的一些设置
QChart*chart=new QChart();
chart->setAnimationOptions(QChart::AllAnimations);//动画效果
chart->setLocalizeNumbers(true);
chart->addSeries(line);
chart->createDefaultAxes();
chart->setTitle("POI checking-in data");
//画图
QChartView*chartView=new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
这样我们画图的设置便已经完成,在这里我是直接在main函数里写的,所以直接用mainwindow
来进行操作:
MainWindow w;
w.setCentralWidget(chartView);
w.resize(900,600);
w.show();
这样我们第一步大体完成,然后考虑p[]
中的数据何来,于是我搜了搜有关读取.csv文件。
这里用的最关键的是一个QFileDialog
类,来实现选择文件的对话框。原理就是我们用QString
来存储读取的文件的路径名,然后用QFile
打开,用QTextStream
来读取数据。.csv文件比较容易,一行一行读取即可,读取的数据以QString
存在,我们用一个QStringList
来写成类似坐标形式,然后用at
函数来获取右边一栏的数值,用toInt
强制转换成int
存储。
代码实现如下:
QString name=QFileDialog::getOpenFileName(NULL,"Open File","/home","Excel(*.csv)");
//if(name=="")return;
QFile file(name);
if(!file.open(QIODevice::ReadOnly))
qDebug()<<"OPEN FILE FAILED!";
QTextStream *out=new QTextStream(&file);
QStringList tempOption=out->readAll().split('\n');
for(int i=1;i<tempOption.count();i++)
{
n++;
QStringList tt=tempOption.at(i-1).split(",");
int tmp=tt.at(1).toInt();
p[i-1]=tmp;
}
这样以来第二步也完成了。最后我们要获取输入值,我搜了搜,发现一个很方便的类QInputDialog
,可以直接满足我们的需求,只需强制存储我们输入的值即可,代码如下:
bool isOK;
int front=QInputDialog::getText(NULL,"Input Dialog :","The starting week:",QLineEdit::Normal,"",&isOK).toInt();
int rear=QInputDialog::getText(NULL,"Input Dialog :","The starting week:",QLineEdit::Normal,"",&isOK).toInt();
至此我们已经完成全部功能,全部在main
函数里实现的,main.cpp 代码如下:
#include "mainwindow.h"
#include <QApplication>
#include<QtCharts/QChartView>
#include<QtCharts/QLineSeries>
#include<QDoubleSpinBox>
#include<QFileDialog>
#include<QFile>
#include<QDebug>
#include<QString>
#include<QInputDialog>
QT_CHARTS_USE_NAMESPACE
int p[100]={0};
int n=0;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
//Part 1
QString name=QFileDialog::getOpenFileName(NULL,"Open File","/home","Excel(*.csv)");
//if(name=="")return;
QFile file(name);
if(!file.open(QIODevice::ReadOnly))
qDebug()<<"OPEN FILE FAILED!";
QTextStream *out=new QTextStream(&file);
QStringList tempOption=out->readAll().split('\n');
for(int i=1;i<tempOption.count();i++)
{
n++;
QStringList tt=tempOption.at(i-1).split(",");
int tmp=tt.at(1).toInt();
p[i-1]=tmp;
}
//Part 2
bool isOK;
int front=QInputDialog::getText(NULL,"Input Dialog :","The starting week:",QLineEdit::Normal,"",&isOK).toInt();
int rear=QInputDialog::getText(NULL,"Input Dialog :","The starting week:",QLineEdit::Normal,"",&isOK).toInt();
//Part 3
QLineSeries*line=new QLineSeries();
line->setName(QString("POI"));
line->setVisible(true);
line->setPointLabelsVisible(false);
line->setPointsVisible(true);
for(int i=front;i<=rear;i++)
{
line->append(i,p[i]);
}
QChart*chart=new QChart();
chart->setAnimationOptions(QChart::AllAnimations);
chart->setLocalizeNumbers(true);
chart->addSeries(line);
chart->createDefaultAxes();
chart->setTitle("POI checking-in data");
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignBottom);//底部对齐
chart->legend()->setBackgroundVisible(true);//设置背景是否可视
chart->legend()->setLabelColor(QColor(255,128,255));//设置标签颜色
chart->legend()->setVisible(true);//设置是否可视
QFont font = chart->legend()->font();
QChartView*chartView=new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
w.setCentralWidget(chartView);
w.resize(900,600);
w.show();
return a.exec();
}
Version 2.0
但是,我转念一想,第二个要求上课的时候小姐姐讲了很长时间如何自己设计构件,我觉得这样直接用QInputDialog
太不负责任了,于是我打算推倒重建,结合上课所讲的知识来完成任务。
首先我打算把代码全部从main
里面撤销,也不用mainwindow
,而是自己定义一个widget
来实现功能,我一开始其实只是想用widget来实现自定义输入的box,后来发现这样的话在不同文件间传输数据很麻烦,且不能满足各个功能的时序性。
我在将box的样式构建好之后,应该如何触发画图呢?思来想去发现应该在main
函数里调用QObject::connect
函数,但是直接调用会报错,为什么呢? 发现这里的button
是在widget
函数里调用的,而不是widget
类,所以我们要把里面的一些小构建写在widget.h
头文件的类里。
QLabel*label=new QLabel("Please input the beginning week:");
QLabel*labelbel=new QLabel("Please input the ending week:");
spinbox=new QSpinBox(this);
spinboxbox=new QSpinBox(this);
QSlider*slider=new QSlider(Qt::Horizontal,this);
QSlider*sliderder=new QSlider(Qt::Horizontal,this);
spinbox->setRange(1,52);
spinboxbox->setRange(1,52);
slider->setRange(1,52);
sliderder->setRange(1,52);
QObject::connect(slider,&QSlider::valueChanged,spinbox,&QSpinBox::setValue);
void (QSpinBox:: *spinBoxSignal)(int)=&QSpinBox::valueChanged;
QObject::connect(spinbox,spinBoxSignal,slider,&QSlider::setValue);
QObject::connect(sliderder,&QSlider::valueChanged,spinboxbox,&QSpinBox::setValue);
QObject::connect(spinboxbox,spinBoxSignal,sliderder,&QSlider::setValue);
QVBoxLayout*layout=new QVBoxLayout;
btn=new QPushButton("I have entered the value!");
layout->addWidget(label);
layout->addWidget(spinbox);
layout->addWidget(slider);
layout->addWidget(labelbel);
layout->addWidget(spinboxbox);
layout->addWidget(sliderder);
layout->addWidget(btn);
this->setLayout(layout);
this->show();
而在main
函数里,我们如此调用:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
widget w;
QObject::connect(w.btn,&QPushButton::clicked,&w,&widget::showline);
QObject::connect(w.btn,&QPushButton::clicked,&w,&widget::close);
return a.exec();
}
也就是在外层操控,一旦按钮被点开,直接用main
函数来关闭窗口并运行画图函数widget::showline()
。我们也只能无奈地把showline
写在widget
里了。
其他的部分改动不大,我们把数组也写进了类的头文件里,把读取文件的代码块直接移植到``widget`里,于是我们改进版大功告成。(改bug改了五个小时一直到ddl前半小时才提交……)
最后放一下最终的widget.cpp
代码:
#include "mainwindow.h"
#include "widget.h"
#include <QApplication>
#include<QStringList>
#include<QTextStream>
#include<QtDebug>
#include<QChart>
#include<QSlider>
#include<QSpinBox>
#include<QHBoxLayout>
#include<QWidget>
#include<QLabel>
#include<QWidget>
#include<QLineSeries>
#include<QChartView>
#include<QPushButton>
QT_CHARTS_USE_NAMESPACE
widget::widget(QWidget *parent) : QWidget(parent)
{
//load a csv file from the disk
QString name=QFileDialog::getOpenFileName(NULL,"Open File","/home","Excel(*.csv)");
//if(name=="")return;
QFile file(name);
if(!file.open(QIODevice::ReadOnly))
qDebug()<<"OPEN FILE FAILED!";
QTextStream *out=new QTextStream(&file);
QStringList tempOption=out->readAll().split('\n');
for(int i=1;i<tempOption.count();i++)
{
n++;
QStringList tt=tempOption.at(i-1).split(",");
int tmp=tt.at(1).toInt();
p[i-1]=tmp;
}
//design the box to get the beginning and ending week
QLabel*label=new QLabel("Please input the beginning week:");
QLabel*labelbel=new QLabel("Please input the ending week:");
spinbox=new QSpinBox(this);
spinboxbox=new QSpinBox(this);
QSlider*slider=new QSlider(Qt::Horizontal,this);
QSlider*sliderder=new QSlider(Qt::Horizontal,this);
spinbox->setRange(1,52);
spinboxbox->setRange(1,52);
slider->setRange(1,52);
sliderder->setRange(1,52);
QObject::connect(slider,&QSlider::valueChanged,spinbox,&QSpinBox::setValue);
void (QSpinBox:: *spinBoxSignal)(int)=&QSpinBox::valueChanged;
QObject::connect(spinbox,spinBoxSignal,slider,&QSlider::setValue);
QObject::connect(sliderder,&QSlider::valueChanged,spinboxbox,&QSpinBox::setValue);
QObject::connect(spinboxbox,spinBoxSignal,sliderder,&QSlider::setValue);
QVBoxLayout*layout=new QVBoxLayout;
btn=new QPushButton("I have entered the value!");
layout->addWidget(label);
layout->addWidget(spinbox);
layout->addWidget(slider);
layout->addWidget(labelbel);
layout->addWidget(spinboxbox);
layout->addWidget(sliderder);
layout->addWidget(btn);
this->setLayout(layout);
this->show();
}
void widget::showline()
{
//draw the chart based on the data
QLineSeries*line=new QLineSeries();
line->setName(QString("POI"));
line->setVisible(true);
line->setPointLabelsVisible(false);
line->setPointsVisible(true);
for(int i=spinbox->value();i<=spinboxbox->value();i++)
{
line->append(i,p[i]);
}
QChart*chart=new QChart();
chart->setAnimationOptions(QChart::AllAnimations);
chart->setLocalizeNumbers(true);
chart->addSeries(line);
chart->createDefaultAxes();
chart->setTitle("POI checking-in data");
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignBottom);
chart->legend()->setBackgroundVisible(true);
chart->legend()->setLabelColor(QColor(255,128,255));
chart->legend()->setVisible(true);
QFont font = chart->legend()->font();
QChartView*chartView=new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
chartView->resize(900,600);
chartView->show();
}
“小作业”艰难完成。
1:Qt 多线程的学习
由于大作业需要处理的数据量十分庞大,所以为了减少界面的卡顿,换而言之防止程序直接卡死,我们需要采用多线程的方式去优化我们的project.
本节参照bilibili上的一个教程而画大饼大致学习完成。
线程类 QThread
常用的共用成员函数:
// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;
// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
QThread::IdlePriority --> 最低的优先级
QThread::LowestPriority
QThread::LowPriority
QThread::NormalPriority
QThread::HighPriority
QThread::HighestPriority
QThread::TimeCriticalPriority --> 最高的优先级
QThread::InheritPriority --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);
信号槽:
// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();
// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();
静态函数:
// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs); // 单位: 毫秒
[static] void QThread::sleep(unsigned long secs); // 单位: 秒
[static] void QThread::usleep(unsigned long usecs); // 单位: 微秒
任务处理函数:
// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();
使用方式一:到线程搞
我们需要创建一个线程类的子类,继承QT中的线程类QThread
。
我们需要这个线程处理的任务,写在父类的run()
函数里面。重写父类的run()
,编写子线程要处理的具体的业务流程。
在主线程里我们直接创建子线程的对象,用指针new一个出来,然后调用start()
就可以直接启动了。(start
相当于是间接启动run()
)
父子线程之间的通信可以通过信号槽的方式。
看起来有手就行,写起来手呢?看一个示例:
首先我们需要捋顺mainwindow.cpp
的逻辑:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1. 创建子线程对象
Generate* gen = new Generate;
BubbleSort* bubble = new BubbleSort;
QuickSort* quick = new QuickSort;
connect(this, &MainWindow::starting, gen, &Generate::recvNum);
// 2. 启动子线程
connect(ui->start, &QPushButton::clicked, this, [=]()
{
emit starting(10000);
gen->start();
});
connect(gen, &Generate::sendArray, bubble, &BubbleSort::recvArray);
connect(gen, &Generate::sendArray, quick, &QuickSort::recvArray);
// 接收子线程发送的数据
connect(gen, &Generate::sendArray, this, [=](QVector<int> list){
bubble->start();
quick->start();
for(int i=0; i<list.size(); ++i)
{
ui->randList->addItem(QString::number(list.at(i)));
}
});
connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){
for(int i=0; i<list.size(); ++i)
{
ui->bubbleList->addItem(QString::number(list.at(i)));
}
});
connect(quick, &QuickSort::finish, this, [=](QVector<int> list){
for(int i=0; i<list.size(); ++i)
{
ui->quickList->addItem(QString::number(list.at(i)));
}
});
connect(this, &MainWindow::destroy, this, [=]()
{
gen->quit();
gen->wait();
gen->deleteLater(); // delete t1;
bubble->quit();
bubble->wait();
bubble->deleteLater();
quick->quit();
quick->wait();
quick->deleteLater();
});
}
MainWindow::~MainWindow()
{
delete ui;
}
而分别的线程我们是如何实现的呢?看线程类的cpp函数,注意观察不同的类传递不同的信号:
#include "mythread.h"
#include <QElapsedTimer>
#include <QDebug>
Generate::Generate(QObject *parent) : QThread(parent)
{
}
void Generate::recvNum(int num)
{
m_num = num;
}
void Generate::run()
{
qDebug() << "生成随机数的线程的线程地址: " << QThread::currentThread();
QVector<int> list;
QElapsedTimer time;
time.start();
for(int i=0; i<m_num; ++i)
{
list.push_back(qrand() % 100000);
}
int milsec = time.elapsed();
qDebug() << "生成" << m_num << "个随机数总共用时:" << milsec << "毫秒";
emit sendArray(list);
}
BubbleSort::BubbleSort(QObject *parent) : QThread(parent)
{
}
void BubbleSort::recvArray(QVector<int> list)
{
m_list = list;
}
void BubbleSort::run()
{
qDebug() << "冒泡排序的线程的线程地址: " << QThread::currentThread();
QElapsedTimer time;
time.start();
int temp;
for(int i=0; i<m_list.size(); ++i)
{
for(int j=0; j<m_list.size()-i-1; ++j)
{
if(m_list[j] > m_list[j+1])
{
temp = m_list[j];
m_list[j] = m_list[j+1];
m_list[j+1] = temp;
}
}
}
int milsec = time.elapsed();
qDebug() << "冒泡排序用时" << milsec << "毫秒";
emit finish(m_list);
}
QuickSort::QuickSort(QObject *parent) : QThread(parent)
{
}
void QuickSort::recvArray(QVector<int> list)
{
m_list = list;
}
void QuickSort::run()
{
qDebug() << "快速排序的线程的线程地址: " << QThread::currentThread();
QElapsedTimer time;
time.start();
quickSort(m_list, 0, m_list.size()-1);
int milsec = time.elapsed();
qDebug() << "快速排序用时" << milsec << "毫秒";
emit finish(m_list);
}
void QuickSort::quickSort(QVector<int> &s, int l, int r)
{
if (l < r)
{
int i = l, j = r;
// 拿出第一个元素, 保存到x中,第一个位置成为一个坑
int x = s[l];
while (i < j)
{
// 从右向左找小于x的数
while (i < j && s[j] >= x)
{
//左移, 直到遇到小于等于x的数
j--;
}
if (i < j)
{
//将右侧找到的小于x的元素放入左侧坑中, 右侧出现一个坑
//左侧元素索引后移
s[i++] = s[j];
}
// 从左向右找大于等于x的数
while (i < j && s[i] < x)
{
//右移, 直到遇到大于x的数
i++;
}
if (i < j)
{
//将左侧找到的元素放入右侧坑中, 左侧出现一个坑
//右侧元素索引向前移动
s[j--] = s[i];
}
}
//此时 i=j,将保存在x中的数填入坑中
s[i] = x;
quickSort(s, l, i - 1); // 递归调用
quickSort(s, i + 1, r);
}
}
以上是多线程的第一种实现方式,直接用QThread
写类,简单粗暴。
使用方式二:搞到线程
Qt 提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些,我们看一看具体操作:
首先我们创建一个继承QObject
的类,这个类后面我们用来执行第一种方式QThread
类中run()
函数所做的事情。
在类里添加一个公共的成员函数,函数体就是我们要子线程中致性的业务逻辑。
class MyWork:public QObject
{
public:
.......
// 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
void working();
}
在主线程中创建一个QThread
对象,此即子线程的对象。在主线程中创建工作的类对象(注:不要给创建的对象指定父对象)。
将工作类对象移动到创建的子线程对象中,需要调用QObject
类的moveToThread()
方法。
启动子线程,调用工作函数,这时候线程便启动了,但是移动到线程中的工作对象并没有工作,这时我们调用工作对象的工作函数,让这个函数开始执行,这时候便在移动到的那个子线程中运行。
与方式一的代码功能相同,我们用方式二也实现一个:
首先理解这个代码,先看mainwindow.cpp
文件的代码逻辑:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
#include <QThread>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1. 创建子线程对象
QThread* t1 = new QThread;
QThread* t2 = new QThread;
QThread* t3 = new QThread;
// 2. 创建任务类的对象
Generate* gen = new Generate;
BubbleSort* bubble = new BubbleSort;
QuickSort* quick = new QuickSort;
// 3. 将任务对象移动到某个子线程中
gen->moveToThread(t1);
bubble->moveToThread(t2);
quick->moveToThread(t3);
connect(this, &MainWindow::starting, gen, &Generate::working);
// 2. 启动子线程
connect(ui->start, &QPushButton::clicked, this, [=]()
{
emit starting(10000);
t1->start();
});
connect(gen, &Generate::sendArray, bubble, &BubbleSort::working);
connect(gen, &Generate::sendArray, quick, &QuickSort::working);
// 接收子线程发送的数据
connect(gen, &Generate::sendArray, this, [=](QVector<int> list){
t2->start();
t3->start();
for(int i=0; i<list.size(); ++i)
{
ui->randList->addItem(QString::number(list.at(i)));
}
});
connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){
for(int i=0; i<list.size(); ++i)
{
ui->bubbleList->addItem(QString::number(list.at(i)));
}
});
connect(quick, &QuickSort::finish, this, [=](QVector<int> list){
for(int i=0; i<list.size(); ++i)
{
ui->quickList->addItem(QString::number(list.at(i)));
}
});
connect(this, &MainWindow::destroy, this, [=]()
{
t1->quit();
t1->wait();
t1->deleteLater(); // delete t1;
t2->quit();
t2->wait();
t2->deleteLater();
t3->quit();
t3->wait();
t3->deleteLater();
gen->deleteLater();
bubble->deleteLater();
quick->deleteLater();
});
}
MainWindow::~MainWindow()
{
delete ui;
}
可以看到我们先开线程后各自工作,然后信号传输还是如方式一,不过细化到具体的工作类更加直观。
再来看一下工作类是如何实现的:
#include "mythread.h"
#include <QElapsedTimer>
#include <QDebug>
#include <QThread>
Generate::Generate(QObject *parent) : QObject(parent)
{
}
void Generate::working(int num)
{
qDebug() << "生成随机数的线程的线程地址: " << QThread::currentThread();
QVector<int> list;
QElapsedTimer time;
time.start();
for(int i=0; i<num; ++i)
{
list.push_back(qrand() % 100000);
}
int milsec = time.elapsed();
qDebug() << "生成" << num << "个随机数总共用时:" << milsec << "毫秒";
emit sendArray(list);
}
BubbleSort::BubbleSort(QObject *parent) : QObject(parent)
{
}
void BubbleSort::working(QVector<int> list)
{
qDebug() << "冒泡排序的线程的线程地址: " << QThread::currentThread();
QElapsedTimer time;
time.start();
int temp;
for(int i=0; i<list.size(); ++i)
{
for(int j=0; j<list.size()-i-1; ++j)
{
if(list[j] > list[j+1])
{
temp = list[j];
list[j] = list[j+1];
list[j+1] = temp;
}
}
}
int milsec = time.elapsed();
qDebug() << "冒泡排序用时" << milsec << "毫秒";
emit finish(list);
}
QuickSort::QuickSort(QObject *parent) : QObject(parent)
{
}
void QuickSort::working(QVector<int> list)
{
qDebug() << "快速排序的线程的线程地址: " << QThread::currentThread();
QElapsedTimer time;
time.start();
quickSort(list, 0, list.size()-1);
int milsec = time.elapsed();
qDebug() << "快速排序用时" << milsec << "毫秒";
emit finish(list);
}
void QuickSort::quickSort(QVector<int> &s, int l, int r)
{
if (l < r)
{
int i = l, j = r;
// 拿出第一个元素, 保存到x中,第一个位置成为一个坑
int x = s[l];
while (i < j)
{
// 从右向左找小于x的数
while (i < j && s[j] >= x)
{
//左移, 直到遇到小于等于x的数
j--;
}
if (i < j)
{
//将右侧找到的小于x的元素放入左侧坑中, 右侧出现一个坑
//左侧元素索引后移
s[i++] = s[j];
}
// 从左向右找大于等于x的数
while (i < j && s[i] < x)
{
//右移, 直到遇到大于x的数
i++;
}
if (i < j)
{
//将左侧找到的元素放入右侧坑中, 左侧出现一个坑
//右侧元素索引向前移动
s[j--] = s[i];
}
}
//此时 i=j,将保存在x中的数填入坑中
s[i] = x;
quickSort(s, l, i - 1); // 递归调用
quickSort(s, i + 1, r);
}
}
内容与方式一类似,注意继承的父类是不同的,这只是单纯工作的类!
使用这种多线程方式,假设有多个不相关的业务流程需要被处理,那么就可以创建多个类似于 MyWork 的类,将业务流程放多类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中 moveToThread() 就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。