Data Analysis and Visualization of POI Checking-In Data


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

  1. provide a dialog that allows users to select the .csv file and load it from disk.

  2. provide boxes that allow users to tune the starting and ending week for display.

  3. 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() 就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。


评论
评论
  目录