C++客户端Qt开发——多线程编程(一)

news2024/9/21 12:29:42

多线程编程(一)

①QThread

在Qt中,多线程的处理一般是通过QThread类来实现。

QThread代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。

QThread对象管理程序中的一个控制线程。

run()

线程的入口函数

start()

通过调用run()开始执行函数,操作系统将根据优先级参数调度线程,如果线程已经在运行,这个函数什么也不做

currentThread()

返回一个指向管理当前执行线程的QThread的指针

isRunning()

如果线程正在运行则返回true,否则返回false

sleep() / msleep() / usleep()

使线程休眠,单位为秒/毫秒/微秒

wait()

阻塞线程,直到满足以下任何一个条件:

  • 与此QThread对象关联的线程已经完成执行(即当它从run(0返回时)。如果线程已经完成,这个函数将返回true。如果线程尚未启动,它也返回true。
  • 已经过了几毫秒。如果时间是ULONG MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回false。
  • 这提供了与POSIX pthread_join()函数类似的功能。

terminate()

终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。在terminate()之后使用QThread:wait()来确保。

finished()

当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。

②两种多线程使用方式

1>第一种

Qt中提供的多线程的第一种使用方式的特点是:简单

  1. 需要创建一个线程类的子类,让其继承QT中的线程类QThread,例如:
class MyThread:public QThread
{
    ......
}
  1. 重写父类中的run()方法,在该函数内部编写子线层要处理的具体的业务流程
class MyThread:public QThread
{
    ......
 protected:
    void run()
    {
        ........
    }
}
  1. 在主线程中new一个子线程对象,在主线程中合适的位置启动子线程,调用start()方法
MyThread * subThread = new MyThread;

subThread->start();

不能在类的外部调用run() 方法启动子线程,在外部调用start()相当于让run()开始运行

当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意事项:

  • 在Qt中在子线程中不要操作程序中的窗口类型对象, 不允许, 如果操作了程序就挂了
  • 只有主线程才能操作程序中的窗口对象, 默认的线程就是主线程, 自己创建的就是子线程

这种在程序中添加子线程的方式是非常简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。

示例:生成随机数,然后使用冒泡排序和快速排序对其进行处理

创建类的方法就不多说了

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include<QVector>

//生成随机数,将构造类的名字直接改成Generate更明确一些
class Generate : public QThread
{
    Q_OBJECT
public:
    explicit Generate(QObject *parent = nullptr);

    //将主线程传递过来的数保存到m_num
    void recvNum(int num);

protected:
    void run() override;

signals:
    void sendArray(QVector<int>);

private:
    int m_num;

};


class BubbleSort : public QThread
{
    Q_OBJECT
public:
    explicit BubbleSort(QObject *parent = nullptr);

    //将主线程传递过来的数保存到m_num
    void recvArray(QVector<int> list);

protected:
    void run() override;

signals:
    void finish(QVector<int>);

private:
    QVector<int> m_list;

};

class QuickSort : public QThread
{
    Q_OBJECT
public:
    explicit QuickSort(QObject *parent = nullptr);

    //将主线程传递过来的数保存到m_num
    void recvArray(QVector<int> list);

protected:
    void run() override;

private:
    void quickSort(QVector<int> &list,int l, int r);

signals:
    void finish(QVector<int>);

private:
    QVector<int> m_list;

};

#endif // MYTHREAD_H

 mythread.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;

        int x = s[l];
        while(i < j)
        {
            while(i < j && s[j] >= x)
            {
                j--;
            }
            if(i < j)
            {
                s[i++] = s[j];
            }

            while(i < j && s[i] < x)
            {
                i++;
            }
            if(i < j)
            {
                s[j--] = s[i];
            }
        }
        s[i] = x;
        quickSort(s,l,i-1);
        quickSort(s,i+1,r);
    }
}

 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void starting(int num);

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

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);


    //接收子线程发送的数据,显示在randlist里,同时启动两个排序子线程
    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::destroyed,this,[=]()
    {
        //线程释放
        gen->quit();
        gen->wait();
        gen->deleteLater();

        bubble->quit();
        bubble->wait();
        bubble->deleteLater();

        quick->quit();
        quick->wait();
        quick->deleteLater();

    });

}

MainWindow::~MainWindow()
{
    delete ui;
}

main.cpp 

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //向 Qt 元对象系统注册 QVector<int> 类型
    qRegisterMetaType<QVector<int>>("QVector<int>");
    MainWindow w;
    w.show();
    return a.exec();
}

为什么需要注册类型?

在 Qt 中,某些类型在信号和槽机制中传递时需要被注册。Qt 默认支持一些基本类型(如 intdoubleQString 等),但是对于自定义类型或者某些复杂类型(如 QVector<int>),需要显式注册。

  • 信号和槽机制: Qt 的信号和槽机制允许对象之间进行通信。当一个信号发出时,连接到该信号的槽函数会被调用。如果你希望信号或槽函数使用 QVector<int> 这样的类型,你需要向 Qt 注册该类型,这样 Qt 才能在运行时识别和处理这种类型。
  • QMetaType 系统: Qt 的元对象系统(QMetaType)需要知道所有使用的类型,以便能够在运行时进行类型转换、创建对象和处理信号槽连接等操作。通过 qRegisterMetaType 注册类型,可以确保 Qt 的元对象系统能够识别并处理这种类型。

2>第二种

Qt提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些

  1. 创建一个新的类,让其从QObject派生(QObject类提供moveToThread()方法)
class MyWork:public QObject
{
    .......
}
  1. 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
class MyWork:public QObject
{
public:
    .......
    // 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
    void working();
}
  1. 在主线程中创建一个QThread对象,这就是子线程的对象
QThread* sub = new QThread;
  1. 在主线程中创建工作的类对象
MyWork* work = new MyWork(this);    // 错误,这里不能指定this
//如果指定了父类为this,那就无法移动任务到其他的线程中
MyWork* work = new MyWork;          // 正确,什么也不用指定
  1. 将MyWork对象移动到创建的子线程对象中,需要调用QObject类提供的moveToThread()方法
// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了,也就是第四步中提到的this
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作
  1. 启动子线程,调用start(),这时候线程启动了,但是移动到线程中的对象并没有工作
  2. 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的
示例:生成随机数,然后使用冒泡排序和快速排序对其进行处理

直接复制了上边示例的项目,所以这里的类名还是一致的,但是这种方法是从QObject中派生的,命名在这里用于区分代码就好

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include<QVector>

//生成随机数,将构造类的名字直接改成Generate更明确一些
class Generate : public QObject
{
    Q_OBJECT
public:
    explicit Generate(QObject *parent = nullptr);

    void working(int num);

signals:
    void sendArray(QVector<int>);

};


class BubbleSort : public QObject
{
    Q_OBJECT
public:
    explicit BubbleSort(QObject *parent = nullptr);

    void working(QVector<int> list);

signals:
    void finish(QVector<int>);

};

class QuickSort : public QObject
{
    Q_OBJECT
public:
    explicit QuickSort(QObject *parent = nullptr);

    void working(QVector<int> list);

private:
    void quickSort(QVector<int> &list,int l, int r);

signals:
    void finish(QVector<int>);

};

#endif // MYTHREAD_H

 mythread.cpp

#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;

        int x = s[l];
        while(i < j)
        {
            while(i < j && s[j] >= x)
            {
                j--;
            }
            if(i < j)
            {
                s[i++] = s[j];
            }

            while(i < j && s[i] < x)
            {
                i++;
            }
            if(i < j)
            {
                s[j--] = s[i];
            }
        }
        s[i] = x;
        quickSort(s,l,i-1);
        quickSort(s,i+1,r);
    }
}

 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void starting(int num);

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

 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);


    //接收子线程发送的数据,显示在randlist里,同时启动两个排序子线程
    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::destroyed,this,[=]()
    {
        //线程释放
        t1->quit();
        t1->wait();
        t1->deleteLater();

        t2->quit();
        t2->wait();
        t2->deleteLater();

        t3->quit();
        t3->wait();
        t3->deleteLater();

        //任务对象释放
        gen->deleteLater();
        bubble->deleteLater();
        quick->deleteLater();
    });

}

MainWindow::~MainWindow()
{
    delete ui;
}

 main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qRegisterMetaType<QVector<int>>("QVector<int>");
    MainWindow w;
    w.show();
    return a.exec();
}

connect函数第五个参数

connect()函数第五个参数为Qt:ConnectionType,用于指定信号和槽的连接类型。同时影响信号的传递方式和槽函数的执行顺序。只有在多线程的时候才意义。

Qt:ConnectionType提供了以下五种方式:

Qt:AutoConnection

在Qt中,会根据信号和槽函数所在的线程自动选择连接类型。如果信号和槽函数在同一线程中,那么使用Qt:DirectConnection类型;如果它们位于不同的线程中,那么使用Qt:QueuedConnection类型。

Qt:DirectConnection

当信号发出时,槽函数会立即在同一线程中执行。这种连接类型适用于信号和槽函数在同一线程中的情况,可以实现直接的函数调用,但需要注意线程安全性

Qt::QueuedConnection

当信号发出时,槽函数会被插入到接收对象所属的线程的事件队列中,等待下一次事件循环时执行。这种连接类型适用于信号和槽函数在不同线程中的情况,可以确保线程安全

Qt:BlockingQueuedConnection

Qt:QueuedConnection类似,但是发送信号的线程会被阻塞,直到槽函数执行完毕,这种连接类型适用于需要等待槽函数执行完毕再继续的场景,但需要注意可能引起线程死锁的风险

Qt:UniqueConnection

这是一个标志,可以使用位或与上述任何一种连接类型组合使用。

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

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

相关文章

用Python插入表格到PowerPoint演示文稿

有效的信息传达是演示文稿中的重点&#xff0c;而PowerPoint演示文稿作为最广泛使用的演示工具之一&#xff0c;提供了丰富的功能来帮助演讲者实现这一目标。其中&#xff0c;在演示文稿中插入表格可以帮助观众更直观地理解数据和比较信息。通过使用Python这样的强大编程语言&a…

前端优化之图片的渐进式加载

起因&#xff1a; 在访问自己部署的前端项目的时候发现&#xff0c;背景图片加载太慢&#xff0c;并不是很美观。 这是因为&#xff0c;除了 JavaScript 和 CSS&#xff0c;网站通常还会包含大量的图片。当自己把<img> 元素添加到网站里面时&#xff0c;对应的所有图片…

计算机网络-基于PIM-DM+IGMP的组播实验配置

前面我们将IGMP协议和PIM-DM协议理论知识都学完了&#xff0c;现在开始进入实践&#xff0c;毕竟只有完成实践是最好的检验方式。IGMP是用于感知组播组成员&#xff0c;而PIM-DM是用于在域内构建组播分发树的的协议&#xff0c;本次实验使用这两项技术进行分析与实践。 一、拓扑…

操作系统与进程简单介绍

操作系统与进程 操作系统进程 操作系统 上一篇博客中介绍了操作系统到底层硬件它们之间的一个关系&#xff0c;那么还是这张图 操作系统到用户它们之间的关系又是如何的呢&#xff1f; 又回到了最根本的问题上&#xff1a;为什么要有操作系统呢&#xff1f; 1、向下管理好软…

jQuery入门(五)Ajax和json

一、Ajax 简介 AJAX(Asynchronous JavaScript And XML)&#xff1a;异步的 JavaScript 和 XML。 本身不是一种新技术&#xff0c;而是多个技术综合。用于快速创建动态网页的技术。 一般的网页如果需要更新内容&#xff0c;必需重新加载个页面。 而 AJAX 通过浏览器与服务器进行…

一篇文章读懂抖音短视频矩阵系统:核心功能与优势分析

抖音短视频矩阵系统作为当下备受欢迎的内容创作与分发平台&#xff0c;已经吸引了大量用户和创作者的关注。本文将详细介绍抖音短视频矩阵系统的核心功能与优势&#xff0c;帮助您全面了解这一强大的内容创作工具。 1. 抖音短视频矩阵系统 抖音短视频矩阵系统是一个集创作、编…

【Hot100】LeetCode—287. 寻找重复数

目录 题目1- 思路2- 实现⭐287. 寻找重复数——题解思路 3- ACM 实现 题目 原题连接&#xff1a;287. 寻找重复数 1- 思路 快慢指针 2- 实现 ⭐287. 寻找重复数——题解思路 class Solution {public int findDuplicate(int[] nums) {int slow nums[0];int fast nums[0];//…

DB-Engines Ranking 2024年8月数据库排行

DB-Engines Ranking 2024年8月数据库排行 DB-Engines排名根据数据库管理系统的受欢迎程度进行排名。排名每月更新一次。 2024年8月&#xff0c;共有423个数据库进入排行。 排行榜 前15名趋势图 关系型数据库前 10 名 键值数据库前 10 名 文档数据库前 10 名 时序数据库前 10 …

从0到1:构建高性能的视频美颜SDK和直播美颜插件

本篇文章&#xff0c;笔者将探讨如何从0到1&#xff0c;构建一个高性能的视频美颜SDK和直播美颜插件&#xff0c;助力开发者打造出色的产品。 1.需求分析与技术选型 通常情况下&#xff0c;视频美颜功能需要包括基础的滤镜效果、磨皮美白、面部特征优化等。这些功能既要保证实…

C++入门级文章

一、一个用于查询C标准库内函数、操作符等的链接 https://legacy.cplusplus.com/reference/ 声明&#xff1a;该文档并非官方文档&#xff0c;但其具有易于查询和使用的优势&#xff0c;足够日常使用。 二、C的第一个程序 1、C语言中的语法在C中仍旧适用&#xff0c;首先我们来…

书生大模型学习笔记 - Python

Python实现wordcount 请实现一个wordcount函数&#xff0c;统计英文字符串中每个单词出现的次数。返回一个字典&#xff0c;key为单词&#xff0c;value为对应单词出现的次数。 解题思路&#xff1a;首先把字母转小写>然后把单词取出来去除标点>循环单词列表>key已存…

老板让你点评网页UI,你却不知道如何说起……

当评价网页UI设计时&#xff0c;可以参考以下几个标准&#xff1a; 1. 一致性&#xff08;Consistency&#xff09;&#xff1a; 一个优秀的网页UI应该保持一致性&#xff0c;即在整个网页中使用相同的设计元素和样式&#xff0c;如颜色、字体、按钮样式等。这样可以增加用户…

2024年【北京市安全员-B证】新版试题及北京市安全员-B证模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 北京市安全员-B证新版试题考前必练&#xff01;安全生产模拟考试一点通每个月更新北京市安全员-B证模拟试题题目及答案&#xff01;多做几遍&#xff0c;其实通过北京市安全员-B证作业考试题库很简单。 1、【多选题】…

Html实现全国省市区三级联动

目录 前言 1.全国省市区的Json数据 2.找到Json数据文件(在此博文绑定资源)之后&#xff0c;放到resource目录下。 3.通过类加载器加载资源文件&#xff0c;读取Json文件 3.1 创建JsonLoader类 3.2 注入JsonLoader实体&#xff0c;解析Json文件 4.构建前端Html页面 5.通过…

至尊雄心:成为不甘平庸的男人,掌握顶级赢家思维

至尊雄心&#xff1a;成为不甘平庸的男人&#xff0c;掌握顶级赢家思维 嘿&#xff0c;伙计们&#xff01;如果你是个有抱负的男人&#xff0c;或者你想要成为一个有成就的男人&#xff0c;那么这篇文章就是为你量身定做的。这里&#xff0c;我们将一起探讨那些顶级赢家的思维…

开源蓝牙协议栈-Zephyr Bluetooth

关于Zephyr的介绍&#xff0c;参考&#xff1a; https://blog.csdn.net/2201_75889983/article/details/129366754 Zephyr最初是由Wind River公司开发的一个微内核&#xff0c;在2016年的时候成为Linux基金会维护的一个项目&#xff0c;发展至今&#xff0c;已经成为了一个功能…

国内访问github出现无法访问,用Watt Toolkit加速

文章目录 前置1. 访问github出现“无法访问...”2. 安装Watt Toolkit点击下载接受并下载下载渠道 Watt Toolkit 就绪侧栏“网络加速”下拉滚动条勾选github&#xff0c;点右上“一键加速”再次访问 github.com ,可以访问 前置 准备好微软账号 1. 访问github出现“无法访问…”…

vulnhub靶机:Tomato

目录 靶机导入 信息收集 发现 IP 目录扫描 端口扫描 访问 web 漏洞利用 方法1&#xff1a;报错连接拿 shell 方法2&#xff1a; 使用python3进行反弹shell 提权 靶机导入 tomato 下载地址&#xff1a;Tomato: 1 ~ VulnHub 信息收集 发现 IP arp-scan -l 发现靶机 IP…

【Qt】QMainWindow之菜单栏

目录 一.菜单栏 1.概念 2.组成 二.代码创建菜单栏 1.创建菜单栏 2.在菜单栏中添加菜单 3.在菜单中添加菜单项 三.图形化创建菜单栏 1.在打开Qt自带的ui文件界面后&#xff0c;得到以下界面 2.双击点击界面中&#xff08;在这里输入&#xff09;&#xff0c;在菜单栏中进行…

【开端】JAVA日志框架LogFactory

熟悉的一行代码 private static final Log logger LogFactory.getLog(Application.class); 这一行代码就是使用了LogFactory日志框架&#xff0c;对类Application&#xff0c;进行日志输出。 private static final 这里表示虚拟机启动后就创建一个最终的日志对象Log logger 创…